Make Node.js exit regardless of a native module async call pending - node.js

I'm writing a Napi-based module for Node.js.
The module uses WINAPI WaitForSingleObject(pid). The blocking WINAPI call is wrapped in Napi::AsyncWorker.
The issue
The async call prevents Node.js from exiting. I want Node.js to exit when it has nothing else to do, like it does with child_process.unref(). So I want to unref the async call from the Node.js event loop.

I don't have time to make a NPM package of it, but here is my solution. The solution is valid for all blocking system calls (that put the calling thread to the waiting state, like Sleep() does).
The idea is to:
Use std::thread for the blocking WINAPI call.
Use napi_threadsafe_function to call back from the new thread to Javascript safely.
Use napi_unref_threadsafe_function to unref the async operation from Node.js event loop.
More details:
For safe simultaneous calls, create a new void* context with thread-specific data and pass it around (supported by N-API).
For the unreffed napi_threadsafe_function, finalizer is supported. Stop the waiting thread here to prevent crash messages upon Node.js exit.
I use the C N-API and C++ node-addon-api interchangeably. Unfortunately a pure C++ solution is impossible at the time of writing because napi_unref_threadsafe_function is only available from the C API.
addon.cc:
#include <napi.h>
#include <node_api.h>
#include <windows.h>
#include <tlhelp32.h>
#include <sstream>
#include <string>
#include <iostream>
#include <thread>
using namespace std;
using namespace Napi;
struct my_context {
std::thread nativeThread;
HANDLE hStopEvent;
napi_threadsafe_function tsfn;
};
std::string WaitForPid(int pid, my_context* context, bool* stopped) {
HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (process == NULL)
{
std::stringstream stream;
stream << "OpenProcess failed: " << (int)GetLastError();
return stream.str();
}
context->hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (process == NULL)
{
std::stringstream stream;
stream << "CreateEvent failed: " << (int)GetLastError();
return stream.str();
}
HANDLE hEvents[2] = {process, context->hStopEvent};
DWORD waitResult = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
if (waitResult == WAIT_FAILED)
{
std::stringstream stream;
stream << "WaitForSingleObject failed: " << (int)GetLastError();
return stream.str();
} else if (waitResult == WAIT_OBJECT_0 + 1) {
*stopped = true;
}
return std::string();
}
Value WaitForProcessToExit(CallbackInfo &info)
{
Env env = info.Env();
if (info.Length() < 3) {
TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!info[0].IsNumber() || !info[1].IsBoolean() || !info[2].IsFunction()) {
TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Null();
}
int pid = info[0].As<Number>().Int32Value();
bool unref = info[1].As<Boolean>();
Function func = info[2].As<Function>();
// Do async stuff
my_context* context = new my_context();
NAPI_THROW_IF_FAILED(env, napi_create_threadsafe_function(
(napi_env) env,
(napi_value) func,
NULL,
(napi_value) String::New(env, "WaitForProcessToExit"),
0,
1,
NULL,
[](napi_env env, void* finalize_data, void* finalize_hint /* Context attached to the TSFN */) {
SetEvent(((my_context*)finalize_hint)->hStopEvent);
((my_context*)finalize_hint)->nativeThread.join();
delete finalize_hint;
},
context, // Context attached to the TSFN
[](napi_env env, napi_value js_callback, void* context, void* data) {
std::string* error = (std::string*)data;
// Transform native data into JS data, passing it to the provided `js_callback` (the TSFN's JavaScript function)
napi_status status = napi_ok;
napi_value global;
status = napi_get_global(env, &global);
if (status != napi_ok)
{
std::cout << "napi_get_global failed" << std::endl;
}
napi_value result;
if (error->empty()) {
status = napi_call_function(env, global, js_callback, 0, NULL, &result);
} else {
napi_value values[] = { (napi_value)Error::New(env, *error).Value() };
status = napi_call_function(env, global, js_callback, 1, values, &result);
}
delete data;
if (status != napi_ok)
{
std::cout << "napi_call_function failed" << std::endl;
}
status = napi_release_threadsafe_function(((my_context*)context)->tsfn, napi_tsfn_release);
if (status != napi_ok)
{
std::cout << "napi_release_threadsafe_function failed" << std::endl;
}
},
&(context->tsfn)), env.Undefined());
context->nativeThread = std::thread([pid, context] {
bool stopped = false;
std::string error = WaitForPid(pid, context, &stopped);
if (stopped) {
return;
}
napi_status status = napi_call_threadsafe_function(context->tsfn, new std::string(error), napi_tsfn_blocking);
if (status != napi_ok)
{
std::cout << "napi_call_threadsafe_function failed" << std::endl;
}
});
if (unref) {
NAPI_THROW_IF_FAILED(env, napi_unref_threadsafe_function((napi_env) env, context->tsfn), env.Undefined());
}
return env.Undefined();
}
Object Init(Env env, Object exports)
{
exports.Set(String::New(env, "WaitForProcessToExit"), Function::New(env, Winapi::WaitForProcessToExit));
return exports;
}
NODE_API_MODULE(hello, Init)

