You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 14:24:19