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

Posted

技术标签:

【中文标题】MidiInProc 回调在哪个上下文中执行?【英文标题】:In which context is the MidiInProc callback executed? 【发布时间】:2020-02-10 20:18:13 【问题描述】:

帮助!需要心平气和。 我正在编写一个使用 Windows MidiInProc 作为回调和虚拟 MIDI 端口的照明控制台应用程序。

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

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

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

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

已经用了3年了,至今没有出现任何问题,但不知道。

【问题讨论】:

移除标签 processing>。 Processing 是一个灵活的软件速写本,也是一种用于学习如何在视觉艺术环境中编码的语言。 【参考方案1】:

回调是从另一个线程调用的,大部分时间。为了证明这一点,考虑这个改编自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。

【讨论】:

以上是关于MidiInProc 回调在哪个上下文中执行?的主要内容,如果未能解决你的问题,请参考以下文章

使用类方法作为回调时的 Promise.then 执行上下文

如何检测 MidiInProc 中丢失的 SysEx 数据?

前端小组分享会之异步回调函数中的上下文

node.js初步了解——慕课网(回调,作用域,上下文)

如何检索已执行上下文菜单的元素

OpenLayers RenderComplete 事件回调在回调中使用此上下文