title: Emitting a Node.js Event from a C++ callback function
body: <p>In a Node.js application, I need to be notified when the default audio device has been changed. This program will be used on Windows 7.</p>
  
  <p>Currently I am trying to do this by making a C++ add-on for node that, through the IMMNotificationClient::OnDefaultDeviceChanged method from the Windows Core Audio API, emits an event that can be received by a Node event emitter.</p>
  
  <p>Here's an example of how the Windows Core Audio API callback method would look:</p>
  
  <pre><code>HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceID)
  {
     //User written code that emits a node Event.
  }
  </code></pre>
  
  <p>Here is some ideal c++ style pseudo code of what I would love to have in the above callback function:</p>
  
  <pre><code>EventEmitter.emit(v8::String::NewFromUtf8("defaultDeviceChanged"), deviceName);
  </code></pre>
  
  <p>I'm not sure how to make this happen though, so here is my question: How do I emit an event to a Node.js application through a C++ callback function?</p>
  
  <p>I'm open to other solutions as long as they can be run natively on Node (IE through an add-on), and will work on Windows 7, this means external calls to applications like nircmd are off the table.</p>
  
  <p><em>EDIT</em></p>
  
  <p>This is some experimental code to try and help describe what I am doing:</p>
  
  <p>AudioDeviceEmitter.h:</p>
  
  <pre><code>#pragma once
  
  #include &lt;stdio.h&gt;
  #include &lt;wchar.h&gt;
  #include &lt;tchar.h&gt;
  #include &lt;time.h&gt;
  #include "windows.h"
  #include "Mmdeviceapi.h"
  #include "Propidl.h"
  #include "Functiondiscoverykeys_devpkey.h"
  #include &lt;vector&gt;
  #include &lt;string&gt;
  #include &lt;comdef.h&gt;
  #define NAPI_DISABLE_CPP_EXCEPTIONS
  #include &lt;napi.h&gt;
  #include &lt;vector&gt;
  
  class AudioDeviceEmitter : public Napi::ObjectWrap&lt;AudioDeviceEmitter&gt;, public IMMNotificationClient {
  public:
      static Napi::Object Init(Napi::Env env, Napi::Object exports);
      AudioDeviceEmitter(const Napi::CallbackInfo&amp; info);
  
  
      Napi::Value AudioDeviceEmitter::enrollInNotifications(const Napi::CallbackInfo&amp; info);
      Napi::Value AudioDeviceEmitter::unenrollInNotifications(const Napi::CallbackInfo&amp; info);
      //WIN API 
      ~AudioDeviceEmitter()
      {
          if (_pEnumerator != NULL) {
              _pEnumerator-&gt;UnregisterEndpointNotificationCallback(this);
              _pEnumerator-&gt;Release();
          }
      }
  
  
      // IUnknown methods -- AddRef, Release, and QueryInterface
  
      ULONG STDMETHODCALLTYPE AddRef()
      {
          return InterlockedIncrement(&amp;_cRef);
      }
  
  ULONG STDMETHODCALLTYPE Release()
  {
      ULONG ulRef = InterlockedDecrement(&amp;_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-&gt;GetDevice(pwstrDeviceId, &amp;pDevice);
              if(SUCCEEDED(hr)){
  
              name = getFriendlyNameString(pDevice);
              pDevice-&gt;Release();
          }
  
          for(int i = 0; i &lt; _stillEnrolled.size(); i++) {
              if(_stillEnrolled.at(i)) {
                  Napi::CallbackInfo &amp; info = _enrolledSessions.at(i);
                  Napi::Env env = info.Env();
                  Napi::Function emit = info.This().As&lt;Napi::Object&gt;()
                      .Get("emit").As&lt;Napi::Function&gt;();
  
                  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-&gt;OpenPropertyStore(STGM_READ, &amp;pStore);
  
          PROPVARIANT variant;
          PropVariantInit(&amp;variant);
          hr = pStore-&gt;GetValue(PKEY_Device_FriendlyName, &amp;variant);
          size_t strlen = wcslen((wchar_t *)variant.pwszVal);
          int throwAwaylen;
          char *pOutBuffer = (char *)malloc(strlen);
          wcstombs_s((size_t *)&amp;throwAwaylen, pOutBuffer, size, wideCharArray, size);
  
          std::string toReturn = pOutBuffer;
  
          free(pOutBuffer);
  
          PropVariantClear(&amp;variant);
          pStore-&gt;Release();
  
          return toReturn;
  
      }
  
      LONG _cRef;
      IMMDeviceEnumerator *_pEnumerator;
      static Napi::FunctionReference constructor;
  
      std::vector&lt;Napi::CallbackInfo&gt; _enrolledSessions;
      std::vector&lt;bool&gt; _stillEnrolled;
      bool _locked;
  
  };
  </code></pre>
  
  <p>AudioDeviceEmitter.cpp</p>
  
  <pre><code>#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", &amp;AudioDeviceEmitter::enrollInNotifications),
      InstanceMethod("unenrollInNotifications", &amp;AudioDeviceEmitter::unenrollInNotifications)
    });
  
    constructor = Napi::Persistent(func);
    constructor.SuppressDestruct();
  
    exports.Set("AudioDeviceEmitter", func);
    return exports;
  }
  
  AudioDeviceEmitter::AudioDeviceEmitter(const Napi::CallbackInfo&amp; info)
  : Napi::ObjectWrap&lt;AudioDeviceEmitter&gt;(info), _locked(false), _cRef(1),
          _pEnumerator(NULL)
      {
          HRESULT hr = CoInitialize(NULL);
  
          hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
              CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&amp;_pEnumerator);
  
          _pEnumerator-&gt;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&amp; 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&amp; info) {
      while(_locked){}
  
      for(int i = 0; i &lt; _enrolledSessions.size(); i++) {
          if (info.This() == _enrolledSessions.at(i).This()) {
              _stillEnrolled.at(i) = false;
          }
      }
  
      return Napi::String::New(info.Env(), "OK");
  }
  </code></pre>
  
  <p>binding.cpp:</p>
  
  <pre><code>#include &lt;napi.h&gt;
  #include "AudioDeviceEmitter.h"
  
  Napi::Object InitNAPI(Napi::Env env, Napi::Object exports) {
      AudioDeviceEmitter::Init(env, exports);
      return exports;
  }
  
  NODE_API_MODULE(WinDefaultAudioDevice, InitNAPI)
  </code></pre>
  
  <p>This code throws errors and doesn't compile, it is here to hopefully explain my problem more fully.</p>