Jump to content

Recommended Posts

Posted (edited)

So hopefully there's a better way of doing this, but I was pretty happy when this actually worked!

We've been looking at some MediaFoundation stuff recently - and in order to spin up the MFMediaEngine object you must provide an interface to handle callbacks. Basically you are meant to write your own handler with an IMFEventNotify interface, then pass it to the factory. But how do you do this in AutoIt?  Well it seems you can dodgy something up!

IMFEventNotify object inherits from IUnknown  has 1 method of its own - EventNotify

  1. write the 4 methods and throw them into memory with DllCallbackRegister. 
  2. Grab all the function pointers and pop them into an array. You now have yourself a vtable.
  3. The pointer to a vtable is an interface ptr. And that is a COM object! 
     
#include <WinAPI.au3>

Global Const $sIID_IUnknown = "{00000000-0000-0000-C000-000000000046}"
Global Const $sIID_IMFMediaEngineNotify = "{fee7c112-e776-42b5-9bbf-0048524e2bd5}"
Global Const $tag_IMFMediaEngineNotify = "EventNotify hresult(uint; ulong_ptr; uint);"

;The Obj will look like this in memory.
;Only 2 interfaces are supported, IUnknown, and one derived from IUnknown. - They both can coexist in the one place
;for a more complex object we might need to store the locations of multiple vtables, and return the correct one with QueryInterface.

; - pIface -> +- pVtab  ->  +- pQueryInterface
;             |             |
;             +- iRefCnt    +- pAddRef
;                           |
;                           +- pRelease
;                           |
;                           +- pEventNotify

Local $tEventNotify_QI = DllCallbackRegister("IMFEventNotiy_QueryInterface", "long", "ptr;ptr;ptr")
Local $tEventNotify_AR = DllCallbackRegister("IMFEventNotiy_AddRef", "long", "ptr")
Local $tEventNotify_R = DllCallbackRegister("IMFEventNotiy_Release", "long", "ptr")
Local $tEventNotify_EN = DllCallbackRegister("IMFEventNotiy_EventNotify", "long", "ptr;dword;dword_ptr;dword")

Local $tIMFMediaEngineNotify_Vtab = DllStructCreate("ptr pQueryInterface;ptr pAddRef;ptr pRelease;ptr pEventNotify")
$tIMFMediaEngineNotify_Vtab.pQueryInterface = DllCallbackGetPtr($tEventNotify_QI)
$tIMFMediaEngineNotify_Vtab.pAddRef = DllCallbackGetPtr($tEventNotify_AR)
$tIMFMediaEngineNotify_Vtab.pRelease = DllCallbackGetPtr($tEventNotify_R)
$tIMFMediaEngineNotify_Vtab.pEventNotify = DllCallbackGetPtr($tEventNotify_EN)

Local $tIMFMediaEngineNotify = DllStructCreate("ptr pVTab;int iRefCnt")
$tIMFMediaEngineNotify.pVtab = DllStructGetPtr($tIMFMediaEngineNotify_Vtab)

Func IMFEventNotiy_QueryInterface($pThis, $pIID, $ppObj)
    ; hResult = oThis.QueryInterface(In pIID, Out pObj*)
    Local $hResult = $S_OK

    If Not $ppObj Then Return $E_POINTER
    Local $tRetObj = DllStructCreate("ptr pObj", $ppObj) ;pObj is ByRef, so ppObj should always be provided.

    Switch _WinAPI_StringFromGUID($pIID)
        ;These interfaces are located where we currently are!
        ;So return ptr to self.
        Case $sIID_IMFMediaEngineNotify, $sIID_IUnknown
            $tRetObj.pObj = $pThis
            IMFEventNotiy_AddRef($pThis)

        Case Else
            $tRetObj.pObj = 0
            $hResult = $E_NOINTERFACE
    EndSwitch

    Return $hResult
EndFunc   ;==>IMFEventNotiy_QueryInterface

Func IMFEventNotiy_AddRef($pThis)
    ; iRefCnt = oThis.AddRef()
    Local $tThis = DllStructCreate("ptr VTab;int RefCnt", $pThis)
    $tThis.RefCnt += 1

    Return $tThis.RefCnt
EndFunc   ;==>IMFEventNotiy_AddRef

Func IMFEventNotiy_Release($pThis)
    ;iRefCnt = oThis.Release()
    Local $tThis = DllStructCreate("ptr VTab;int RefCnt", $pThis)
    $tThis.RefCnt -= 1

    ;I guess we could probably do some cleanup once RefCnt = 0.
    ;Not sure how best to do that!.

    Return $tThis.RefCnt
EndFunc   ;==>IMFEventNotiy_Release

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Define Event handler.
Func IMFEventNotiy_EventNotify($pThis, $iEvent, $iParam1, $iParam2)
    ConsoleWrite(StringFormat("EventRecieved! Event: %d, Param1: %d, Param2: %d", $iEvent, $iParam1, $iParam2) & @CRLF)
