I had a need to provide a mechanism for VB applications to load and invoke a DLL at runtime.
It is possible to call a function in a DLL using the DECLARE statement. The only problem with the DECLARE statement is it requires the DLL to be discoverable by that name. In our case we had to dynamically extract the DLL and create a unique name so the name of the DLL would not be known until runtime, making the DECLARE statement a non-solution.
VB is not my native language, so I searched the Internet to see what solution others were using. I came across two. One of them repurposes VB's COM interface early-binding mechanism and another repurposes the CallWindowProc function. I'll let you decide which one you would prefer to go with:
1. Matthew Curland's approach.
Matthew Curland wrote an article in Visual Studio magazine called Call Function Pointers which used to be available for download here:
http://www.fawcette.com/archives/listissue.asp?pubID=1&MagIssueId=265#
Update: That link appears to be dead. I can now only find the code in Curland's book http://www.powervb.com. If anyone finds the sample code or article available online, please share a link.
This technique worked well enough, but I encountered a couple of problems when calling functions that themselves returned failure HRESULT codes. The problem is that VB will then QI for additional interfaces to examine the error info, and Curland's approach would blindly let QI return S_OK for any query, leading to a v-table call for a v-table entry that doesn't exist.
Some sample code that will fix that particular problem is included below.
2. Arakidy Olovyannikov's approach.
When I went to write this blog I found another approach which seems more elegant. I haven't tested it out since I already have a working solution, but it looks simple and sound. The only thing it lacks is explanatory documentation, but really it's pretty straightforward. The sample code can be found here: http://www.freevbcode.com/ShowCode.Asp?ID=1863
THE FIX TO CURLAND'S APPROACH
Here is some sample code that will fix the problem where Curland's approach can crash if the called function returns an error:
Public Const E_NOINTERFACE = &H80004002
Private Sub InitializeInterfaces()
' 00000000-0000-0000-C000-000000000046
With IID_IUnknown
.Data1 = &H0
.Data2 = &H0
.Data3 = &H0
.Data4(0) = &HC0
.Data4(1) = &H0
.Data4(2) = &H0
.Data4(3) = &H0
.Data4(4) = &H0
.Data4(5) = &H0
.Data4(6) = &H0
.Data4(7) = &H46
End With
' C4AAECE0-6842-4e87-AB7A-93E93C50EDEE
With IID_IWhatever
.Data1 = &HC4AAECE0
.Data2 = &H6842
.Data3 = &H4E87
.Data4(0) = &HAB
.Data4(1) = &H7A
.Data4(2) = &H93
.Data4(3) = &HE9
.Data4(4) = &H3C
.Data4(5) = &H50
.Data4(6) = &HED
.Data4(7) = &HEE
End With
bInitializedInterfaces = True
End Sub
Private Function IsEqualIID(ByRef guid1 As GUID, ByRef guid2 As GUID) As Boolean
If (guid1.Data1 = guid2.Data1 And _
guid1.Data2 = guid2.Data2 And _
guid1.Data3 = guid2.Data3 And _
guid1.Data4(0) = guid2.Data4(0) And _
guid1.Data4(1) = guid2.Data4(1) And _
guid1.Data4(2) = guid2.Data4(2) And _
guid1.Data4(3) = guid2.Data4(3) And _
guid1.Data4(4) = guid2.Data4(4) And _
guid1.Data4(5) = guid2.Data4(5) And _
guid1.Data4(6) = guid2.Data4(6) And _
guid1.Data4(7) = guid2.Data4(7)) Then
IsEqualIID = True
Else
IsEqualIID = False
End If
End Function
Private Function IsKnownInterface(ByRef guidIface As GUID) As Boolean
If Not bInitializedInterfaces Then
InitializeInterfaces
End If
If (IsEqualIID(guidIface, IID_IUnknown) Or _
IsEqualIID(guidIface, IID_IWhatever)) Then
IsKnownInterface = True
Else
IsKnownInterface = False
End If
End Function
Private Function QueryInterface(ByVal This As Long, _
ByRef riid As GUID, pvObj As Long) As Long
' Matthew Curland has one key problem in his delegator implementation that
' we need to work around. He returns success from every QueryInterface call.
' If a function call is defined to return an HRESULT, then VB will call
' QueryInterface(ISupportErrorInfo) on that interface. Curland's implementation
' will return your original interface and VB will try to call it as if it
' were ISupportErrorInfo. We avoid this by making sure the interface is known.
If IsKnownInterface(riid) Then
pvObj = This
QueryInterface = 0
Else
QueryInterface = E_NOINTERFACE
End If
End Function