Related

Regarding thread communication to post task back from called child thread to main thread

I have a requirement to post a task from child thread to main thread back. I am creating child thread from the main thread and posting tasks over there. But after receiving few callbacks from common API, I need to execute a few particular tasks on main thread only like proxy creation, etc. so in such a scenario, I have to communicate with the main thread and need to post that particular task on the main thread. I have designed LoopingThread.cpp as mentioned below and communicating with the main for posting tasks on that:
LoopingThread.cpp:
#include <iostream>
#include "loopingThread.hpp"
using namespace std;
LoopingThread::LoopingThread() : thread(nullptr), scheduledCallbacks() {
}
LoopingThread::~LoopingThread() {
if (this->thread) {
delete this->thread;
}
}
void LoopingThread::runCallbacks() {
this->callbacksMutex.lock();
if (this->scheduledCallbacks.size() > 0) {
std::thread::id threadID = std::this_thread::get_id();
std::cout<<"inside runCallback()threadId:"<<threadID<<std::endl;
// This is to allow for new callbacks to be scheduled from within a callback
std::vector<std::function<void()>> currentCallbacks = std::move(this->scheduledCallbacks);
this->scheduledCallbacks.clear();
this->callbacksMutex.unlock();
for (auto callback : currentCallbacks) {
//callback();
//this->callback();
int id = 1;
this->shared_func(id);
}
} else {
this->callbacksMutex.unlock();
}
}
void LoopingThread::shared_func(int id)
{
std::thread::id run_threadID = std::this_thread::get_id();
std::cout<<"shared_func: "<<run_threadID<<std::endl;
this->callbacksMutex.lock();
if (id == 0)
std::cout<<"calling from main,id: "<<id<<std::endl;
else if (id == 1)
std::cout<<"calling from child,id: "<<id<<std::endl;
this->callbacksMutex.unlock();
}
void LoopingThread::run() {
std::thread::id run_threadID = std::this_thread::get_id();
std::cout<<"Child_run_threadID: "<<run_threadID<<std::endl;
for (;;) {
this->runCallbacks();
// Run the tick
if (!this->tick()) {
std::cout<<"Run the tick"<<std::endl;
break;
}
}
// Run pending callbacks, this might result in an infinite loop if there are more
// callbacks scheduled from within scheduled callbacks
this->callbacksMutex.lock();
while (this->scheduledCallbacks.size() > 0) {
std::cout<<"inside scheduledCallbacks.size() > 0"<<std::endl;
this->callbacksMutex.unlock();
this->runCallbacks();
this->callbacksMutex.lock();
}
this->callbacksMutex.unlock();
}
void LoopingThread::scheduleCallback(std::function<void()> callback) {
std::cout<<"inside schedulecallback"<<std::endl;
this->callbacksMutex.lock();
this->scheduledCallbacks.push_back(callback);
this->callbacksMutex.unlock();
}
void LoopingThread::start() {
if (!this->thread) {
this->thread = new std::thread(&LoopingThread::run, this);
//std::thread::id threadID = std::this_thread::get_id();
//std::cout<<"creating thread: "<<threadID<<std::endl;
}
}
void LoopingThread::join() {
if (this->thread && this->thread->joinable()) {
this->thread->join();
std::cout<<"joining thread"<<std::endl;
}
}
**main.cpp:**
#include <thread>
#include <chrono>
#include <iostream>
#include <mutex>
#include <string>
#include "loopingThread.hpp"
using namespace std;
std::mutex stdoutMutex;
// Example usage of LoopingThread with a classic MainThread:
class MainThread : public LoopingThread {
private:
MainThread();
public:
virtual ~MainThread();
static MainThread& getInstance();
virtual bool tick();
};
MainThread::MainThread() {}
MainThread::~MainThread() {}
MainThread& MainThread::getInstance() {
// Provide a global instance
static MainThread instance;
return instance;
}
bool MainThread::tick() {
// std::cout<<"main thread:"<<threadID<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
stdoutMutex.lock();
std::cout << "tick" << std::endl;
stdoutMutex.unlock();
// Return false to stop this thread
return true;
}
void doLongAsyncTask() {
std::thread longTask([] () {
stdoutMutex.lock();
std::cout << "Beginning long task..." <<std::endl;
stdoutMutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(2));
stdoutMutex.lock();
std::cout << "Long task finished!" << std::endl;
stdoutMutex.unlock();
MainThread::getInstance().scheduleCallback([] () {
stdoutMutex.lock();
std::cout << "This is called within the main thread!" << std::endl <<
"No need to worry about thread safety or " <<
"race conditions here" << std::endl;
stdoutMutex.unlock();
});
});
longTask.detach();
}
int main() {
doLongAsyncTask();
MainThread::getInstance().start();
MainThread::getInstance().join();
MainThread::getInstance().run();
}
Now suppose child thread receives any task of creating proxy then It needs to post that task back to the main thread. How to achieve this scenario?

