fisofo Posted June 4, 2013 Share Posted June 4, 2013 As mentioned at the end of >this thread, I'm looking to code a way to capture OnDeviceStateChanged events. I've read through the msdn info, as well as an example they have, but it's frankly over my head. Tracexx's code utilizes some of this stuff, but not this particular method. The purpose is for triggering code when an audio endpoint is added/removed/changed from the system. GUIRegisterMsg($WM_DEVICECHANGE, 'WM_DEVICECHANGE') can be used for some of these when they are USB related, but not when a bluetooth speaker (for example) connects or disconnects. Link to comment Share on other sites More sharing options...
trancexx Posted June 4, 2013 Share Posted June 4, 2013 Great, you made new thread. Ok so first you have to know what object is. Object is set of methods and it's represented by simple pointer. There is no mumbo-jumbo about that. If you want to go deeper it can be said that object is pointer to pointer pointing to set of pointers. Each of pointers in that set is new function pointer. In case of object with thre methods/functions: --> Function1 Pointer / |OBJECT| ---> OBJECT Pointer --+ --> Function2 Pointer \ --> Function3 Pointer So how to make object in AutoIt? One of my goals as AutoIt developer was to add so-called event or sink or callback objects. Even though all the code was done and all that's left was to commit the addition, my role as developer was over due to ... I will stop now before I say something that would offend some people. But, don't worry. Considering AutoIt has callback functions these objects could be build using them. The maing thing to account for is packing the set of functions inside object. You know by now that there is thing called Interface definition. This is description of the object of that interface. In AutoIt that's done by "Interface description string". If you go through the IPolicyConfig script code you will see $tagIMMDeviceEnumerator or $tagIPolicyConfig, or... Those are interface desc. strings and they describe every function (method) object exposes. Each of lines is one function and inside the object they are represented by pointer. So if I would create each function with DllCallbackRegister() and use that pointer in place of method I would create object. Interface strings by definition does not include first three methods of every COM interface (we work with that). This was done to lift the load from users so AutoIt internally add first three IUnknown methods for COM objects. So I need to create callbacks for them too to have proper v-table order (google if you aren't familiar with the term). So, to get object from interface script I can write this function: Func ObjectFromDtag($sFunctionPrefix, $tagInterface, ByRef $tInterface) Local Const $tagIUnknown = "QueryInterface hresult(ptr;ptr*);" & _ "AddRef dword();" & _ "Release dword();" ; Adding IUnknown methods $tagInterface = $tagIUnknown & $tagInterface Local Const $PTR_SIZE = DllStructGetSize(DllStructCreate("ptr")) Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(GetMethods($tagInterface), "object", "idispatch"), "variant*", "ptr"), "hresult", "long"), "bstr", "ptr"), @LF, 3) Local $iUbound = UBound($aMethods) Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams ; Allocation. Read http://msdn.microsoft.com/en-us/library/ms810466.aspx to see why like this (object + methods): $tInterface = DllStructCreate("ptr[" & $iUbound + 1 & "]") If @error Then Return SetError(1, 0, 0) For $i = 0 To $iUbound - 1 $aSplit = StringSplit($aMethods[$i], "|", 2) If UBound($aSplit) <> 2 Then ReDim $aSplit[2] $sNamePart = $aSplit[0] $sTagPart = $aSplit[1] $sMethod = $sFunctionPrefix & $sNamePart $aTagPart = StringSplit($sTagPart, ";", 2) $sRet = $aTagPart[0] $sParams = StringReplace($sTagPart, $sRet, "", 1) $sParams = "ptr" & $sParams DllStructSetData($tInterface, 1, DllCallbackGetPtr(DllCallbackRegister($sMethod, $sRet, $sParams)), $i + 2) ; Freeing is left to AutoIt. Next DllStructSetData($tInterface, 1, DllStructGetPtr($tInterface) + $PTR_SIZE) ; Interface method pointers are actually pointer size away Return ObjCreateInterface(DllStructGetPtr($tInterface), "", $tagInterface, False) ; and first pointer is object pointer that's wrapped EndFunc ... So now all I need to do is use Interface description string and I have my own custom object whose pointer I get by simply calling default method (I will show you). An example could be: expandcollapse popup#include <WinApi.au3> Global Const $tagMyInterface = "FirstMethod hresult(wstr);" & _ "SecondMethod hresult(int;wstr);" ;==================================================================== ; Define methods of your object. Every method starts with e.g. _MyObject_ because you will pass that to ObjectFromDtag Func _MyObject_QueryInterface($pSelf, $pRIID, $pObj) ConsoleWrite("_MyNotificationHandler_QueryInterface called" & @CRLF) Local $tStruct = DllStructCreate("ptr", $pObj) Switch _WinAPI_StringFromGUID($pRIID) Case $sIID_IUnknown; , $sIID_Whatewer_you_want DllStructSetData($tStruct, 1, $pSelf) ConsoleWrite(" <IUnknown or Whatewer_you_want>" & @CRLF) Return 0 ; S_OK EndSwitch ConsoleWrite(@CRLF) Return 0x80004002 ; E_NOINTERFACE EndFunc Func _MyObject_AddRef($pSelf) #forceref $pSelf ConsoleWrite("_MyNotificationHandler_AddRef called" & @CRLF) Return 0 EndFunc Func _MyObject_Release($pSelf) #forceref $pSelf ConsoleWrite("_MyNotificationHandler_Release called" & @CRLF) Return 0 EndFunc Func _MyObject_FirstMethod($pSelf, $sParam) #forceref $pSelf ConsoleWrite("_MyObject_FirstMethod called. $sParam = " & $sParam & @CRLF) Return 0 ; S_OK EndFunc Func _MyObject_SecondMethod($pSelf, $iParam, $sParam) #forceref $pSelf ConsoleWrite("_MyObject_FirstMethod called. $iParam = " & $iParam & ", $sParam = " & $sParam & @CRLF) Return 0 ; S_OK EndFunc ;==================================================================== ; So just create your object Global $tMyObject Global $oMyObject = ObjectFromDtag("_MyObject_", $tagMyInterface, $tMyObject) ; Is object get? ConsoleWrite("!!! IsObj($oMyObject) = " & IsObj($oMyObject) & @CRLF) ; Try calling some method $oMyObject.FirstMethod("Test string") ; Try another $oMyObject.SecondMethod(2345, "Another string") ; Get object pointer: ConsoleWrite("+>>> Object pointer = " & $oMyObject() & @CRLF) ; The End Func ObjectFromDtag($sFunctionPrefix, $tagInterface, ByRef $tInterface) Local Const $tagIUnknown = "QueryInterface hresult(ptr;ptr*);" & _ "AddRef dword();" & _ "Release dword();" ; Adding IUnknown methods $tagInterface = $tagIUnknown & $tagInterface Local Const $PTR_SIZE = DllStructGetSize(DllStructCreate("ptr")) ; Below line really simple even though it looks super complex. It's just written weird to fit one line, not to steal your eyes Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace($tagInterface, "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "object", "idispatch"), "variant*", "ptr"), "hresult", "long"), "bstr", "ptr"), @LF, 3) Local $iUbound = UBound($aMethods) Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams ; Allocation. Read http://msdn.microsoft.com/en-us/library/ms810466.aspx to see why like this (object + methods): $tInterface = DllStructCreate("ptr[" & $iUbound + 1 & "]") If @error Then Return SetError(1, 0, 0) For $i = 0 To $iUbound - 1 $aSplit = StringSplit($aMethods[$i], "|", 2) If UBound($aSplit) <> 2 Then ReDim $aSplit[2] $sNamePart = $aSplit[0] $sTagPart = $aSplit[1] $sMethod = $sFunctionPrefix & $sNamePart $aTagPart = StringSplit($sTagPart, ";", 2) $sRet = $aTagPart[0] $sParams = StringReplace($sTagPart, $sRet, "", 1) $sParams = "ptr" & $sParams DllStructSetData($tInterface, 1, DllCallbackGetPtr(DllCallbackRegister($sMethod, $sRet, $sParams)), $i + 2) ; Freeing is left to AutoIt. Next DllStructSetData($tInterface, 1, DllStructGetPtr($tInterface) + $PTR_SIZE) ; Interface method pointers are actually pointer size away Return ObjCreateInterface(DllStructGetPtr($tInterface), "", $tagInterface, False) ; and first pointer is object pointer that's wrapped EndFunc IMMNotificationClient is: Global Const $sIID_IMMNotificationClient = "{7991EEC9-7E89-4D85-8390-6C703CEC60C0}" Global Const $tagIMMNotificationClient = "OnDeviceStateChanged hresult(wstr;dword);" & _ "OnDeviceAdded hresult(wstr);" & _ "OnDeviceRemoved hresult(wstr);" & _ "OnDefaultDeviceChanged hresult(dword;dword;wstr);" & _ "OnPropertyValueChanged hresult(wstr;int64);" ; last param type is improvisation because AutoIt lacks proper type ... Now try and ask again if there would be problems. ♡♡♡ . eMyvnE Link to comment Share on other sites More sharing options...
fisofo Posted June 6, 2013 Author Share Posted June 6, 2013 trancexx, thank you for taking the time to dig into this in-depth with me! Having said that, much of what you posted is over my head, and will probably take me a long time to really get a grasp of it... I've read through it a few times, but I'm having difficulty understanding it; it's a level or three above my current grasp of programming/AutoIt. I can *sort of* see what you are getting at, in that you are showing me how to access the methods of the object, and I can see how you've done that with your original $tagIMMDeviceEnumerator and with your example code here, but there are bits and pieces all over that I don't get or am not sure how to translate to what I want to do, and I don't even know where to start asking questions. I figured out that I need to do a IMMDeviceEnumerator::RegisterEndpointNotificationCallback first, but I'm afraid that I'll need to table this for now until I can take more time to learn this stuff, or until someone else codes it and I can learn from that... it probably would have been easier back in college when I was actually more fluent in programming languages, but it's been too long! If you feel like whipping the code together, that would be great, but I won't ask or expect you to do that after you've already done so much. Thanks for trying to teach me though! Link to comment Share on other sites More sharing options...
fisofo Posted August 7, 2016 Author Share Posted August 7, 2016 Hate to be that guy, but I'm resurrecting this post because SetDefaultEndpoint is no longer working after the latest Win10 update. I was going to just PM you Trancexx, but looks like I can't; mind taking a look? Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now