GraalVM原生镜像中无法处理MIDI输入事件问题求助
问题:GraalVM编译EXE后MIDI输入触发NoSuchMethodError异常
环境与现象
运行环境:Windows10 + GraalVM 17 + Eclipse + 标准MIDI键盘
程序在JVM上运行完全正常,可正常处理MIDI输入输出事件;但编译为EXE后,MIDI输出功能正常,按下键盘触发MIDI输入时抛出以下异常:
Exception in thread "Java Sound MidiInDevice Thread" java.lang.NoSuchMethodError: com.sun.media.sound.MidiInDevice.callbackShortMessage(IJ)V at com.oracle.svm.jni.functions.JNIFunctions$Support.getMethodID(JNIFunctions.java:1247) at com.oracle.svm.jni.functions.JNIFunctions$Support.getMethodID(JNIFunctions.java:1232) at com.oracle.svm.jni.functions.JNIFunctions.GetMethodID(JNIFunctions.java:412) at com.sun.media.sound.MidiInDevice.nGetMessages(MidiInDevice.java) at com.sun.media.sound.MidiInDevice.run(MidiInDevice.java:127) at java.lang.Thread.run(Thread.java:833) at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704) at com.oracle.svm.core.windows.WindowsPlatformThreads.osThreadStartRoutine(WindowsPlatformThreads.java:143)
已尝试调整初始化时机(构建时/运行时),问题未解决。
完整代码
import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiMessage; import javax.sound.midi.MidiSystem; import javax.sound.midi.Receiver; import javax.sound.midi.ShortMessage; import javax.sound.midi.Transmitter; public class HelloMidi { /** Main method. */ public static void main(String[] args) { System.err.println("HelloMidi"); //-Recover MIDI in & out final Receiver midiOut=pickMidiOut("ESI M4U XT"); final Transmitter midiIn=pickMidiIn("MIDIIN4 (ESI M4U XT)"); //-Define what to do with events recovere for keyboard midiIn.setReceiver(new Receiver() { /** {@inheritDoc} */ @Override public final void send(final MidiMessage message,final long timeStamp) { //-A message has just been received System.err.print("Message received->"+message.getLength()+" bytes"); for(final byte b:message.getMessage()) System.err.print(", "+Byte.toUnsignedInt(b)); System.err.println(); } /** {@inheritDoc} */ @Override public final void close() { //-Nothing for this test ! } }); assert(midiOut!=null); try { midiOut.send(new ShortMessage(144,60,120),-1); Thread.sleep(1000); midiOut.send(new ShortMessage(144,67,120),-1); Thread.sleep(1000); midiOut.send(new ShortMessage(144,72,120),-1); Thread.sleep(20000); //-Print accessible devics for information for(final MidiDevice.Info mdi:MidiSystem.getMidiDeviceInfo()) System.err.println("Device '"+mdi.getName()+"':"+mdi); } catch(final Exception e) { //-Any problem e.printStackTrace(); } //-Exit System.err.println("ByeMidi"); System.exit(0); } /** Check for requested MIDI OUT receiver. */ private static Receiver pickMidiOut(final String aName) { //-Parse all devices for(final MidiDevice.Info mdi:MidiSystem.getMidiDeviceInfo()) { //-Check name if(!mdi.getName().equals(aName)) continue; //-Try in case there are errors try { //-Recover a MIDI device final MidiDevice device=MidiSystem.getMidiDevice(mdi); //-Try to open it if(!device.isOpen()) device.open(); if(!device.isOpen()) continue; //-Check receiver final Receiver answer=device.getReceiver(); if(answer!=null) return(answer); } catch(final Throwable e) { //-Any problem continue; } } //-Return 'null' as none found return(null); } /** Check for requested MIDI IN transmitter. */ private static Transmitter pickMidiIn(final String aName) { //-Parse all devices for(final MidiDevice.Info mdi:MidiSystem.getMidiDeviceInfo()) { //-Check name if(!mdi.getName().equals(aName)) continue; //-Try in case there are errors try { //-Recover a MIDI device final MidiDevice device=MidiSystem.getMidiDevice(mdi); //-Try to open it if(!device.isOpen()) device.open(); if(!device.isOpen()) continue; //-Check receiver final Transmitter answer=device.getTransmitter(); if(answer!=null) return(answer); } catch(final Throwable e) { //-Any problem continue; } } //-Return 'null' as none found return(null); } }
解决方案
这个问题的核心是GraalVM静态编译时,未将Java Sound MIDI模块中JNI回调依赖的方法纳入编译范围,导致运行时无法找到对应方法。
1. 手动添加反射配置文件
创建reflect-config.json文件,内容如下:
[ { "name": "com.sun.media.sound.MidiInDevice", "methods": [ { "name": "callbackShortMessage", "parameterTypes": ["int", "long"] }, { "name": "callbackSysexMessage", "parameterTypes": ["byte[]", "long"] }, { "name": "callbackMetaMessage", "parameterTypes": ["int", "byte[]", "long"] } ] } ]
这些方法是MIDI输入设备通过JNI回调的核心方法,必须显式告知GraalVM保留它们。
2. 编译时指定配置文件
使用native-image命令编译时,添加配置文件参数:
native-image -cp . HelloMidi -H:ReflectionConfigurationFiles=reflect-config.json
如果使用Eclipse的GraalVM插件,在Native Image配置中添加该反射配置文件的路径即可。
3. 自动生成完整配置(可选)
如果存在其他JNI或反射相关的缺失方法,可使用GraalVM的native-image-agent生成自动配置:
java -agentlib:native-image-agent=config-output-dir=./config HelloMidi
运行程序并触发所有MIDI输入操作,./config目录下会生成完整的反射、JNI等配置文件,编译时直接引用:
native-image -cp . HelloMidi -H:ConfigurationFiles=./config/reflect-config.json,./config/jni-config.json
验证
编译完成后运行EXE,按下MIDI键盘按键,即可正常打印输入消息,不再抛出异常。
内容的提问来源于stack exchange,提问作者java71
相关产品推荐
相关产品推荐

