使用JNR-FFI向CMD屏幕缓冲区输出字符表时程序崩溃求助
问题:调用Windows控制台API写入屏幕缓冲区后崩溃(错误0xC0000005)
编写Java库时,通过JNR-FFI调用Kernel32的控制台API直接访问并写入CMD屏幕缓冲区,程序能成功打印第一帧,但之后会以退出代码0xC0000005崩溃。在CMD中运行时冻结无异常,IntelliJ控制台显示错误码,事件查看器记录崩溃模块为KERNELBASE.dll。
原代码
import jnr.ffi.LibraryLoader; import jnr.ffi.Pointer; public class Main { public interface Kernel32 { Kernel32 INSTANCE = LibraryLoader.create(Kernel32.class).load("kernel32"); int GENERIC_READ = 0x80000000; int GENERIC_WRITE = 0x40000000; int FILE_SHARE_READ = 0x00000001; int FILE_SHARE_WRITE = 0x00000002; int CONSOLE_TEXTMODE_BUFFER = 1; Pointer CreateConsoleScreenBuffer(int dwDesiredAccess, int dwShareMode, Pointer lpSecurityAttributes, int dwFlags, Pointer lpScreenBufferData); void SetConsoleActiveScreenBuffer(Pointer hConsoleOutput); int WriteConsoleOutputCharacterW(Pointer hConsoleOutput, char[] lpCharacter, int nLength, int dwWriteCoord, int lpNumberOfCharsWritten); } private static final int nScreenWidth = 240; private static final int nScreenHeight = 135; static Kernel32 kernel32 = Kernel32.INSTANCE; private static Pointer hConsole = kernel32.CreateConsoleScreenBuffer( Kernel32.GENERIC_WRITE | Kernel32.GENERIC_READ, Kernel32.FILE_SHARE_READ | Kernel32.FILE_SHARE_WRITE, null, Kernel32.CONSOLE_TEXTMODE_BUFFER, null ); public static void writeCMD(char[] screen){ try { if (screen.length == (nScreenWidth * nScreenHeight) + 1) { int lpNumberOfCharsWritten = 0; System.out.println(kernel32.WriteConsoleOutputCharacterW(hConsole, screen, (nScreenWidth * nScreenHeight) + 1, 0, lpNumberOfCharsWritten)); } } catch (Exception t) { t.printStackTrace(); } } public static void main(String[] args) { kernel32.SetConsoleActiveScreenBuffer(hConsole); char[] screen = new char[(nScreenWidth * nScreenHeight) + 1]; for (int i = 0; i < screen.length - 1; i++) { screen[i] = '-'; } screen[screen.length - 1] = '\0'; int position = 0; while(true){ screen[position] = '#'; writeCMD(screen); screen[position] = '-'; position++; } } }
崩溃日志
Faulting application name: java.exe, version: 20.0.2.0, time stamp: 0x75f7bb13 Faulting module name: KERNELBASE.dll, version: 10.0.22621.2715, time stamp: 0x10f6a783 Exception code: 0xc0000005 Fault offset: 0x00000000000b5bdc Faulting process id: 0x0x5B98 Faulting application start time: 0x0x1DA1CB1377C8871 Faulting application path: C:\Program Files\Java\jdk-20\bin\java.exe Faulting module path: C:\Windows\System32\KERNELBASE.dll Report Id: 97a532d2-82fd-4b33-a399-ee74ea9e606d Faulting package full name: Faulting package-relative application ID:
解决方案
崩溃的核心原因是**WriteConsoleOutputCharacterW最后一个参数的错误传递**:该参数是Windows API的输出参数(用于返回实际写入的字符数),需要传递一个可写入的内存地址,而原代码直接传入int值0,导致API尝试往内存地址0写入数据,触发访问违规错误(0xC0000005)。
关键修改步骤
- 修正Kernel32接口中
WriteConsoleOutputCharacterW的方法签名,将最后一个参数改为jnr.ffi.byref.IntByReference(JNR中用于传递整数输出参数的类型)。 - 在调用该方法时,创建
IntByReference实例,而非直接传入int值。 - 移除字符数组末尾的无效终止符,优化循环逻辑避免数组越界。
修改后的完整代码
import jnr.ffi.LibraryLoader; import jnr.ffi.Pointer; import jnr.ffi.byref.IntByReference; public class Main { public interface Kernel32 { Kernel32 INSTANCE = LibraryLoader.create(Kernel32.class).load("kernel32"); int GENERIC_READ = 0x80000000; int GENERIC_WRITE = 0x40000000; int FILE_SHARE_READ = 0x00000001; int FILE_SHARE_WRITE = 0x00000002; int CONSOLE_TEXTMODE_BUFFER = 1; Pointer CreateConsoleScreenBuffer(int dwDesiredAccess, int dwShareMode, Pointer lpSecurityAttributes, int dwFlags, Pointer lpScreenBufferData); void SetConsoleActiveScreenBuffer(Pointer hConsoleOutput); // 修改最后一个参数为IntByReference,返回值改为boolean对应API的BOOL类型 boolean WriteConsoleOutputCharacterW(Pointer hConsoleOutput, char[] lpCharacter, int nLength, int dwWriteCoord, IntByReference lpNumberOfCharsWritten); } private static final int nScreenWidth = 240; private static final int nScreenHeight = 135; static Kernel32 kernel32 = Kernel32.INSTANCE; private static Pointer hConsole = kernel32.CreateConsoleScreenBuffer( Kernel32.GENERIC_WRITE | Kernel32.GENERIC_READ, Kernel32.FILE_SHARE_READ | Kernel32.FILE_SHARE_WRITE, null, Kernel32.CONSOLE_TEXTMODE_BUFFER, null ); public static void writeCMD(char[] screen){ try { if (screen.length == nScreenWidth * nScreenHeight) { // 创建IntByReference实例接收实际写入的字符数 IntByReference charsWritten = new IntByReference(); kernel32.WriteConsoleOutputCharacterW( hConsole, screen, nScreenWidth * nScreenHeight, 0, charsWritten ); } } catch (Exception t) { t.printStackTrace(); } } public static void main(String[] args) { kernel32.SetConsoleActiveScreenBuffer(hConsole); // 移除多余的终止符,直接创建对应缓冲区大小的数组 char[] screen = new char[nScreenWidth * nScreenHeight]; for (int i = 0; i < screen.length; i++) { screen[i] = '-'; } int position = 0; while(true){ // 防止数组越界,到达末尾后重置位置 if (position >= screen.length) { position = 0; } screen[position] = '#'; writeCMD(screen); screen[position] = '-'; position++; // 添加短暂延迟,降低CPU占用并让动画更直观 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }
内容的提问来源于stack exchange,提问作者SX12C
相关产品推荐
相关产品推荐

