回调是从另一个线程调用的,大部分时间。为了证明这一点,考虑这个改编自midi sample program gist的示例程序:
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
printf("Callback thread id=%ld\n", GetCurrentThreadId());
switch (wMsg) {
case MIM_OPEN:
printf("wMsg=MIM_OPEN\n");
break;
case MIM_CLOSE:
printf("wMsg=MIM_CLOSE\n");
break;
case MIM_DATA:
printf("wMsg=MIM_DATA, dwInstance=%Ix, dwParam1=%Ix, dwParam2=%Ix\n", dwInstance, dwParam1, dwParam2);
break;
case MIM_LONGDATA:
printf("wMsg=MIM_LONGDATA\n");
break;
case MIM_ERROR:
printf("wMsg=MIM_ERROR\n");
break;
case MIM_LONGERROR:
printf("wMsg=MIM_LONGERROR\n");
break;
case MIM_MOREDATA:
printf("wMsg=MIM_MOREDATA\n");
break;
default:
printf("wMsg = unknown\n");
break;
}
return;
}
int main(int argc, char* argv[])
{
HMIDIIN hMidiDevice = nullptr;;
DWORD nMidiPort = 2;
UINT nMidiDeviceNum;
MMRESULT rv;
printf("Main thread id=%ld\n", GetCurrentThreadId());
nMidiDeviceNum = midiInGetNumDevs();
if (nMidiDeviceNum == 0) {
fprintf(stderr, "midiInGetNumDevs() return 0...");
return -1;
}
rv = midiInOpen(&hMidiDevice, nMidiPort, (DWORD_PTR)(void*)MidiInProc, 0, CALLBACK_FUNCTION);
if (rv != MMSYSERR_NOERROR) {
fprintf(stderr, "midiInOpen() failed...rv=%d", rv);
return -1;
}
midiInStart(hMidiDevice);
while (true) {
if (!_kbhit()) {
Sleep(100);
continue;
}
int c = _getch();
if (c == VK_ESCAPE) break;
if (c == 'q') break;
}
midiInStop(hMidiDevice);
midiInClose(hMidiDevice);
return 0;
}
在我的系统中执行它,连接了 3 个 MIDI 设备(#2 是一个控制器),按下并释放一个键后我得到这个输出:
Main thread id=9656
Callback thread id=9656
wMsg=MIM_OPEN
Callback thread id=5684
wMsg=MIM_DATA, dwInstance=0, dwParam1=513190, dwParam2=cfb
Callback thread id=5684
wMsg=MIM_DATA, dwInstance=0, dwParam1=403180, dwParam2=eaa
您可以在运行时在ProcessHacker2 或 SysInternals 的ProcessExplorer 中检查您的程序线程:
您可能会观察到您的进程中至少有 2 个线程 ID:9656 和 5684。您的 main() 函数线程 ID 是 9656,midiInOpen() 和 midiInClose() 函数调用的回调打印相同ID。但是对于note事件来说,线程id是5684,这个线程的起始地址对应的是wdmaud.drv模块,这是一个Windows驱动程序。
这是处理 MIDI 输入的任何进程的典型场景:producer and consumer 问题。您的方法是合理的:您将接收到的 MIDI 事件排入回调函数(生产者)中,然后另一个线程使用排队的事件。您会发现许多适合该任务的lock free ring buffer 实现。
midiInOpen() 函数还有另一个变体,它使用最后一个参数标志 CALLBACK_WINDOW 或 CALLBACK_THREAD。在这种情况下,您可以向 Windows 提供窗口句柄或线程 ID,而不是回调函数,您的窗口或线程过程将接收排队的 MIDI 消息,并与其他不相关的窗口事件交错。我的偏好是使用 CALLBACK_FUNCTION。