How to determine if condition_variable::wait_for timed out

I have a class which allows to wait on a condition_variable taking care of the spurious wake ups. Following is the code:
Code:
// CondVarWrapper.hpp
#pragma once
#include <mutex>
#include <chrono>
#include <condition_variable>
class CondVarWrapper {
public:
void Signal() {
std::unique_lock<std::mutex> unique_lock(mutex);
cond_var_signalled = true;
unique_lock.unlock();
cond_var.notify_one();
}
// TODO: WaitFor needs to return false if timed out waiting
bool WaitFor(const std::chrono::seconds timeout) {
std::unique_lock<std::mutex> unique_lock(mutex);
bool timed_out = false;
// How to determine if wait_for timed out ?
cond_var.wait_for(unique_lock, timeout, [this] {
return cond_var_signalled;
});
cond_var_signalled = false;
return timed_out;
}
void Wait() {
std::unique_lock<std::mutex> unique_lock(mutex);
cond_var.wait(unique_lock, [this] {
return cond_var_signalled;
});
cond_var_signalled = false;
}
private:
bool cond_var_signalled = false;
std::mutex mutex;
std::condition_variable cond_var;
};
// main.cpp
#include "CondVarWrapper.hpp"
#include <iostream>
#include <string>
#include <thread>
int main() {
CondVarWrapper cond_var_wrapper;
std::thread my_thread = std::thread([&cond_var_wrapper]{
std::cout << "Thread started" << std::endl;
if (cond_var_wrapper.WaitFor(std::chrono::seconds(1))) {
std::cout << "Wait ended before timeout" << std::endl;
} else {
std::cout << "Timed out waiting" << std::endl;
}
});
std::this_thread::sleep_for(std::chrono::seconds(6));
// Uncomment following line to see the timeout working
cond_var_wrapper.Signal();
my_thread.join();
}
Question:
In the method WaitFor, I need to determine if cond_var timed out waiting? How do I do that? WaitFor should return false when it timed out waiting else it should return true. Is that possible?
I see cv_status explained on cppreference but struggling to find a good expample of how to use it.

How to prematurely kill std::async threads before they are finished *without* using a std::atomic_bool?

I have a function that takes a callback, and used it to do work on 10 separate threads. However, it is often the case that not all of the work is needed. For example, if the desired result is obtained on the third thread, it should stop all work being done on of the remaining alive threads.
This answer here suggests that it is not possible unless you have the callback functions take an additional std::atomic_bool argument, that signals whether the function should terminate prematurely.
This solution does not work for me. The workers are spun up inside a base class, and the whole point of this base class is to abstract away details of multithreading. How can I do this? I am anticipating that I will have to ditch std::async for something more involved.
#include <iostream>
#include <future>
#include <vector>
class ABC{
public:
std::vector<std::future<int> > m_results;
ABC() {};
~ABC(){};
virtual int callback(int a) = 0;
void doStuffWithCallBack();
};
void ABC::doStuffWithCallBack(){
// start working
for(int i = 0; i < 10; ++i)
m_results.push_back(std::async(&ABC::callback, this, i));
// analyze results and cancel all threads when you get the 1
for(int j = 0; j < 10; ++j){
double foo = m_results[j].get();
if ( foo == 1){
break; // but threads continue running
}
}
std::cout << m_results[9].get() << " <- this shouldn't have ever been computed\n";
}
class Derived : public ABC {
public:
Derived() : ABC() {};
~Derived() {};
int callback(int a){
std::cout << a << "!\n";
if (a == 3)
return 1;
else
return 0;
};
};
int main(int argc, char **argv)
{
Derived myObj;
myObj.doStuffWithCallBack();
return 0;
}
I'll just say that this should probably not be a part of a 'normal' program, since it could leak resources and/or leave your program in an unstable state, but in the interest of science...
If you have control of the thread loop, and you don't mind using platform features, you could inject an exception into the thread. With posix you can use signals for this, on Windows you would have to use SetThreadContext(). Though the exception will generally unwind the stack and call destructors, your thread may be in a system call or other 'non-exception safe place' when the exception occurs.
Disclaimer: I only have Linux at the moment, so I did not test the Windows code.
#if defined(_WIN32)
# define ITS_WINDOWS
#else
# define ITS_POSIX
#endif
#if defined(ITS_POSIX)
#include <signal.h>
#endif
void throw_exception() throw(std::string())
{
throw std::string();
}
void init_exceptions()
{
volatile int i = 0;
if (i)
throw_exception();
}
bool abort_thread(std::thread &t)
{
#if defined(ITS_WINDOWS)
bool bSuccess = false;
HANDLE h = t.native_handle();
if (INVALID_HANDLE_VALUE == h)
return false;
if (INFINITE == SuspendThread(h))
return false;
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_CONTROL;
if (GetThreadContext(h, &ctx))
{
#if defined( _WIN64 )
ctx.Rip = (DWORD)(DWORD_PTR)throw_exception;
#else
ctx.Eip = (DWORD)(DWORD_PTR)throw_exception;
#endif
bSuccess = SetThreadContext(h, &ctx) ? true : false;
}
ResumeThread(h);
return bSuccess;
#elif defined(ITS_POSIX)
pthread_kill(t.native_handle(), SIGUSR2);
#endif
return false;
}
#if defined(ITS_POSIX)
void worker_thread_sig(int sig)
{
if(SIGUSR2 == sig)
throw std::string();
}
#endif
void init_threads()
{
#if defined(ITS_POSIX)
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = worker_thread_sig;
sigaction(SIGUSR2, &sa, 0);
#endif
}
class tracker
{
public:
tracker() { printf("tracker()\n"); }
~tracker() { printf("~tracker()\n"); }
};
int main(int argc, char *argv[])
{
init_threads();
printf("main: starting thread...\n");
std::thread t([]()
{
try
{
tracker a;
init_exceptions();
printf("thread: started...\n");
std::this_thread::sleep_for(std::chrono::minutes(1000));
printf("thread: stopping...\n");
}
catch(std::string s)
{
printf("thread: exception caught...\n");
}
});
printf("main: sleeping...\n");
std::this_thread::sleep_for(std::chrono::seconds(2));
printf("main: aborting...\n");
abort_thread(t);
printf("main: joining...\n");
t.join();
printf("main: exiting...\n");
return 0;
}
Output:
main: starting thread...
main: sleeping...
tracker()
thread: started...
main: aborting...
main: joining...
~tracker()
thread: exception caught...
main: exiting...