EndFunc   ;==>IMFEventNotiy_EventNotify

;Ready to go!
Local $pIMFMediaEngineNotify = DllStructGetPtr($tIMFMediaEngineNotify) ; Interface ptr (to IMFMediaEngineNotify)
IMFEventNotiy_AddRef($pIMFMediaEngineNotify) ;The oject exists in mem, so refCnt should start at 1.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Local $oIMFMediaEngineNotify = ObjCreateInterface($pIMFMediaEngineNotify, $sIID_IMFMediaEngineNotify, $tag_IMFMediaEngineNotify)
ConsoleWrite("IsObj($oIMFMediaEngineNotify) = " & IsObj($oIMFMediaEngineNotify) & @CRLF & @CRLF)

;Test some methods. - Get IUnknown Iface, then "release" it.
ConsoleWrite("QueryInterface Test:" & @CRLF)
Local $pIUnknown, $tGUID = _WinAPI_GUIDFromString($sIID_IUnknown)
$oIMFMediaEngineNotify.QueryInterface(DllStructGetPtr($tGUID), $pIUnknown)
ConsoleWrite(StringFormat("$pIUnknown = %s", Ptr($pIUnknown)) & @CRLF)
Local $oIUnknown = ObjCreateInterface($pIUnknown, $sIID_IUnknown, "")
ConsoleWrite("RefCnt = " & $oIUnknown.Release() & ", .Release()" & @CRLF & @CRLF)

;Test external Addref call.
ConsoleWrite("AddRef Test:" & @CRLF)
ConsoleWrite("RefCnt = " & $oIMFMediaEngineNotify.AddRef() & ", .AddRef()" & @CRLF)
ConsoleWrite("RefCnt = " & $oIMFMediaEngineNotify.Release() & ", .Release()" & @CRLF & @CRLF)

;Send an event.
ConsoleWrite("EventNotify Test:" & @CRLF)
$oIMFMediaEngineNotify.EventNotify(1, 2, 3)

 

Edited by MattyD
Tweaked example
Posted

Very interesting.  I was curious to do somewhat the inverse and it didn't work well.  Maybe you have an explanation.

#include <WinAPIDiag.au3>
#include <WinAPIConv.au3>

Global Const $MF_VERSION = 0x00020070
Global Enum $MFSTARTUP_FULL, $MFSTARTUP_LITE

DllCall("Mfplat.dll", "long", "MFStartup", "long", $MF_VERSION, "long", $MFSTARTUP_FULL)

Local $aCall = DllCall("Mf.dll", "long", "MFCreateMediaSession", "ptr", 0, "ptr*", 0)
Local $pSession = $aCall[2]

Local $tSession = DllStructCreate("ptr pVTab;int iRefCnt", $pSession)
ConsoleWrite("Ref = " & $tSession.iRefCnt & "/" & Hex($tSession.iRefCnt) & @CRLF)
Local $tSession_Vtab = DllStructCreate("ptr pFunc[10]", $tSession.pVtab)
For $i = 1 To 10
  ConsoleWrite($tSession_Vtab.pFunc(($i)) & @CRLF)
Next

DllCall("Mfplat.dll", "long", "MFShutdown")

The list of the addresses in the vtable seems right.  But the ref count is not, it looks like another address of something. 

Posted (edited)

yeah it will be different - I've just made up where to put the reference count.

By my understanding the object's resources sit somewhere under the vtable pointers, but I didn't bother trying to unpick what is actually in there.  I guess it would make sense for this to be another table of pointers to different properties or something.

Edit: just for fun - I found the refcount for MediaSession via a memory dump!

Local $oSession = ObjCreateInterface($pSession, $sIID_IUnknown, "")
$iOffset = (@AutoItX64) ? 75 : 77
$iPtrSz = (@AutoItX64) ? 8 : 4
$tRefCnt = DllStructCreate("int Count", Ptr($pSession + $iPtrSz * $iOffset))
$oSession.AddRef()
ConsoleWrite($tRefCnt.Count & @CRLF)
$oSession.AddRef()
ConsoleWrite($tRefCnt.Count & @CRLF)

 

Edited by MattyD
Posted (edited)

Ok, if I understand you correctly, each object has its own way to store its data.  Only thing that relates them all is the vtable which is the first ptr of the structure. Rest of the structure is just a bunch of data/ptr for managing its methods and properties.  Once you establish the vtable,  you can pretty much do everything you want with the rest.  So the structure you get from a pointer of one object can be totally different from another object as long as the vtable is the first part of the structure.

Edited by Nine
Posted

yeah more or less,  everything external to the object should be interacting with it via the funcs in its vtable.

Don't get me wrong, I'm sure there's some "proper" way to organize things -  but I'm not too concerned about the internals. We just need to write and expose methods well enough so the object behaves appropriately.

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...