Created
November 27, 2014 18:25
-
-
Save chowey/6705c5c6b07399e6a73a to your computer and use it in GitHub Desktop.
Volume monitoring and control DLL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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