C++11 non-blocking, long-running, producer consumer threads

I am trying to teach myself C++11 threading, and I would like to start a background producer thread at the beginning of the application, and have it run until application exit. I would also like to have consumer thread (which also runs for the life of the application).
A real-world example would be a producer thread listening on a Com port for incoming GPS data. Once a full message had been accumulated, it could be parsed to see if it was a message of interest, then converted into a string (say), and 'delivered back' to be consumed (update current location, for example).
My issue is I haven't been able to figure out how to do this without blocking the rest of the application when I 'join()' on the consumer thread.
Here is my very simplified example that hopefully shows my issues:
#include <QCoreApplication>
#include <QDebug>
#include <thread>
#include <atomic>
#include <iostream>
#include <queue>
#include <mutex>
#include <chrono>
#include "threadsafequeuetwo.h"
ThreadSafeQueueTwo<int> goods;
std::mutex mainMutex;
std::atomic<bool> isApplicationRunning = false;
void theProducer ()
{
std::atomic<int> itr = 0;
while(isApplicationRunning)
{
// Simulate this taking some time...
std::this_thread::sleep_for(std::chrono::milliseconds(60));
// Push the "produced" value onto the queue...
goods.push(++itr);
// Diagnostic printout only...
if ((itr % 10) == 0)
{
std::unique_lock<std::mutex> lock(mainMutex);
std::cout << "PUSH " << itr << " on thread ID: "
<< std::this_thread::get_id() << std::endl;
}
// Thread ending logic.
if (itr > 100) isApplicationRunning = false;
}
}
void theConsumer ()
{
while(isApplicationRunning || !goods.empty())
{
int val;
// Wait on new values, and 'pop' when available...
goods.waitAndPop(val);
// Here, we would 'do something' with the new values...
// Simulate this taking some time...
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Diagnostic printout only...
if ((val % 10) == 0)
{
std::unique_lock<std::mutex> lock(mainMutex);
std::cout << "POP " << val << " on thread ID: "
<< std::this_thread::get_id() << std::endl;
}
}
}
int main(int argc, char *argv[])
{
std::cout << "MAIN running on thread ID: "
<< std::this_thread::get_id() << std::endl;
// This varaiable gets set to true at startup, and,
// would only get set to false when the application
// wants to exit.
isApplicationRunning = true;
std::thread producerThread (theProducer);
std::thread consumerThread (theConsumer);
producerThread.detach();
consumerThread.join(); // BLOCKS!!! - how to get around this???
std::cout << "MAIN ending on thread ID: "
<< std::this_thread::get_id() << std::endl;
}
The ThreadSafeQueueTwo class is the thread safe queue implementation taken almost exactly as is from the "C++ Concurrency In Action" book. This seems to work just fine. Here it is if anybody is interested:
#ifndef THREADSAFEQUEUETWO_H
#define THREADSAFEQUEUETWO_H
#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
template<typename T>
class ThreadSafeQueueTwo
{
public:
ThreadSafeQueueTwo()
{}
ThreadSafeQueueTwo(ThreadSafeQueueTwo const& rhs)
{
std::lock_guard<std::mutex> lock(myMutex);
myQueue = rhs.myQueue;
}
void push(T newValue)
{
std::lock_guard<std::mutex> lock(myMutex);
myQueue.push(newValue);
myCondVar.notify_one();
}
void waitAndPop(T& value)
{
std::unique_lock<std::mutex> lock(myMutex);
myCondVar.wait(lock, [this]{return !myQueue.empty(); });
value = myQueue.front();
myQueue.pop();
}
std::shared_ptr<T> waitAndPop()
{
std::unique_lock<std::mutex> lock(myMutex);
myCondVar.wait(lock, [this]{return !myQueue.empty(); });
std::shared_ptr<T> sharedPtrToT (std::make_shared<T>(myQueue.front()));
myQueue.pop();
return sharedPtrToT;
}
bool tryPop(T& value)
{
std::lock_guard<std::mutex> lock(myMutex);
if (myQueue.empty())
return false;
value = myQueue.front();
myQueue.pop();
return true;
}
std::shared_ptr<T> tryPop()
{
std::lock_guard<std::mutex> lock(myMutex);
if (myQueue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> sharedPtrToT (std::make_shared<T>(myQueue.front()));
myQueue.pop();
return sharedPtrToT;
}
bool empty()
{
std::lock_guard<std::mutex> lock(myMutex);
return myQueue.empty();
}
private:
mutable std::mutex myMutex;
std::queue<T> myQueue;
std::condition_variable myCondVar;
};
#endif // THREADSAFEQUEUETWO_H
Here's the output:
I know there are obvious issues with my example, but my main question is how would I run something like this in the background, without blocking the main thread?
Perhaps an even better way of trying to solve this is, is there a way that every time the producer has 'produced' some new data, could I simply call a method in the main thread, passing in the new data? This would be similar to queued signal/slots it Qt, which I am big fan of.

wasapi: detect that nothing are played (device is not in use)

I use wasapi for loopback capturing. All data after that are saved to wav file.
When it starts capturing it delivers data all the time until I stop capturing process, even if no application use audio device and thus no one produces music. As a result I write to file not valuable data - just silence.
So is there any way to distinct silence in currently played music and situation when device is not in use at all. In later situation I want to interrupt process of recording data into file and create new one when smth are played through audio device again.
PS AudioCaptureClinet method GetBuffer is featured with output parameter flag which seemingly can have value AUDCLNT_BUFFERFLAGS_SILENT == 0x1 in some conditions but in my case it returns flag==0 everytime
Ok, it turns out there's no reliable way to infer from a loopback stream itself that nothing's played to the corresponding output device. However, it is possible to query audio sessions on the output device itself and for each session, check that it's state is active. It is also possible to get notified when sessions are added to/removed from a device, and to get notified when a session (de)activates. This way, you can start the loopback stream when the first session activates on a device and stop it when the last one deactivates.
Here's how to do it:
Implement IAudioSessionNotification and IAudioSessionEvents
Set up an MTA thread, STA won't work
Retrieve IMMDevice interface of the output you want to monitor
From IMMDevice, obtain IAudioSessionManager2
From IAudioSessionManager2, obtain IAudioSessionEnumerator
From IAudioSessionEnumerator, enumerate IAudioSessionControls
This is the list of initial sessions. It will stay constant for the lifetime of the enumerator.
From IAudioSessionControl, use GetState to see what's initially active
Use IAudioSessionControl::RegisterAudioSessionNotification to get notified of (de)activation of initial sessions
Use IAudioSessionManager2::RegisterSessionNotification to register your custom session notification
This will get called for any additional sessions created after the initial enumeration
From IAudioSessionNotification::OnSessionCreated, use IAudioSessionControl::RegisterAudioSessionNotification to get notified of (de)activation of additional sessions
If any session is initially active, start the loopback stream
When the last (initial or additional) session gets deactivaed or disconnected, stop the loopback stream
And of course restart when any session activates again
Below is a working example. Note that i DID NOT:
Do any thorough error handling besides printing what's wrong
Actually set up the loopback stream. Example only prints when anything (de)activates.
Do any bookkeeping of COM interfaces! Sample is leaking interface handles all over. To get this stuff right you probably want to store your SessionNotifications/SessionEvents objects in some thread-safe collection and unregister/release them as sessions get destroyed.
#include <windows.h>
#include <atlbase.h>
#include <atlcomcli.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <audiopolicy.h>
#include <iostream>
#include <vector>
#include <string>
#define VERIFY(expr) do { \
if(FAILED(hr = (expr))) { \
std::cout << "Error: " << #expr << ": " << hr << "\n"; \
goto error; \
} \
} while(0)
static volatile ULONG sessionId = 0;
class SessionEvents : public IAudioSessionEvents {
private:
LONG rc;
ULONG id;
~SessionEvents() {}
public:
SessionEvents(ULONG id) :
id(id), rc(1) {}
ULONG STDMETHODCALLTYPE AddRef() {
return InterlockedIncrement(&rc);
}
ULONG STDMETHODCALLTYPE Release() {
ULONG rc = InterlockedDecrement(&this->rc);
if(rc == 0) {
delete this;
}
return rc;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) {
if(IID_IUnknown == riid) {
AddRef();
*ppv = static_cast<IUnknown*>(this);
return S_OK;
}
else if(__uuidof(IAudioSessionEvents) == riid) {
AddRef();
*ppv = static_cast<IAudioSessionEvents*>(this);
return S_OK;
}
else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState NewState) {
switch(NewState) {
case AudioSessionStateInactive: std::wcout << L"AudioSessionStateInactive session # " << id << L"\n"; break;
case AudioSessionStateActive: std::wcout << L"AudioSessionStateActive session # " << id << L"\n"; break;
case AudioSessionStateExpired: std::wcout << L"AudioSessionStateExpired session # " << id << L"\n"; break;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) {
std::wcout << L"OnSessionDisconnected session # " << id << L"\n";
return S_OK;
}
};
class SessionNotification : public IAudioSessionNotification {
private:
LONG rc;
~SessionNotification() {}
public:
SessionNotification() : rc(1) {}
ULONG STDMETHODCALLTYPE AddRef() {
return InterlockedIncrement(&rc);
}
ULONG STDMETHODCALLTYPE Release() {
ULONG rc = InterlockedDecrement(&this->rc);
if(rc == 0)
delete this;
return rc;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) {
if(IID_IUnknown == riid) {
AddRef();
*ppv = static_cast<IUnknown*>(this);
return S_OK;
}
else if(__uuidof(IAudioSessionNotification) == riid) {
AddRef();
*ppv = static_cast<IAudioSessionNotification*>(this);
return S_OK;
}
else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
HRESULT OnSessionCreated(IAudioSessionControl *newSession) {
HRESULT hr = S_OK;
if(newSession) {
newSession->AddRef();
CComHeapPtr<WCHAR> name;
ULONG id = InterlockedIncrement(&sessionId);
VERIFY(newSession->GetDisplayName(&name));
std::wcout << L"created session # " << id << L": " << name.m_pData << L"\n";
VERIFY(newSession->RegisterAudioSessionNotification(new SessionEvents(id)));
}
error:
return hr;
}
};
int main(int argc, char** argv) {
HRESULT hr = S_OK;
VERIFY(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
{
int count;
std::wstring line;
CComPtr<IMMDevice> defaultOutput;
CComPtr<IMMDeviceEnumerator> devices;
CComPtr<IAudioSessionManager2> manager;
CComPtr<IAudioSessionEnumerator> sessions;
SessionNotification* notification = new SessionNotification;
VERIFY(devices.CoCreateInstance(__uuidof(MMDeviceEnumerator)));
VERIFY(devices->GetDefaultAudioEndpoint(eRender, eMultimedia, &defaultOutput));
VERIFY(defaultOutput->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&manager)));
VERIFY(manager->RegisterSessionNotification(notification));
VERIFY(manager->GetSessionEnumerator(&sessions));
VERIFY(sessions->GetCount(&count));
std::wcout << L"Initial sessions: " << count << L"\n";
for(int s = 0; s < count; s++) {
AudioSessionState state;
CComHeapPtr<WCHAR> name;
CComPtr<IAudioSessionControl> control;
VERIFY(sessions->GetSession(s, &control));
VERIFY(control->GetDisplayName(&name));
VERIFY(control->GetState(&state));
std::wcout << L"Initial session name: " << name.m_pData << L", active = " << (state == AudioSessionStateActive) << L"\n";
VERIFY(control->RegisterAudioSessionNotification(new SessionEvents(InterlockedIncrement(&sessionId))));
}
std::wcout << L"Press return to exit...\n";
std::getline(std::wcin, line);
VERIFY(manager->UnregisterSessionNotification(notification));
}
error:
CoUninitialize();
return 0;
}

Resources