Skip to content

Instantly share code, notes, and snippets.

@chowey
Created November 27, 2014 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chowey/6705c5c6b07399e6a73a to your computer and use it in GitHub Desktop.
Save chowey/6705c5c6b07399e6a73a to your computer and use it in GitHub Desktop.
Volume monitoring and control DLL
// dllmain.cpp : Defines the entry point for the DLL application.
#include "volumemonitor.h"
// We use a worker thread for all COM calls, since CoInitialize is not
// otherwise safe to execute in a DLL.
HANDLE worker_thread_;
// There is one global VolumeMonitor that is lazy-loaded. This helps us
// report errors on initialization, and will also re-attempt initialization
// at the next call.
VolumeMonitor *monitor = NULL;
HRESULT LoadMonitor() {
if (monitor != NULL) {
return S_OK;
}
monitor = new VolumeMonitor(worker_thread_);
HRESULT hr = monitor->Init();
if (hr != S_OK) {
delete monitor;
monitor = NULL;
}
return hr;
}
void UnloadMonitor() {
if (monitor == NULL) {
return;
}
monitor->Teardown();
delete monitor;
monitor = NULL;
}
// EventResult is a data structure used to pass work to the worker thread.
// It contains the input and output as an error code. It also contains
// a synchronization object. It is created automatically by DispatchWork.
// It is the lParam received by an APC callback function. The APC callback
// function must call SetEvent() on hEvent to signal it is done.
struct EventResult {
HANDLE hEvent;
void* lParam;
HRESULT hError;
};
// WorkerLoop is the main function for the worker thread. We schedule all work
// using APC.
DWORD WINAPI WorkerLoop(void* lpParam) {
while (WAIT_IO_COMPLETION == SleepEx(INFINITE, TRUE)) {
if (worker_thread_ == NULL) break;
}
return 0;
}
// DispatchWork is a helper function to schedule work on the worker thread.
// This function also does syncronization, and will block until the worker
// thread has completed this work.
BOOL DispatchWork(PAPCFUNC pfnAPC, void* lParam) {
EventResult r = {0};
r.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
r.lParam = lParam;
DWORD ret = QueueUserAPC(pfnAPC, worker_thread_, (ULONG_PTR)&r);
if (ret == FALSE) {
CloseHandle(r.hEvent);
return FALSE;
}
ret = WaitForSingleObject(r.hEvent, INFINITE);
CloseHandle(r.hEvent);
if (ret != WAIT_OBJECT_0) {
SetLastError(ret);
return FALSE;
}
if (r.hError != S_OK) {
SetLastError(r.hError);
return FALSE;
}
return TRUE;
}
void CALLBACK WorkerInit(ULONG_PTR lParam) {
CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
}
void CALLBACK WorkerTeardown(ULONG_PTR lParam) {
CloseHandle(worker_thread_);
worker_thread_ = NULL;
UnloadMonitor();
CoUninitialize();
}
void CALLBACK WorkerGetVolume(ULONG_PTR lParam) {
EventResult *r = (EventResult *)lParam;
VolumeState *state = (VolumeState *)r->lParam;
r->hError = LoadMonitor();
if (r->hError == S_OK) {
r->hError = monitor->GetVolume(state);
}
SetEvent(r->hEvent);
}
BOOL GetVolume(VolumeState *state) {
return DispatchWork(WorkerGetVolume, state);
}
void CALLBACK WorkerSetVolume(ULONG_PTR lParam) {
EventResult *r = (EventResult *)lParam;
float vol = *(float *)r->lParam;
r->hError = LoadMonitor();
if (r->hError == S_OK) {
r->hError = monitor->SetVolume(vol);
}
SetEvent(r->hEvent);
}
BOOL SetVolume(float *level) {
return DispatchWork(WorkerSetVolume, level);
}
void CALLBACK WorkerNotifyVolume(ULONG_PTR lParam) {
EventResult *r = (EventResult *)lParam;
VolumeNotifyCallback callback = (VolumeNotifyCallback)r->lParam;
r->hError = LoadMonitor();
if (r->hError == S_OK) {
monitor->RegisterCallback(callback);
}
SetEvent(r->hEvent);
}
BOOL NotifyVolume(VolumeNotifyCallback callback) {
return DispatchWork(WorkerNotifyVolume, callback);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
worker_thread_ = CreateThread(NULL, 0, WorkerLoop, NULL, 0, NULL);
QueueUserAPC(WorkerInit, worker_thread_, NULL);
break;
case DLL_PROCESS_DETACH:
if (NULL != worker_thread_) {
QueueUserAPC(WorkerTeardown, worker_thread_, NULL);
}
break;
}
return TRUE;
}
#ifndef VOLUMEDLL_H
#define VOLUMEDLL_H
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <objbase.h>
#ifdef VOLUMEDLL_EXPORTS
#define VOLUMEDLL_API __declspec(dllexport)
#else
#define VOLUMEDLL_API __declspec(dllimport)
#endif
struct VolumeState {
float level;
BOOL muted;
};
typedef BOOL (__stdcall *VolumeNotifyCallback)( _In_ VolumeState *state );
// DLL exports
extern "C" {
// GetVolume uses GetMasterVolumeLevelScalar to get the system volume.
// It will be a normalized value (between 0.0 and 1.0).
VOLUMEDLL_API BOOL GetVolume(VolumeState *state);
// SetVolume uses SetMasterVolumeLevelScalar to set the system volume.
// It should be passed a floating point number between 0.0 and 1.0.
VOLUMEDLL_API BOOL SetVolume(float *level);
// NotifyVolume requires a VolumeNotifyCallback using stdcall notation.
VOLUMEDLL_API BOOL NotifyVolume(VolumeNotifyCallback callback);
}
#endif
#include "volumemonitor.h"
VolumeMonitor::VolumeMonitor(HANDLE worker_thread) {
deviceEnumerator = NULL;
endpointVolume = NULL;
registeredCallback = NULL;
worker_thread_ = worker_thread;
}
VolumeMonitor::~VolumeMonitor() {
}
HRESULT VolumeMonitor::Init() {
// Enumerate devices.
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
if (hr == S_OK) {
// Get the default device.
IMMDevice *defaultDevice = NULL;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
if (hr == S_OK) {
// Get the endpoint volume interface.
hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
if (hr == S_OK) {
hr = endpointVolume->RegisterControlChangeNotify(this);
}
defaultDevice->Release();
}
// Register for endpoint notifications.
if (hr == S_OK) {
hr = deviceEnumerator->RegisterEndpointNotificationCallback(this);
if (hr != S_OK) {
endpointVolume->UnregisterControlChangeNotify(this);
}
}
}
if (hr != S_OK) {
if (endpointVolume != NULL) {
endpointVolume->Release();
endpointVolume = NULL;
}
if (deviceEnumerator != NULL) {
deviceEnumerator->Release();
deviceEnumerator = NULL;
}
}
return hr;
}
void VolumeMonitor::Teardown() {
endpointVolume->UnregisterControlChangeNotify(this);
endpointVolume->Release();
endpointVolume = NULL;
deviceEnumerator->UnregisterEndpointNotificationCallback(this);
deviceEnumerator->Release();
deviceEnumerator = NULL;
}
HRESULT VolumeMonitor::GetVolume(VolumeState *state) {
// Get the endpoint volume.
HRESULT hr = endpointVolume->GetMute(&state->muted);
if (hr == S_OK) {
hr = endpointVolume->GetMasterVolumeLevelScalar(&state->level);
}
return hr;
}
HRESULT VolumeMonitor::SetVolume(float level) {
// Set the endpoint volume; we don't care to identify ourselves.
endpointVolume->SetMute(FALSE, NULL);
return endpointVolume->SetMasterVolumeLevelScalar(level, NULL);
}
void VolumeMonitor::RegisterCallback(VolumeNotifyCallback callback) {
registeredCallback = callback;
}
HRESULT VolumeMonitor::OnDefaultDeviceChanged(EDataFlow flow, ERole, LPCWSTR) {
if (flow == eRender) {
return QueueUserAPC(ChangeEndpoint, worker_thread_, (ULONG_PTR)this);
}
// return value of this callback is ignored
return S_OK;
}
HRESULT VolumeMonitor::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA) {
return QueueUserAPC(ChangeVolume, worker_thread_, (ULONG_PTR)this);
}
void CALLBACK VolumeMonitor::ChangeEndpoint(ULONG_PTR lParam) {
VolumeMonitor *self = (VolumeMonitor *)lParam;
self->Teardown();
self->Init();
}
void CALLBACK VolumeMonitor::ChangeVolume(ULONG_PTR lParam) {
VolumeMonitor *self = (VolumeMonitor *)lParam;
VolumeState state = {0};
HRESULT hr = self->GetVolume(&state);
if (hr == S_OK && self->registeredCallback != NULL) {
self->registeredCallback(&state);
}
}
// IUnknown methods
HRESULT VolumeMonitor::QueryInterface(REFIID iid, void** ppUnk)
{
if ((iid == __uuidof(IUnknown)) ||
(iid == __uuidof(IMMNotificationClient)))
{
*ppUnk = static_cast<IMMNotificationClient*>(this);
}
else if (iid == __uuidof(IAudioEndpointVolumeCallback))
{
*ppUnk = static_cast<IAudioEndpointVolumeCallback*>(this);
}
else
{
*ppUnk = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG VolumeMonitor::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG VolumeMonitor::Release()
{
long lRef = InterlockedDecrement(&m_cRef);
if (lRef == 0)
{
delete this;
}
return lRef;
}
#ifndef VOLUMEMONITOR_H
#define VOLUMEMONITOR_H
#include "volume.h"
#include <mmdeviceapi.h>
#include <endpointvolume.h>
class VolumeMonitor : IMMNotificationClient, IAudioEndpointVolumeCallback {
public:
VolumeMonitor(HANDLE worker_thread);
~VolumeMonitor();
HRESULT Init();
HRESULT GetVolume(VolumeState *state);
HRESULT SetVolume(float level);
void Teardown();
void RegisterCallback(VolumeNotifyCallback callback);
// IMMNotificationClient (only need to really implement OnDefaultDeviceChanged)
IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId);
IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR, DWORD) { return S_OK; }
IFACEMETHODIMP OnDeviceAdded(LPCWSTR) { return S_OK; }
IFACEMETHODIMP OnDeviceRemoved(LPCWSTR) { return S_OK; }
IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) { return S_OK; }
IFACEMETHODIMP OnDeviceQueryRemove() { return S_OK; }
IFACEMETHODIMP OnDeviceQueryRemoveFailed() { return S_OK; }
IFACEMETHODIMP OnDeviceRemovePending() { return S_OK; }
// IAudioEndpointVolumeCallback
IFACEMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify);
// IUnknown
IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk);
private:
IMMDeviceEnumerator *deviceEnumerator;
IAudioEndpointVolume *endpointVolume;
HANDLE worker_thread_;
long m_cRef;
VolumeNotifyCallback registeredCallback;
static void CALLBACK ChangeEndpoint(ULONG_PTR lParam);
static void CALLBACK ChangeVolume(ULONG_PTR lParam);
// IUnknown
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment