Suppose you have an automation object that needs to implement a callback into caller Visual Basic environment, which can be Scripting Host, Visual Basic for Applications, ASP etc. With all the late binding in VB scripting and so much different C++ code – how to put everything together? There are great choices, let us have three.
On all three samples we add numbers on the way:
300
is the initial argument of the VB caller20
more in the VB callback1
adds the C++ COM class
The three .VBS
files will output 321
is everything goes well. The C++ method name is OuterDo
, while the supposed callback method is InnerDo
.
There are two basic problems on the way:
- We have to somehow pass the callback to COM class
- We have to define function in a way that all they are compatible one with another
Callback in VB Class Function
Straightforwardly, we can pass VB class to have a method called back. While it might be a good idea with VBA where it might be possible to add a reference to type library and have the named callback interface available in scripting environment (VB’s Implements
keyword), this does not work directly in VB Scripting Host. To work this around in .VBS sample we don’t reference IFirstSite
interface, and the automation object First
will access the method by its hardcoded name.
The source code has commented parts which can be used to connect the parts more strictly using IFirstSite
interface.
Class FirstSite
'Implements IFirstSite
Public Function IFirstSite_InnerDo(ByVal A)
IFirstSite_InnerDo = 20 + A
End Function
End Class
Dim First
Set First = WScript.CreateObject("AlaxInfo.VbsCallback.First")
Result = First.OuterDo(300, new FirstSite)
WScript.Echo Result
C++ implementation accepts the call using IDL syntax:
interface IFirst : IDispatch
{
//[id(1)] HRESULT OuterDo([in] LONG nA, [in] IFirstSite* pSite, [out, retval] LONG* pnB);
[id(1)] HRESULT OuterDo([in] LONG nA, [in] IDispatch* pSite, [out, retval] LONG* pnB);
};
In C++ we receive a COM interface of the class, and now we are to locate the callback method by its name using IDispatchEx
interface. Once we succeed with this, we invoke a dispatch interface call.
Callback in VB Function
An alternate option is to pass separate function IDispatch
interface and have it called from C++. compared to using strictly defined interface IFirstSite
this might look like an inferior way, however considering the workarounds we have to put in method above to stay compatible with Scripting Host, this method might look even a bit simpler.
Function InnerDo(ByVal A)
InnerDo = 20 + A
End Function
Dim Second
Set Second = WScript.CreateObject("AlaxInfo.VbsCallback.Second")
Result = Second.OuterDo(300, GetRef("InnerDo"))
WScript.Echo Result
On the caller side, the key to success is GetRef
method that creates a IDispatch
-enabled object from a separate function. C++ will use IDispatch::Invoke
on DISPID
of zero in order for the call to reach the implementation.
Callback through Connection Points
Connection points are standard and well known mechanism to deliver outgoing calls from an automation object, however they are subject to certain constraints:
- late binding is taking place and we have to use
IDispatch::Invoke
to deliver calls, luckily Visual Studio is capable of generating proxy classes for that (noIFirstSite
-like strictly defined and callable interfaces!) - connection points assume that there might be several parties connected to the points/events, and the interface should nicely supports this (no return values!)
Most of the environments have support for connection points on caller side, so this methods is nicely applicable.
Sub Third_InnerDo(ByRef C)
C = C + 20
End Sub
Dim Third
Set Third = WScript.CreateObject("AlaxInfo.VbsCallback.Third", "Third_")
Result = Third.OuterDo(300)
WScript.Echo Result
In C++ there is a proxy class to deliver the event, so implementation is as simple as this:
// IThird
STDMETHOD(OuterDo)(LONG nA, LONG* pnB) throw()
{
ATLASSERT(pnB);
CComVariant vB(nA + 1);
ATLVERIFY(SUCCEEDED(Fire_InnerDo(&vB)));
ATLASSERT(vB.vt == VT_I4);
*pnB = vB.lVal;
return S_OK;
}
Note that [out]
parameters need to be VARIANT
s or otherwise the returned values might get lost on the way.
So we are ready for a test run:
D:\Projects\Alax.Info\Repository-Public\Utilities\VbsCallback\Scripts>cscript First.vbs
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
321
D:\Projects\Alax.Info\Repository-Public\Utilities\VbsCallback\Scripts>cscript Second.vbs
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
321
D:\Projects\Alax.Info\Repository-Public\Utilities\VbsCallback\Scripts>cscript Third.vbs
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
321
Good news, all three methods work well!
Visual C++ .NET 2010 source code [Trac, Subversion] is available from SVN. .VBS
scripts are included.