MidiInProc 回调在哪个上下文中执行?

In which context is the MidiInProc callback executed?

求助!需要安心。 我正在完成编写一个照明控制台应用程序,该应用程序使用 windows MidiInProc 作为回调和虚拟 MIDI 端口。

'打开midi设备并设置回调 ret=midiInOpen(@hDevice,devNo,cast(uinteger,@MidiInProc),0,CALLBACK_FUNCTION)

当接收到 midi 消息时,midiInProc 访问一个循环队列来存储 midi 消息,因此不会丢弃任何消息(对于剧院照明非常重要)并且主程序将它们出列以进行后续处理。

回调如何工作。它会在自己的线程中中断主程序或 运行 还是什么?

是否有可能回调与主程序在尝试同时访问队列时发生冲突。如果是这样,我该如何防止这种情况发生?

程序用了3年了,没出过什么问题,谁知道呢

大部分时间回调都是从另一个线程调用的。为了证明这一点,请考虑改编自 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 or in SysInternals' ProcessExplorer 程序线程处于 运行 时检查它:

您可能会观察到您的进程中至少有 2 个线程 ID:9656 和 5684。您的 main() 函数线程 ID 是 9656,midiInOpen() 和 [= 的回调14=] 函数调用打印相同的 id。但是对于note事件,thread id是5684。而这个thread的起始地址对应的是wdmaud.drv模块,是一个Windows驱动。

这是处理 MIDI 输入的任何进程的典型场景:producer and consumer problem. Your approach is sound: you enqueue the received MIDI events in the callback function (the producer), and then another thread consumes the queued events. You will find many lock free ring buffer 适合任务的实现。

midiInOpen() 函数还有另一个变体,它使用最后一个参数标志 CALLBACK_WINDOW 或 CALLBACK_THREAD。在这种情况下,您可以向 Windows 提供 window 句柄或线程 ID,而不是回调函数,并且您的 window 或线程过程将接收排队并与其他不相关的交错的 MIDI 消息windows 个事件。我的偏好是使用 CALLBACK_FUNCTION.