Jump to content

Create Interface Object on Tag - UDF


Go to solution Solved by Nine,

Recommended Posts

Posted (edited)

First I must thank @MattyD for showing me how to perform such a task in this thread.  I felt I needed to play with it to fully understand the intrinsic.

So I ended up making a small UDF that is accomplishing the creation of interface objects and the deletion of those made by the UDF.

The example included in the zip file is largely inspired on the example made by the original author of the Media Engine topic. 

I believe the UDF is quite straightforward  to use but, in any cases, post here your questions, I will be glad to answer in the scope of my knowledge.

Version 2025-04-16

* Solved an issue with the tag transformation
* Code optimization
* Added tag validation

 

ObjFromTag UDF.zip

Edited by Nine
Posted

Hey mate - great work!

just a suggestion - take or leave it

This *should* add a placeholder for methods we don't care about, and don't want to wrap.  (probably requires some more testing)

#include <WinAPIConv.au3>
#include <WinAPIConstants.au3>

; #ObjFromTag# =================================================================================================================
; Name ..........: ObjFromTag UDF.au3
; Description ...: Create COM interface object based on tag instead of CLSID
; Author ........: Nine
; Created .......: 2020-04-14
; Modified ......:
; Remark ........: Inspired by MattyD, trancexx
; Links .........: https://www.autoitscript.com/forum/topic/212828-byo-com-object/
;                : https://www.autoitscript.com/forum/topic/153520-iuiautomation-ms-framework-automate-chrome-ff-ie/page/6/#findComment-1143566
; Example .......: Yes
; ===============================================================================================================================

; #Functions# ===================================================================================================================
; ObjCreateFromTag($sPrefix, $tagInterface, ByRef $tInterface, $sIID = Default, $bIsUnknown = Default)
; _QueryInterface($pSelf, $pRIID, $pObj)
; _AddRef($pSelf)
; _Release($pSelf)
; ObjDeleteFromTag(ByRef $tInterface)
; ===============================================================================================================================

Global Const $sIID_IUNKNOWN = "{00000000-0000-0000-C000-000000000046}"
Global Const $tagIUNKNOWN = _
        "QueryInterface hresult(ptr; ptr*);" & _
        "AddRef ulong();" & _
        "Release ulong();"

Global Const $tagIHEADER = "align 4;ptr pObject;long iRefCnt;char sIID[" & StringLen($sIID_IUNKNOWN) + 1 & "];"
Global Const $tagIINTERFACE = $tagIHEADER & "int iSize;ptr pVTable[%i];ptr pCallback[%i];"

Global $hQueryInterface = DllCallbackRegister("_QueryInterface", "long", "ptr;ptr;ptr*")
Global $hAddRef = DllCallbackRegister("_AddRef", "int", "ptr")
Global $hRelease = DllCallbackRegister("_Release", "int", "ptr")
Global $hNoMethod = DllCallbackRegister("_NoMthd", "int64", "ptr;int64;int64;int64;int64;int64;int64;int64;int64;int64;int64;int64;int64")

Func ObjCreateFromTag($sPrefix, $tagInterface, ByRef $tInterface, $sIID = Default, $bIsUnknown = Default)
  If $sIID = Default Then $sIID = $sIID_IUNKNOWN
  If $bIsUnknown = Default Then $bIsUnknown = True
  Local $sInterface = ($bIsUnknown ? $tagIUNKNOWN : "") & $tagInterface
  Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace(StringRegExpReplace($sInterface, "\w+\*", "ptr"), "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "object", "idispatch"), "hresult", "long"), "bstr", "ptr"), "variant", "ptr"), @LF, 3)
  Local $iUbound = UBound($aMethods), $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams, $hCallback
  $tInterface = DllStructCreate(StringFormat($tagIINTERFACE, $iUbound, $iUbound))
  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 = $sPrefix & $sNamePart
    $aTagPart = StringSplit($sTagPart, ";", 2)
    $sRet = $aTagPart[0]
    $sParams = StringStripWS("ptr" & StringReplace($sTagPart, $sRet, "", 1), $STR_STRIPALL)
    $hCallback = DllCallbackRegister($sMethod, $sRet, $sParams)
    If @error Then
        ConsoleWrite( $sMethod & "/" & $sRet & "/" & $sParams & " = " & @error & @CRLF)
        $hCallback = $hNoMethod
        If $bIsUnknown Then
            Switch $i
                Case 0
                    $hCallback = $hQueryInterface
                Case 1
                    $hCallback = $hAddRef
                Case 2
                    $hCallback = $hRelease
            EndSwitch
        EndIf
    EndIf
    DllStructSetData($tInterface, "pVTable", DllCallbackGetPtr($hCallback), $i + 1)
    DllStructSetData($tInterface, "pCallback", $hCallback, $i + 1)
  Next
  $tInterface.iRefCnt = 1 ; start ref at 1 since creation is completed
  $tInterface.iSize = $iUbound
  $tInterface.pObject = DllStructGetPtr($tInterface, "pVTable")
  $tInterface.sIID = $sIID
  Return ObjCreateInterface(DllStructGetPtr($tInterface), $sIID, $tagInterface, $bIsUnknown)
EndFunc   ;==>ObjFromTag

Func _QueryInterface($pSelf, $pRIID, $pObj)
  If Not $pObj Then Return $E_POINTER
  Local $sIID = _WinAPI_StringFromGUID($pRIID)
  Local $tInter = DllStructCreate($tagIHEADER, $pSelf)
  If $sIID = $tInter.sIID Or $sIID = $sIID_IUNKNOWN Then
    DllStructSetData(DllStructCreate("ptr", $pObj), 1, $pSelf)
    _AddRef($pSelf)
    Return $S_OK
  EndIf
  Return $E_NOINTERFACE
EndFunc   ;==>_QueryInterface

Func _AddRef($pSelf)
  Local $tInter = DllStructCreate($tagIHEADER, $pSelf)
  $tInter.iRefCnt += 1
  Return $tInter.iRefCnt
EndFunc   ;==>_AddRef

Func _Release($pSelf)
  Local $tInter = DllStructCreate($tagIHEADER, $pSelf)
  $tInter.iRefCnt -= 1
  Return $tInter.iRefCnt
EndFunc   ;==>_Release

Func _NoMthd($pThis, $vP1, $vP2, $vP3, $vP4, $vP5, $vP6, $vP7, $vP8, $vP9, $vP10, $vP11, $vP12)
EndFunc
Posted (edited)

@MattyD  Thanks.  Since we already need to provide a prefixed function for each of the method, we can left its content blank if we do not plan to use it.  No reason to call another empty function.  Also, if we have unused methods, we can leave their parameters blank, and simply use $pSelf in the callback function as a single parameter.

Edited by Nine
  • Solution
Posted (edited)
2 hours ago, Nine said:

Since we already need to provide a prefixed function for each of the method,

 Just wanted to clarify, the purpose was to remove the requirement. So if you don't define a function for a particular method, you (or something else) can still call it, but you'll actually be executing _NoMthd() so nothing will happen.

But yeah, requiring funcs to be provided is an entirely reasonable position too!

Edited by MattyD
Posted (edited)

Understand.  I think I will leave it like that, feel it is less prone to bug if user misspelled by error the method-function.

However, I could make it clearer to the user if a method-function does not exist.  DllCallbackRegister will return an error but it is not obvious what is the reason.

Edited by Nine

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...