首页 > 解决方案 > 从 C++ 回调函数发出 Node.js 事件

问题描述

在 Node.js 应用程序中,我需要在更改默认音频设备时收到通知。该程序将在 Windows 7 上使用。

目前,我正在尝试通过为节点制作一个 C++ 插件来做到这一点,该插件通过 Windows Core Audio API 中的 IMMNotificationClient::OnDefaultDeviceChanged 方法发出一个可由节点事件发射器接收的事件。

下面是一个 Windows Core Audio API 回调方法的示例:

HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceID)
{
   //User written code that emits a node Event.
}

这是我希望在上述回调函数中拥有的一些理想的 C++ 风格的伪代码:

EventEmitter.emit(v8::String::NewFromUtf8("defaultDeviceChanged"), deviceName);

我不确定如何实现这一点,所以这是我的问题:如何通过 C++ 回调函数向 Node.js 应用程序发出事件?

我对其他解决方案持开放态度,只要它们可以在 Node 上本地运行(IE 通过附加组件),并且可以在 Windows 7 上运行,这意味着对 nircmd 等应用程序的外部调用不可用。

编辑

这是一些实验性代码,可以尝试帮助描述我在做什么:

音频设备发射器.h:

#pragma once

#include <stdio.h>
#include <wchar.h>
#include <tchar.h>
#include <time.h>
#include "windows.h"
#include "Mmdeviceapi.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
#include <vector>
#include <string>
#include <comdef.h>
#define NAPI_DISABLE_CPP_EXCEPTIONS
#include <napi.h>
#include <vector>

class AudioDeviceEmitter : public Napi::ObjectWrap<AudioDeviceEmitter>, public IMMNotificationClient {
public:
    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    AudioDeviceEmitter(const Napi::CallbackInfo& info);


    Napi::Value AudioDeviceEmitter::enrollInNotifications(const Napi::CallbackInfo& info);
    Napi::Value AudioDeviceEmitter::unenrollInNotifications(const Napi::CallbackInfo& info);
    //WIN API 
    ~AudioDeviceEmitter()
    {
        if (_pEnumerator != NULL) {
            _pEnumerator->UnregisterEndpointNotificationCallback(this);
            _pEnumerator->Release();
        }
    }


    // IUnknown methods -- AddRef, Release, and QueryInterface

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

ULONG STDMETHODCALLTYPE Release()
{
    ULONG ulRef = InterlockedDecrement(&_cRef);
    if (0 == ulRef)
    {
        delete this;
    }
    return ulRef;
}

HRESULT STDMETHODCALLTYPE QueryInterface(
        REFIID riid, VOID **ppvInterface)
    {
        if (IID_IUnknown == riid)
        {
            AddRef();
            *ppvInterface = (IUnknown*)this;
        }
        else if (__uuidof(IMMNotificationClient) == riid)
        {
            AddRef();
            *ppvInterface = (IMMNotificationClient*)this;
        }
        else
        {
            *ppvInterface = NULL;
            return E_NOINTERFACE;
        }
        return S_OK;
    }

