通过JNI在Java中使用ncurses:窗口Resize无法触发getch问题
问题描述
我想用JVM语言开发文本应用,目前尝试通过JNI调用ncurses库。在纯C环境中使用ncurses时,调整终端窗口大小会触发getch()并返回410(KEY_RESIZE);但通过JNI在Java中调用时,getch()完全无响应,必须按下其他按键才会有输出。
我有三个疑问:
- 这是JVM的限制吗?
- 有没有解决办法?
- 我是不是根本不该通过JNI使用ncurses?
可复现代码
Java代码
public class Main { public static native void init(); public static native int getch(); public static native void printw(String string); public static native void endwin(); public static void main(String[] args) { System.loadLibrary("jcurses"); init(); for (int key; (key = getch()) != 'q';) printw("%d\n".formatted(key)); endwin(); } }
C代码(JNI绑定)
#include <ncurses.h> #include "Main.h" JNIEXPORT void JNICALL Java_Main_init(JNIEnv *env, jclass clazz) { initscr(); keypad(stdscr, TRUE); noecho(); } JNIEXPORT jint JNICALL Java_Main_getch(JNIEnv *env, jclass clazz) { return (long) getch(); } JNIEXPORT void JNICALL Java_Main_printw(JNIEnv *env, jclass clazz, jstring javaString) { const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0); printw("%s", nativeString); (*env)->ReleaseStringUTFChars(env, javaString, nativeString); } JNIEXPORT void JNICALL Java_Main_endwin(JNIEnv *env, jclass clazz) { endwin(); }
Makefile
all: javac -h . Main.java gcc -I"$$JAVA_HOME/include" -I"$$JAVA_HOME/include/linux" jcurses.c -shared -o libjcurses.so -lncurses run: java -Djava.library.path=. Main
对比用的纯C代码
#include <ncurses.h> int main() { initscr(); keypad(stdscr, TRUE); noecho(); for (int key; (key = getch()) != 'q';) printw("%d\n", key); endwin(); return 0; }
测试环境
- x86架构Ubuntu22.04,通过Windows7的Git Bash远程连接
- arm64架构Ubuntu22.04,本地Gnome终端
回答
这是JVM的限制吗?
是的,本质源于JVM的线程模型和信号处理机制。ncurses依赖SIGWINCH信号检测窗口大小变化,该信号会唤醒阻塞的getch()调用。但JVM会接管进程的信号处理逻辑,默认忽略或修改了SIGWINCH的处理流程,导致ncurses无法收到信号触发的唤醒,getch()会一直阻塞到有按键输入。有没有解决该问题的方法?
有几种可行方案:
- 在JNI层手动处理
SIGWINCH:在JNI初始化代码中重新注册SIGWINCH信号处理函数,调用ncurses的resizeterm()并手动唤醒阻塞的getch()。需注意避免与JVM信号处理冲突,示例修改如下:#include <signal.h> #include <unistd.h> static void handle_sigwinch(int sig) { resizeterm(); // 发送无关信号唤醒getch() kill(getpid(), SIGUSR1); } JNIEXPORT void JNICALL Java_Main_init(JNIEnv *env, jclass clazz) { initscr(); keypad(stdscr, TRUE); noecho(); struct sigaction sa; sa.sa_handler = handle_sigwinch; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGWINCH, &sa, NULL); // 忽略SIGUSR1避免程序终止 signal(SIGUSR1, SIG_IGN); } - 非阻塞
getch()轮询:在Java层用线程定时调用非阻塞的getch()(先执行nodelay(stdscr, TRUE)),同时检查窗口大小变化。但这种方式会增加CPU占用,效率不如信号触发。 - 使用成熟的JVM终端库:比如纯Java实现的Lanterna,或基于JNI封装好的JCurses,这些库已处理好窗口大小变化等底层问题,无需自行实现。
- 我是否根本不应该通过JNI使用ncurses?
如果只是开发常规文本应用,不建议自行通过JNI绑定ncurses——不仅要处理信号、线程等底层问题,还要维护JNI代码,跨平台兼容性也差。优先选择:
- Lanterna:纯Java实现,多平台兼容,API友好,内置窗口大小变化处理。
- JCurses:现成的ncurses JNI绑定库,已解决这类底层问题。
若必须使用ncurses的特定功能,可自行处理JNI的信号问题,但需做好多平台兼容性测试。
内容的提问来源于stack exchange,提问作者Plus
相关产品推荐
相关产品推荐

