Using IMMNotificationClient in a more safe manner

Get help with using AutoHotkey and its commands and hotkeys
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Using IMMNotificationClient in a more safe manner

14 Mar 2016, 19:34

Thanks to Coco's CWebView and Lexikos' ActiveScript, I was able to create a (very) poor solution to receiving audio notifications in AutoHotkey. However, I'm facing the following problems and I would very much appreciate some help:
  • Is the manner in which I'm using VarSetCapacity in sIMMNotificationClient() safe? Rather, will the memory allocated to create an instance of the IMMNotificationClient interface/struct last the lifetime of AutoHotkey.exe or will it be freed whenever when client := sIMMNotificationClient() goes out of scope?
  • Can I force callback functions registered with RegisterCallback to run on the same thread as the rest of the script? I'm not sure but I'm assuming a new thread is used, because I must run CoInitalize again inside the callback function to be able to use the functions from the Vista Audio Control library. I've tried all the variations of setting "Fast" in RegisterCallback() and using Critical in the sIMMNotificationClient_OnDefaultDeviceChanged() function to no avail. What I have now just seems like a disaster waiting to happen. :)

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#Persistent
#SingleInstance force

sIMMNotificationClient_GUID2String(pGUID)
{
	VarSetCapacity(string, 38*2)
	DllCall("ole32\StringFromGUID2", "Ptr", pGUID, "Str", string, "Int", 39)
	return string
}

sIMMNotificationClient_QueryInterface(this_, riid, ppvObject)
{
	static IID_IUnknown              := "{00000000-0000-0000-C000-000000000046}"
		 , IID_IMMNotificationClient := "{7991EEC9-7E89-4D85-8390-6C703CEC60C0}"

	iid := sIMMNotificationClient_GUID2String(riid)
	if (iid = IID_IMMNotificationClient || iid = IID_IUnknown)
	{
		NumPut(this_, ppvObject+0)
		return 0 ;// S_OK
	}

	NumPut(0, ppvObject+0)
	return 0x80004002 ;// E_NOINTERFACE
}

sIMMNotificationClient_AddRef(this_)
{
	return 1
}

sIMMNotificationClient_Release(this_)
{
	return 1
}

sIMMNotificationClient_OnDeviceStateChanged(this_, pwstrDeviceId, dwNewState)
{
	return 0
}

sIMMNotificationClient_OnDeviceAdded(this_, pwstrDeviceId)
{
	return 0
}

sIMMNotificationClient_OnDeviceRemoved(this_, pwstrDeviceId)
{
	return 0
}

sIMMNotificationClient_OnDefaultDeviceChanged(this_, flow, role, pwstrDeviceId)
{
	Critical
	
	Static comInitialised := false
	if (!comInitialised) {
		DllCall("ole32\CoInitialize", "UInt", 0)
		comInitialised := true
	}
	endpointId := StrGet(pwstrDeviceId, "UTF-16")
	device := VA_GetDevice(endpointId)
	MsgBox % VA_GetDeviceName(device)
	ObjRelease(device)
	return 0
}

sIMMNotificationClient_OnPropertyValueChanged(this_, pwstrDeviceId, key)
{
	return 0
}

sIMMNotificationClient_Vtbl()
{
	static vtable
	if !VarSetCapacity(vtable) {
		funcs := ["QueryInterface", "AddRef", "Release", "OnDeviceStateChanged"
				 ,"OnDeviceAdded", "OnDeviceRemoved", "OnDefaultDeviceChanged", "OnPropertyValueChanged"]

		VarSetCapacity(vtable, funcs.Length() * A_PtrSize)

		for i, vtEntry in funcs
			NumPut(RegisterCallback("sIMMNotificationClient_" vtEntry, "Fast"), vtable, (i-1) * A_PtrSize)			
	}
	return &vtable
}

sIMMNotificationClient()
{
	static vtable := sIMMNotificationClient_Vtbl()
	VarSetCapacity(client, A_PtrSize)
	NumPut(vtable, client)
	return client
}

CLSID_MMDeviceEnumerator := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID_IMMDeviceEnumerator := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
client := sIMMNotificationClient()
deviceEnumerator := ComObjCreate(CLSID_MMDeviceEnumerator, IID_IMMDeviceEnumerator)
VA_IMMDeviceEnumerator_RegisterEndpointNotificationCallback(deviceEnumerator, &client) ; VA.ahk needed

lexikos
Posts: 6731
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Using IMMNotificationClient in a more safe manner

14 Mar 2016, 21:05

Is the manner in which I'm using VarSetCapacity in sIMMNotificationClient() safe?
No.
VarSetCapacity wrote:For performance reasons, freeing a variable whose previous capacity was less than 64 characters (128 bytes in Unicode builds) might have no effect because its memory is of a permanent type.
... but you're better of using static client. Also, return client isn't likely to work correctly. It will interpret the binary number as a string of characters. You should return &client instead (and use the return value directly, not &the_return_value).
I'm not sure but I'm assuming a new thread is used, because I must run CoInitalize again inside the callback function to be able to use the functions from the Vista Audio Control library.
Don't confuse AutoHotkey's quasi-threads (which are affected by the "Fast" option) with OS threads. AutoHotkey is not thread safe. Do not use callback mechanisms that call script functions on any thread other than AutoHotkey's main thread.
Can I force callback functions registered with RegisterCallback to run on the same thread as the rest of the script?
That's up to the native code which calls the callback. You could create some machine code (with an assembler, or carefully with specific portable C code) which would synchronize with the main thread via SendMessage or similar mechanism. ("Specific portable C code" means, among other things, that you can't rely on the normal function import mechanism. You can pass the code a pointer to SendMessage instead.)

Alternatively, you could do this via a properly compiled DLL. It's probably easier than constructing portable machine code.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Using IMMNotificationClient in a more safe manner

15 Mar 2016, 04:22

All understood, thank you for clearing everything up, lexikos.

Return to “Ask For Help”

Who is online

Users browsing this forum: Google [Bot], Marcuz990, rico02066 and 172 guests