[RESOLVED] DispCallFunc to call COM Property/Method-VBForums
Results 1 to 26 of 26

Thread: [RESOLVED] DispCallFunc to call COM Property/Method

  1. #1

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Resolved [RESOLVED] DispCallFunc to call COM Property/Method

    Hi VB experts,

    I am trying to retrieve the Caption of a newly created Excel Application using the following code but I can't make it work .. The varRtn argument returns Empty :

    Code:
    Private Declare Function DispCallFunc Lib "OleAut32.dll" ( _
        ByVal pvInstance As Long, _
        ByVal oVft As Long, _
        ByVal cc_ As Long, _
        ByVal vtReturn As Integer, _
        ByVal cActuals As Long, _
        ByRef prgvt As Integer, _
        ByRef prgpvarg As Long, _
        ByRef pvargResult As Variant _
        ) As Long
    
    
    Sub Test()
    
        Const CC_STDCALL As Long = 4&
        Dim XL_App As Object
        Dim pUNK As Long
        Dim IID_First_Method_Offset As Long
        Dim varRtn As Variant
    
       
        Set XL_App = CreateObject("Excel.Application")
        pUNK = ObjPtr(XL_App)
        
        IID_First_Method_Offset = 77 * 4  '<== Excel Caption Property_Get index=77
        
        Call DispCallFunc(pUNK, IID_First_Method_Offset, CC_STDCALL, _
                            VBA.vbString, 0&, 0&, 0&, varRtn)
                            
        MsgBox varRtn
    End Sub
    I hope someone can shed some light on this.

    Regards.

  2. #2
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Quote Originally Posted by JAAFAR View Post
    Hi VB experts,

    I am trying to retrieve the Caption of a newly created Excel Application using the following code but I can't make it work ..
    The DispCallFunc returns a value if it fails. You should be looking at the error, it may shed some light. Ensure you are correct with the VTable offset of 77*4. Additionally, typically you do not return strings without first sizing the string. There may be another function you need to call to get that string size?

    If you can't resolve this on your own, you may want to post the function/method that lives at offset 77*4. Preferably post the link to that method or provide all the details for each parameter, if any, along with the details of the function.

    Edited: Note that your VTable offset must include any VTables/interfaces that your target interface inherits from, i.e., all inherit at least IUnknown and its 3 methods. But as a side note, doesn't the Excel app itself, give you functions/properties for that information? Just curious.
    Last edited by LaVolpe; Jul 17th, 2017 at 08:09 AM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  3. #3

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    @LaVolpe

    Here is a snapshot from a TypeLib Browser of the VTable for Excel Function that lives @ that offset along with details of its parameters :



    Here is the TypeLib Listing :

    Code:
    ' ****************************************************************************************
    ' [get_]Caption property
    ' Interface name = _Application
    ' VTable offset = 308 [&H134]
    ' DispID = 139 [&H0000008B]
    ' ****************************************************************************************
    FUNCTION Excel_Application_get_Caption ( _
        BYVAL pthis AS DWORD PTR _                          ' %VT_DISPATCH <dispinterface>
      , BYREF RHS AS STRING _                               ' *%VT_BSTR <DYNAMIC UNICODE STRING> [out]
        ) AS LONG                                           ' %VT_HRESULT <LONG>
    
        LOCAL HRESULT AS LONG
        IF pthis = 0 THEN FUNCTION = %E_POINTER : EXIT FUNCTION
        CALL DWORD @@pthis[77] USING Excel_Application_get_Caption(pthis, RHS) TO HRESULT
        FUNCTION = HRESULT
    
    END FUNCTION
    ' ****************************************************************************************

    My VB code:
    Code:
    Private Declare Function DispCallFunc Lib "OleAut32.dll" ( _
        ByVal pvInstance As Long, _
        ByVal oVft As Long, _
        ByVal cc_ As Long, _
        ByVal vtReturn As Integer, _
        ByVal cActuals As Long, _
        ByRef prgvt As Integer, _
        ByRef prgpvarg As Long, _
        ByRef pvargResult As Variant _
        ) As Long
    
    
    Private Sub Test()
        Dim app As Object
        Dim lRet As Long
    
        Const CC_STDCALL As Long = 4&
        Dim pUNK As Long
        Dim IID_First_Method_Offset As Long, varRtn As Variant
       
        Set app = CreateObject("Excel.Application")
        pUNK = ObjPtr(app)
        
        IID_First_Method_Offset = 77 * 4
        
        lRet = DispCallFunc(pUNK, IID_First_Method_Offset, CC_STDCALL, _
                            vbString, 0&, 0&, 0&, varRtn)
                            
                            Debug.Print Hex(lRet)
                            Debug.Print TypeName(varRtn)
                           
    End Sub
    The error returned is &H80004003 I looked up this error code and turned out to be : E_POINTER (Pointer that is not valid)

    The variant varRtn returns Empty


    But as a side note, doesn't the Excel app itself, give you functions/properties for that information? Just curious.
    Yes I know but my objective is to learn how to use this handy DispCallFunc API and I like to start with simple examples with COM Interfaces that I am familiar with then, after that, gradually take on more complicated interfaces.

    Regards.
    Last edited by JAAFAR; Jul 17th, 2017 at 11:44 AM.

  4. #4
    Lively Member
    Join Date
    Jan 2015
    Posts
    116

    Re: DispCallFunc to call COM Property/Method

    are u attempting to extend excel's inner methods, events or interface?

  5. #5
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,546

    Re: DispCallFunc to call COM Property/Method

    Something doesn't seem right. You're creating an Excel.Application, and then assigning it to an IDispatch (Object).

    And then calling a VTable entry on Excel.Application. This would only work if _Application derives from IDispatch, and it returns the same pointer as a
    QueryInterface for _Application.

    If you're going to call VTable pointers directly - don't mix it with late binding. You have no guarantee what interface you're even calling.
    Imagine what it would be like to set breakpoints in, or step through subclassing code;
    and then being able to hit stop/end/debug or continue, without crashing the IDE.

    VB6.tlb | Bulletproof Subclassing in the IDE

  6. #6

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    When I change the code line from late binding :
    Code:
    Set app = CreateObject("Excel.Application")
    to early binding :
    Code:
    Set app = New Excel.Application
    The code crashes and shuts down

  7. #7
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,546

    Re: DispCallFunc to call COM Property/Method

    What typelib are you referencing, the EXE itself?

    Also the return type is always an HRESULT / long.

    If you're getting a property value back - you have to pass a pointer to the return value as the last parameter.
    Last edited by DEXWERX; Jul 17th, 2017 at 12:57 PM.
    Imagine what it would be like to set breakpoints in, or step through subclassing code;
    and then being able to hit stop/end/debug or continue, without crashing the IDE.

    VB6.tlb | Bulletproof Subclassing in the IDE

  8. #8

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    What typelib are you referencing, the EXE itself?
    The excel exe as I want to retrieve the Caption Property of the Excel Application.

    If you're getting a property value back - you have to pass a pointer to the return value as the last parameter.
    Not sure how to go about that ... I just hope someone can show me the VB code for how this can be done.

  9. #9
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Here's a thread I started awhile back regarding the DispCallFunc API. Sorry, not on a system where I can play along.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  10. #10
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,546

    Re: DispCallFunc to call COM Property/Method

    if you're crashing that could mean you're close. It's probably an access violation with how your are passing the return variable ptr.
    Imagine what it would be like to set breakpoints in, or step through subclassing code;
    and then being able to hit stop/end/debug or continue, without crashing the IDE.

    VB6.tlb | Bulletproof Subclassing in the IDE

  11. #11

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    Retrieving the Caption Property of the Excel.Application interface via the use of the DispCallFunc API seems to me a fair enough request and "what should be" a good way of demonstrating for beginners how this API can be used in VB for calling COM Interfaces functions.

    I have googled around but I have only seen this function used in VB examples for calling Interface Functions/Methods that are not exposed to VB the calssic way.

  12. #12
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    3,114

    Re: DispCallFunc to call COM Property/Method

    As an alternative, here's how I'd do this. It'll work regardless of whether or not you're the one who loaded Excel.

    Here's the code I pulled together to do it (for a BAS module):

    Code:
    
    Option Explicit
    '
    Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
    Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare Function GetDesktopWindow Lib "user32" () As Long
    '
    Dim hWndCount As Long
    Dim hWnds() As Long
    Dim lTheProcessIdMatch As Long
    '
    
    Public Function hWndOfChildFromPartTitle(sPartTitle As String, Optional hWndParent As Long, Optional iBackTo As Long = 1) As Long
        ' Returns the first child with a matching title name.
        '
        ' iBackTo can be used to fetch a window other than the last one loaded.
        '
        Dim hWnd() As Long
        Dim i As Long
        Dim j As Long
        '
        If hWndParent = 0 Then hWndParent = GetDesktopWindow
        '
        hWnd = hWndOfAllChildWindows(hWndParent)
        If IsDimmedLng(hWnd) Then
            j = 1
            For i = LBound(hWnd) To UBound(hWnd)
                If InStr(WindowText(hWnd(i)), sPartTitle) <> 0 Then
                    If iBackTo = j Then
                        hWndOfChildFromPartTitle = hWnd(i)
                        Exit Function
                    Else
                        j = j + 1
                    End If
                End If
            Next i
        End If
    End Function
    
    Public Function WindowText(hWndOfInterest As Long) As String
        ' Form or control.
        Dim s As String
        Dim l As Long
        '
        l = GetWindowTextLength(hWndOfInterest)
        s = Space$(l + 1)
        l = GetWindowText(hWndOfInterest, s, l + 1)
        s = RTrimNull(s)
        WindowText = Trim$(s)
    End Function
    
    Public Function hWndOfAllChildWindows(hWndParent As Long) As Long()
        ' See note for above function.  This is same except it's for child windows.
        ' The hWndOfNextChildWindow can also be used, but this is more reliable.
        hWndCount = 0
        ReDim hWnds(1 To 100)
        EnumChildWindows hWndParent, AddressOf EnumWindowsCallBack, &H0  ' Doesn't return until done.
        If hWndCount > 0 Then
            ReDim Preserve hWnds(1 To hWndCount)
        Else
            Erase hWnds
        End If
        '
        hWndOfAllChildWindows = hWnds
    End Function
    
    Public Function EnumWindowsCallBack(ByVal hWnd As Long, ByVal lpData As Long) As Long
        ' Only the API calls this.  It should not be called by user.
        '
        ' This callback function is called by Windows (from the EnumWindows
        ' API call) for EVERY window that exists.  It populates the aWindowList
        ' array with a list of windows that we are interested in.
        '
        EnumWindowsCallBack = 1
        '
        If lTheProcessIdMatch = 0 Or ProcessId(hWnd) = lTheProcessIdMatch Then
            hWndCount = hWndCount + 1
            If UBound(hWnds) < hWndCount Then ReDim Preserve hWnds(1 To UBound(hWnds) + 100)
            hWnds(hWndCount) = hWnd
        End If
    End Function
    
    Public Function ProcessId(hWndOfInterest As Long) As Long
        ' This process ID is unique to the entire application to which the window belongs.
        ' A process ID will always be unique for each running copy of an application, even if more than one copy is running.
        Dim lProcId As Long
        Call GetWindowThreadProcessId(hWndOfInterest, lProcId)
        ProcessId = lProcId
    End Function
    
    Public Function IsDimmedLng(TheArray() As Long) As Boolean
        ' This won't fail on one of the (0 to -1) arrays.
        On Error Resume Next
            IsDimmedLng = (LBound(TheArray) = LBound(TheArray)) ' Will error (leaving IsDimmedLng = False) if not dimensioned.
        On Error GoTo 0
    End Function
    
    Public Function RTrimNull(s As String) As String
        Dim i As Integer
        i = InStr(s, vbNullChar)
        If i Then
            RTrimNull = Left$(s, i - 1)
        Else
            RTrimNull = s
        End If
    End Function
    
    
    
    And here's some code I threw into a Form1 to do a bit of testing (just two CommandButtons):

    Code:
    
    Option Explicit
    
    Private Sub Command1_Click()
        Dim h As Long
    
        h = hWndOfChildFromPartTitle("Excel")
        If h Then MsgBox WindowText(h)
    End Sub
    
    Private Sub Command2_Click()
        Dim h As Long
    
        h = hWndOfChildFromPartTitle("Excel", , 2)
        If h Then MsgBox WindowText(h)
    End Sub
    
    
    Enjoy,
    Elroy

    EDIT1: You could pull that ProcessID match stuff out of it if you wanted. That was just in there from needing it for something else. Also, there probably is an easier way to do all of this, but this certainly gets it done.

    EDIT2: This is actually in response to post #13 (didn't want to make it another post). Very good, JAAFAR. In that case, I'll bow out and continue observing. I was just hoping to get you on your way in the event you were trying to do something more specific. Good Luck.
    Last edited by Elroy; Jul 17th, 2017 at 04:02 PM.

  13. #13

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    Hi Elroy,

    I Know how to retrieve the excel application caption either by using the Application.Caption Property directly or by using the GetWindowText API like you have shown in your code but that is not what I want.

    What I want is to see the use of the DispCallFunc API for achieving this goal in order to learn how one could call a COM Interface function just by providing a pointer to the queried interface and the VTable index of the function and without the need of a TLB.

    Thank you.

  14. #14
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Just a bit of research for you. Looking around on the web, it appears the _Application is a co-class of Application interface. As mentioned earlier by DEXWERX, you will need to ensure the ObjPtr() you retrieved is to the right interface. This is typically ensured by calling IUnknown:QueryInterface and passing the GUID of the desired interface. If the Object @ ObjPtr() implements that interface, then a reference to it will be returned. You would call your VTable method and release the interface. In post #9, I referenced a thread that shows the API usage with several examples. In that thread, specifically post #2, is an example of how to query the object to see if it supports the desired interface (GUID).
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  15. #15

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    @LaVolpe

    The TypeLib Browser (2.05 By Jose Roca) that I am using shows _Application as a Dual-Interface and states "Inherited Interface =IDispatch' if that helps .

    $IID__Application = GUID$("{000208D5-0000-0000-C000-000000000046}")

    Am I looking in the right place ?

  16. #16
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    That looks promising. You can query ObjPtr(app) and see if that interface is implemented. Post #2 in that link I gave you shows how to query it. What is interesting is the inherits information. 77*4 is the vtable offset you want to use, but does that include the 4 members of IDispatch & the 3 members of IUnknown? You can validate that I think by simply counting the number of members on _Application until you get to Caption:Get (assuming the 1st member is not from IUnknown else 77 is likely correct). If the number is 77, then IDispatch & IUnknown are probably not calculated in the offset, but I didn't write that typlib viewer so I don't know if Roca is taking into account inherited interfaces when giving you the offset.

    But step 1 is always to ensure you get a reference to the correct interface. You picked a more complicated interface to begin this journey . This is one reason why in that other thread, I picked relatively easier examples... build on it and move forward
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  17. #17
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,546

    Re: DispCallFunc to call COM Property/Method

    if you use early binding, you guarrantee a matching interface.

    I have however tested that at least with Excel 2000 it returns the same interface for IDispatch as _Application.
    And retrieving the caption via DispCallFunc (oVft=(77*4) does work well, I just don't have time to extract the code from my project.

    You'd have better luck using code from Schmidt or Lavolpe in the mean time.
    http://www.vbforums.com/showthread.p...=1#post4902281

    Olaf has a rapper function called vtblCall that works using this method.

    Code:
    hr = vtblCall(vbLong, ObjPtr(b), 77, VarPtr(MyCaption))
    Imagine what it would be like to set breakpoints in, or step through subclassing code;
    and then being able to hit stop/end/debug or continue, without crashing the IDE.

    VB6.tlb | Bulletproof Subclassing in the IDE

  18. #18

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    Quote Originally Posted by LaVolpe View Post
    That looks promising. You can query ObjPtr(app) and see if that interface is implemented. Post #2 in that link I gave you shows how to query it. What is interesting is the inherits information. 77*4 is the vtable offset you want to use, but does that include the 4 members of IDispatch & the 3 members of IUnknown? You can validate that I think by simply counting the number of members on _Application until you get to Caption:Get (assuming the 1st member is not from IUnknown else 77 is likely correct). If the number is 77, then IDispatch & IUnknown are probably not calculated in the offset, but I didn't write that typlib viewer so I don't know if Roca is taking into account inherited interfaces when giving you the offset.

    But step 1 is always to ensure you get a reference to the correct interface. You picked a more complicated interface to begin this journey . This is one reason why in that other thread, I picked relatively easier examples... build on it and move forward
    It does take into account the the 4 members of IDispatch & the 3 members of IUnknown as the first Function in the list has OFFSET # 28

  19. #19

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    Quote Originally Posted by DEXWERX View Post
    if you use early binding, you guarrantee a matching interface.

    I have however tested that at least with Excel 2000 it returns the same interface for IDispatch as _Application.
    And retrieving the caption via DispCallFunc (oVft=(77*4) does work well, I just don't have time to extract the code from my project.

    You'd have better luck using code from Schmidt or Lavolpe in the mean time.
    http://www.vbforums.com/showthread.p...=1#post4902281

    Olaf has a rapper function called vtblCall that works using this method.

    Code:
    hr = vtblCall(vbLong, ObjPtr(b), 77, VarPtr(MyCaption))
    Well, the code I posted doesn't work ! If you could show me the code that worked for you it would be great !

    I'll take a look @ the link and get back later .. Thanks

  20. #20
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Quote Originally Posted by JAAFAR View Post
    It does take into account the the 4 members of IDispatch & the 3 members of IUnknown as the first Function in the list has OFFSET # 28
    :thumbs-up:
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  21. #21
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Using the class in that other thread, copied and pasted into your test project, the following appears to work? If so, then simply use the class as a template for further testing or to rewrite your own generic function for the API.

    1. Name that class, after pasting, as: cDispCall
    2. Paste this code in your button click event
    Code:
    Private Sub Command1_Click()
    
    ' sample of getting the IDataObject GUID
        
        Const IUnknownQueryInterface As Long = 0&   ' IUnknown vTable offset to Query implemented interfaces
        Const IUnknownRelease As Long = 8&          ' IUnkownn vTable offset to decrement reference count
    
        Dim XL_App As Object, pUNK As Long
        Dim IID_Application As Long
        Dim IID_First_Method_Offset As Long
        Dim aGUID(0 To 3) As Long
        Dim c As cDispCall
        Dim sCaption As String
        
        Set XL_App = CreateObject("Excel.Application")
        pUNK = ObjPtr(XL_App)
        
        Set c = New cDispCall
        c.CallFunction_DLL "ole32.dll", "IIDFromString", STR_NONE, CR_LONG, CC_STDCALL, StrPtr("{000208D5-0000-0000-C000-000000000046}"), VarPtr(aGUID(0))
        c.CallFunction_COM pUNK, IUnknownQueryInterface, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(IID_Application)
        If IID_Application <> 0& Then
        
            IID_First_Method_Offset = 77 * 4  '<== Excel Caption Property_Get index=77
            c.CallFunction_COM IID_Application, IID_First_Method_Offset, CR_LONG, CC_STDCALL, VarPtr(sCaption)
        
            c.CallFunction_COM IID_Application, IUnknownRelease, CR_LONG, CC_STDCALL
        End If
    End Sub
    When run, sCaption variable should contain the caption. I'm thinking that property get statements are not returning a value, i.e., VOID. Therefore, you could change
    this: c.CallFunction_COM IID_Application, IID_First_Method_Offset, CR_LONG, CC_STDCALL, VarPtr(sCaption)
    to: c.CallFunction_COM IID_Application, IID_First_Method_Offset, CR_None, CC_STDCALL, VarPtr(sCaption)

    Edited: COM methods (vs. some APIs) always uses BSTR for strings (unicode compatible). When the method asks for a pointer to a string, pass StrPtr(theString). When it asks for a far pointer PTR>PTR, then pass VarPtr(theString). Both examples are shown above.
    Last edited by LaVolpe; Jul 17th, 2017 at 05:31 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  22. #22

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    @LaVolpe

    Thanks for the code ... It is going to take me sometime to understand/make work the Class code for two reasons 1) Because of the abstract extra layer of the Wrapping class which adds more complexity 2) Because I use VBA/Win64 and therefore the size of Pointers and the memory addresses will need to be amended to work for 64bit memory layout

  23. #23
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    Well, that part I can't help you with -- don't use 64bit O/S. However, there are plenty on this site that do. If its VBA questions from this point on, you'll want to post those in the VBA portion of the forum (Office). I know I've seen plenty of posts on 64 bit pointer topics over there.

    The wrapper can be walked/stepped thru. Passing invalid pointers, parameters, etc will crash. DispCallFunc is low-level and isn't friendly to mistakes. Part of the problem in your original attempt was that you needed to pass a parameter and did not. You were also assuming the string would be returned in the variant but like most property get statements, the passed parameter is overwritten with the property value. Another false assumption was that CreateObject would return the correct interface. But with many objects, they can implement more than one interface. Always QueryInterface to be sure.

    Also, another key point is that the parameter pointers array are always to variants (hence: Dim vParameters() as Variant in the class). The variants contain the parameter values.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  24. #24

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: DispCallFunc to call COM Property/Method

    Ok I seem to have successfully managed to adapt LaVolpe's code to make this work for 64Bit as well as 32Bits OSs.

    I have gotten rid of the Class as well as the unnecessary CallFunction_DLL routine, Enums and other bells and whistles for simplicity's sake.

    WIN 32Bits (VBA/VB6) version:
    Code:
    Option Explicit
    
    Private Declare Function IIDFromString Lib "ole32" _
      (ByVal lpsz As Long, ByVal lpiid As Long) As Long
      
    Private Declare Function DispCallFunc Lib "oleaut32.dll" _
        (ByVal pvInstance As Long, ByVal offsetinVft As Long, _
        ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, _
        ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
      
    Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
      
    Private Const CC_STDCALL As Long = 4
    
    Sub Test()
    
        Const IUnknownQueryInterface As Long = 0&   ' IUnknown vTable offset to Query implemented interfaces
        Const IUnknownRelease As Long = 8&          ' IUnkownn vTable offset to decrement reference count
        Const APPLICATION_INTERFACE_ID As String = "{000208D5-0000-0000-C000-000000000046}"
        Const Caption_Func_Method_Offset As Long = 77 * 4  '<== Excel Caption Property_Get index=77
        
        Dim pUNK As Long
        Dim hRes As Long
    
        Dim XL_App As Object
        Dim IID_Application As Long
        Dim sCaption As String
        Dim aGUID(0 To 3) As Long
        
        
        Set XL_App = CreateObject("Excel.Application")
        pUNK = ObjPtr(XL_App)
        
        hRes = IIDFromString(StrPtr(APPLICATION_INTERFACE_ID), VarPtr(aGUID(0)))
        
        If hRes <> 0 Then
            Exit Sub
        End If
        
        CallFunction_COM pUNK, IUnknownQueryInterface, vbLong, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(IID_Application)
        
        If IID_Application <> 0& Then
    
            CallFunction_COM IID_Application, Caption_Func_Method_Offset, vbLong, CC_STDCALL, VarPtr(sCaption)
            
            CallFunction_COM IID_Application, IUnknownRelease, vbLong, CC_STDCALL
            
        End If
        
        MsgBox sCaption
        
    End Sub
    
    
    Private Function CallFunction_COM(ByVal InterfacePointer As Long, ByVal VTableOffset As Long, _
                                ByVal FunctionReturnType As Long, _
                                ByVal CallConvention As Long, _
                                ParamArray FunctionParameters() As Variant) As Variant
    
        '// minimal sanity check for these 4 parameters:
        If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
        If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function ' can only be 4 bytes
    
        Dim pIndex As Long, pCount As Long
        Dim vParamPtr() As Long, vParamType() As Integer
        Dim vRtn As Variant, vParams() As Variant
        
        vParams() = FunctionParameters()                    ' copy passed parameters, if any
        pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
        If pCount = 0& Then                                 ' no return value (sub vs function)
            ReDim vParamPtr(0 To 0)
            ReDim vParamType(0 To 0)
        Else
            ReDim vParamPtr(0 To pCount - 1&)               ' need matching array of parameter types
            ReDim vParamType(0 To pCount - 1&)              ' and pointers to the parameters
            For pIndex = 0& To pCount - 1&
                vParamPtr(pIndex) = VarPtr(vParams(pIndex))
                vParamType(pIndex) = VarType(vParams(pIndex))
            Next
        End If
                                                            ' call the function now
        pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, _
                              pCount, vParamType(0), vParamPtr(0), vRtn)
            
        If pIndex = 0& Then                                 ' 0 = S_OK
            CallFunction_COM = vRtn                         ' return result
        Else
            SetLastError pIndex                             ' set error & return Empty
        End If
    
    End Function
    2- WIN 64Bits (64Bit VBA only) version:
    Code:
    Option Explicit
    
    Private Declare PtrSafe Function IIDFromString Lib "ole32" _
      (ByVal lpsz As LongPtr, ByVal lpiid As LongPtr) As LongPtr
      
    Private Declare PtrSafe Function DispCallFunc Lib "oleaut32.dll" _
        (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, _
        ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, _
        ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
      
    Private Declare PtrSafe Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
      
    Private Const CC_STDCALL As Long = 4
    
    Sub Test()
    
        Const IUnknownQueryInterface As Long = 0&   ' IUnknown vTable offset to Query implemented interfaces
        Const IUnknownRelease As Long = 8&          ' IUnkownn vTable offset to decrement reference count
        Const APPLICATION_INTERFACE_ID As String = "{000208D5-0000-0000-C000-000000000046}"
        Const Caption_Func_Method_Offset As Long = 77 * 4 * 2 '<== Excel Caption Property_Get index=77
        
        Dim pUNK As LongPtr
        Dim hRes As LongPtr
    
        Dim XL_App As Object
        Dim IID_Application As Long
        Dim sCaption As String
        Dim aGUID(0 To 3) As Long
        
        
        Set XL_App = CreateObject("Excel.Application")
        pUNK = ObjPtr(XL_App)
        
        hRes = IIDFromString(StrPtr(APPLICATION_INTERFACE_ID), VarPtr(aGUID(0)))
        
        If hRes <> 0 Then
            Exit Sub
        End If
        
        CallFunction_COM pUNK, IUnknownQueryInterface, vbLong, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(IID_Application)
        
        If IID_Application <> 0& Then
    
            CallFunction_COM IID_Application, Caption_Func_Method_Offset, vbLong, CC_STDCALL, VarPtr(sCaption)
            
            CallFunction_COM IID_Application, IUnknownRelease, vbLong, CC_STDCALL
            
        End If
        
        MsgBox sCaption
        
    End Sub
    
    
    Private Function CallFunction_COM(ByVal InterfacePointer As LongPtr, ByVal VTableOffset As Long, _
                                ByVal FunctionReturnType As Long, _
                                ByVal CallConvention As Long, _
                                ParamArray FunctionParameters() As Variant) As Variant
    
        '// minimal sanity check for these 4 parameters:
        If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
        If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function ' can only be 4 bytes
    
        Dim pIndex As Long, pCount As Long
        Dim vParamPtr() As LongPtr, vParamType() As Integer
        Dim vRtn As Variant, vParams() As Variant
        
        vParams() = FunctionParameters()                    ' copy passed parameters, if any
        pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
        If pCount = 0& Then                                 ' no return value (sub vs function)
            ReDim vParamPtr(0 To 0)
            ReDim vParamType(0 To 0)
        Else
            ReDim vParamPtr(0 To pCount - 1&)               ' need matching array of parameter types
            ReDim vParamType(0 To pCount - 1&)              ' and pointers to the parameters
            For pIndex = 0& To pCount - 1&
                vParamPtr(pIndex) = VarPtr(vParams(pIndex))
                vParamType(pIndex) = VarType(vParams(pIndex))
            Next
        End If
                                                            ' call the function now
        pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, _
                              pCount, vParamType(0), vParamPtr(0), vRtn)
            
        If pIndex = 0& Then                                 ' 0 = S_OK
            CallFunction_COM = vRtn                         ' return result
        Else
            SetLastError pIndex                             ' set error & return Empty
        End If
    
    End Function
    I also tested the code with Early Binding excel (Using New keyword) and it didn't crash !

    I'll need to further explore this subject but I am glad to say that this is a good start for me to gain an insight on how DispCallFunc works to call Interface functions and on COM in general so I would like to thank everyone who has kindly offered me help in this thread and particularly Mr. LaVolpe.

    Regards.

  25. #25
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,638

    Re: DispCallFunc to call COM Property/Method

    You're welcome. Don't forget to mark this thread as resolved: Thread Tools menu near top of your first post.

    P.S. I'm glad the class helped in your understanding of the function.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  26. #26

    Thread Starter
    Member
    Join Date
    Nov 2013
    Posts
    60

    Re: [RESOLVED] DispCallFunc to call COM Property/Method

    Hi,

    Can this technique be also applied for calling InterFace events instead of just for calling Methods/Functions like we have seen so far ?.. This is so that we could hook the events of the newly created excel application in a similar fashion.

    I have noticed that the Excel TypeLib provides all VTable details for event interfaces except the corresponding VTable offsets which are missing ! ... Is there a different mechanism for accessing event interfaces ?!



    Regards.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.