    // Callback methods for device-event notifications.
    //------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
    //------------It doesnt currently work, but hopefully shows----------
    //------------        what I'm trying to accomplish        ----------
    HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
        EDataFlow flow, ERole role,
        LPCWSTR pwstrDeviceId)
    {
        if (flow == eRender) {
            _locked = true;

            std::string name = "";
            IMMDevice * pDevice = NULL;
            HRESULT hr = _pEnumerator->GetDevice(pwstrDeviceId, &pDevice);
            if(SUCCEEDED(hr)){

            name = getFriendlyNameString(pDevice);
            pDevice->Release();
        }

        for(int i = 0; i < _stillEnrolled.size(); i++) {
            if(_stillEnrolled.at(i)) {
                Napi::CallbackInfo & info = _enrolledSessions.at(i);
                Napi::Env env = info.Env();
                Napi::Function emit = info.This().As<Napi::Object>()
                    .Get("emit").As<Napi::Function>();

                emit.Call(info.This(), { Napi::String::New(env, "defaultDeviceChanged"), 
                Napi::String::New(env, name.c_str())});
            }
        }

        _locked = false;
    }
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
    {
        return S_OK;
    };

    HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
    {
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
        LPCWSTR pwstrDeviceId,
        DWORD dwNewState)
    {
    return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
        LPCWSTR pwstrDeviceId,
        const PROPERTYKEY key)
    {
        return S_OK;
    }

private:

    std::string getFriendlyNameString(IMMDevice *pDevice) {
        HRESULT hr;
        IPropertyStore *pStore;
        hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);

        PROPVARIANT variant;
        PropVariantInit(&variant);
        hr = pStore->GetValue(PKEY_Device_FriendlyName, &variant);
        size_t strlen = wcslen((wchar_t *)variant.pwszVal);
        int throwAwaylen;
        char *pOutBuffer = (char *)malloc(strlen);
        wcstombs_s((size_t *)&throwAwaylen, pOutBuffer, size, wideCharArray, size);

        std::string toReturn = pOutBuffer;

        free(pOutBuffer);

        PropVariantClear(&variant);
        pStore->Release();

        return toReturn;

    }

    LONG _cRef;
    IMMDeviceEnumerator *_pEnumerator;
    static Napi::FunctionReference constructor;

    std::vector<Napi::CallbackInfo> _enrolledSessions;
    std::vector<bool> _stillEnrolled;
    bool _locked;

};

AudioDeviceEmitter.cpp

#include "AudioDeviceEmitter.h"

Napi::FunctionReference AudioDeviceEmitter::constructor;

Napi::Object AudioDeviceEmitter::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = DefineClass(env, "AudioDeviceEmitter", {
    InstanceMethod("enrollInNotifications", &AudioDeviceEmitter::enrollInNotifications),
    InstanceMethod("unenrollInNotifications", &AudioDeviceEmitter::unenrollInNotifications)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("AudioDeviceEmitter", func);
  return exports;
}

AudioDeviceEmitter::AudioDeviceEmitter(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<AudioDeviceEmitter>(info), _locked(false), _cRef(1),
        _pEnumerator(NULL)
    {
        HRESULT hr = CoInitialize(NULL);

        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
            CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&_pEnumerator);

        _pEnumerator->RegisterEndpointNotificationCallback(this);
    }

    //------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
    //------------It doesnt currently work, but hopefully shows----------
    //------------        what I'm trying to accomplish        ----------
Napi::Value AudioDeviceEmitter::enrollInNotifications(const Napi::CallbackInfo& info) {
    while(_locked){}

    _locked = true;
    int currentPos = _enrolledSessions.size();
    _enrolledSessions.push_back(info);
    _stillEnrolled.push_back(true);
    _locked = false;

    while(_stillEnrolled.at(currentPos)){}

    return Napi::String::New(info.Env(), "OK");
}


    //------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
    //------------It doesnt currently work, but hopefully shows----------
    //------------        what I'm trying to accomplish        ----------
Napi::Value AudioDeviceEmitter::unenrollInNotifications(const Napi::CallbackInfo& info) {
    while(_locked){}

    for(int i = 0; i < _enrolledSessions.size(); i++) {
        if (info.This() == _enrolledSessions.at(i).This()) {
            _stillEnrolled.at(i) = false;
        }
    }

    return Napi::String::New(info.Env(), "OK");
}

绑定.cpp:

#include <napi.h>
#include "AudioDeviceEmitter.h"

Napi::Object InitNAPI(Napi::Env env, Napi::Object exports) {
    AudioDeviceEmitter::Init(env, exports);
    return exports;
}

NODE_API_MODULE(WinDefaultAudioDevice, InitNAPI)

此代码会引发错误并且无法编译,希望在这里更全面地解释我的问题。

标签: javascriptc++node.jswindowsnode.js-napi

解决方案


实现 Nan 的 AsyncWorker 类之一。有关如何执行此操作的一些示例代码,请查看此答案。实现worker后,给异步worker分配一个回调函数。


推荐阅读