В каком контексте выполняется обратный вызов MidiInPro c? - PullRequest
1 голос
/ 10 февраля 2020

Помощь! Нужно душевное спокойствие. Я заканчиваю писать приложение консоли управления освещением, которое использует windows MidiInPro c в качестве обратного вызова и виртуальный порт MIDI.

'Откройте устройство midi и настройте обратный вызов ret = midiInOpen ( @ hDevice, devNo, cast (uinteger,@MidiInProc), 0, CALLBACK_FUNCTION)

Когда получено сообщение midi, midiInPro c получает доступ к циклической очереди для хранения сообщений midi, поэтому сообщения не удаляются (очень важно для театрального освещения) и основная программа снимает их с очереди для последующей обработки.

Как работает обратный вызов. Он прерывает основную программу или запускается в своем собственном потоке или как?

Существует ли вероятность того, что обратный вызов конфликтует с основной программой при попытке доступа к очереди одновременно. Если так, как я могу предотвратить это?

Пользуюсь программой 3 года, и никаких проблем не возникло, но никто не знает.

1 Ответ

0 голосов
/ 12 февраля 2020

В большинстве случаев обратный вызов вызывается из другого потока. Чтобы доказать это, рассмотрим этот пример программы, адаптированный из midi примера программы 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 вашу программу потоков во время ее работы:

Process threads

Вы можете заметить, что в вашем процессе есть как минимум 2 идентификатора потока: 9656 и 5684. Ваш идентификатор потока функции main() равен 9656, и обратные вызовы для Вызовы функций midiInOpen() и midiInClose() выводят один и тот же идентификатор. Но для событий примечания идентификатор потока равен 5684. И начальный адрес этого потока соответствует модулю wdmaud.drv, который является драйвером Windows.

Это типичный сценарий для любого процесса, обрабатывающего ввод MIDI : производитель и потребитель проблема. Ваш подход обоснован: вы ставите в очередь полученные MIDI-события в функции обратного вызова (продюсер), а затем другой поток потребляет события в очереди. Вы найдете множество реализаций без блокировки кольца , которые подходят для этой задачи.

Существует другой вариант функции midiInOpen(), который использует флаги последнего аргумента CALLBACK_WINDOW или CALLBACK_THREAD. В этом случае вместо функции обратного вызова вы предоставляете дескриптор окна или идентификатор потока для Windows, и ваша процедура окна или потока будет получать MIDI-сообщения, помещенные в очередь и чередующиеся с другими несвязанными событиями windows. Я предпочитаю использовать CALLBACK_FUNCTION.

...