{"id":1381,"date":"2012-08-11T14:19:35","date_gmt":"2012-08-11T12:19:35","guid":{"rendered":"https:\/\/alax.info\/blog\/?p=1381"},"modified":"2012-08-11T18:37:31","modified_gmt":"2012-08-11T16:37:31","slug":"three-ways-to-implement-vbscript-vb6-vba-callback-from-catl-class","status":"publish","type":"post","link":"https:\/\/alax.info\/blog\/1381","title":{"rendered":"Three ways to implement VBScript (VB6, VBA) callback from C++\/ATL class"},"content":{"rendered":"<p>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 &#8211; how to put everything together? There are great choices, let us have three.<\/p>\n<p>On all three samples we add numbers on the way:<\/p>\n<ul>\n<li><code>300<\/code> is the initial argument of the VB caller<\/li>\n<li><code>20<\/code> more in the VB callback<\/li>\n<li><code>1<\/code> adds the C++ COM class<\/li>\n<\/ul>\n<p>The three <code>.VBS<\/code> files will output <code>321<\/code> is everything goes well. The C++ method name is <code>OuterDo<\/code>, while the supposed callback method is <code>InnerDo<\/code>.<\/p>\n<p>There are two basic problems on the way:<\/p>\n<ol>\n<li>We have to somehow pass the callback to COM class<\/li>\n<li>We have to define function in a way that all they are compatible one with another<\/li>\n<\/ol>\n<h3>Callback in VB Class Function<\/h3>\n<p>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&#8217;s <code>Implements<\/code> keyword), this does not work directly in VB Scripting Host. To work this around in .VBS sample we don&#8217;t reference <code>IFirstSite<\/code> interface, and the automation object <code>First<\/code> will access the method by its hardcoded name.<\/p>\n<p>The source code has commented parts which can be used to connect the parts more strictly using <code>IFirstSite<\/code> interface.<\/p>\n<pre><code>Class FirstSite \n  'Implements IFirstSite\n  Public Function IFirstSite_InnerDo(ByVal A)\n    IFirstSite_InnerDo = 20 + A\n  End Function\nEnd Class\n\nDim First\nSet First = WScript.CreateObject(\"AlaxInfo.VbsCallback.First\")\nResult = First.OuterDo(300, new FirstSite)\nWScript.Echo Result\n<\/code><\/pre>\n<p>C++ implementation accepts the call using IDL syntax:<\/p>\n<pre><code>interface IFirst : IDispatch\n{\n    \/\/[id(1)] HRESULT OuterDo([in] LONG nA, [in] IFirstSite* pSite, [out, retval] LONG* pnB);\n    [id(1)] HRESULT OuterDo([in] LONG nA, [in] IDispatch* pSite, [out, retval] LONG* pnB);\n};\n<\/code><\/pre>\n<p>In C++ we receive a COM interface of the class, and now we are to locate the callback method by its name using <code>IDispatchEx<\/code> interface. Once we succeed with this, we invoke a dispatch interface call.<\/p>\n<h3>Callback in VB Function<\/h3>\n<p>An alternate option is to pass separate function <code>IDispatch<\/code> interface and have it called from C++. compared to using strictly defined interface <code>IFirstSite<\/code> 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.<\/p>\n<pre><code>Function InnerDo(ByVal A)\n    InnerDo = 20 + A\nEnd Function\n\nDim Second\nSet Second = WScript.CreateObject(\"AlaxInfo.VbsCallback.Second\")\nResult = Second.OuterDo(300, GetRef(\"InnerDo\"))\nWScript.Echo Result\n<\/code><\/pre>\n<p>On the caller side, the key to success is <code>GetRef<\/code> method that creates a <code>IDispatch<\/code>-enabled object from a separate function. C++ will use <code>IDispatch::Invoke<\/code> on <code>DISPID<\/code> of zero in order for the call to reach the implementation.<\/p>\n<h3>Callback through Connection Points<\/h3>\n<p>Connection points are standard and well known mechanism to deliver outgoing calls from an automation object, however they are subject to certain constraints:<\/p>\n<ul>\n<li>late binding is taking place and we have to use <code>IDispatch::Invoke<\/code> to deliver calls, luckily Visual Studio is capable of generating proxy classes for that (no <code>IFirstSite<\/code>-like strictly defined and callable interfaces!)<\/li>\n<li>connection points assume that there might be several parties connected to the points\/events, and the interface should nicely supports this (no return values!)<\/li>\n<\/ul>\n<p>Most of the environments have support for connection points on caller side, so this methods is nicely applicable.<\/p>\n<pre><code>Sub Third_InnerDo(ByRef C)\n  C = C + 20\nEnd Sub\n\nDim Third\nSet Third = WScript.CreateObject(\"AlaxInfo.VbsCallback.Third\", \"Third_\")\nResult = Third.OuterDo(300)\nWScript.Echo Result\n<\/code><\/pre>\n<p>In C++ there is a proxy class to deliver the event, so implementation is as simple as this:<\/p>\n<pre><code>\/\/ IThird\n    STDMETHOD(OuterDo)(LONG nA, LONG* pnB) throw()\n    {\n        ATLASSERT(pnB);\n        CComVariant vB(nA + 1);\n        ATLVERIFY(SUCCEEDED(Fire_InnerDo(&amp;vB)));\n        ATLASSERT(vB.vt == VT_I4);\n        *pnB = vB.lVal;\n        return S_OK;\n    }\n<\/code><\/pre>\n<p>Note that <code>[out]<\/code> parameters need to be <code>VARIANT<\/code>s or otherwise the returned values might get lost on the way.<\/p>\n<p><!-- more --><\/p>\n<p>So we are ready for a test run:<\/p>\n<pre><code>D:\\Projects\\Alax.Info\\Repository-Public\\Utilities\\VbsCallback\\Scripts&gt;cscript First.vbs\nMicrosoft (R) Windows Script Host Version 5.8\nCopyright (C) Microsoft Corporation. All rights reserved.\n\n321\n\nD:\\Projects\\Alax.Info\\Repository-Public\\Utilities\\VbsCallback\\Scripts&gt;cscript Second.vbs\nMicrosoft (R) Windows Script Host Version 5.8\nCopyright (C) Microsoft Corporation. All rights reserved.\n\n321\n\nD:\\Projects\\Alax.Info\\Repository-Public\\Utilities\\VbsCallback\\Scripts&gt;cscript Third.vbs\nMicrosoft (R) Windows Script Host Version 5.8\nCopyright (C) Microsoft Corporation. All rights reserved.\n\n321\n<\/code><\/pre>\n<p>Good news, all three methods work well!<\/p>\n<p>Visual C++ .NET 2010 source code [<a href=\"https:\/\/www.alax.info\/trac\/public\/browser\/trunk\/Utilities\/VbsCallback\">Trac<\/a>, <a href=\"https:\/\/www.alax.info\/svn\/public\/trunk\/Utilities\/VbsCallback\/\">Subversion<\/a>] is available from SVN. <code>.VBS<\/code> scripts <a href=\"https:\/\/www.alax.info\/trac\/public\/browser\/trunk\/Utilities\/VbsCallback\/Scripts\">are included<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &#8211; how to put everything together? There are great choices, let us have&hellip; <\/p>\n<p><a class=\"moretag\" href=\"https:\/\/alax.info\/blog\/1381\">Read the full article<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,13],"tags":[487,198,38,95,431,428,429,430],"class_list":["post-1381","post","type-post","status-publish","format-standard","hentry","category-atl","category-source","tag-atl","tag-automation","tag-c","tag-com","tag-scripting","tag-vb","tag-vba","tag-vbs"],"_links":{"self":[{"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/posts\/1381","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/comments?post=1381"}],"version-history":[{"count":0,"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/posts\/1381\/revisions"}],"wp:attachment":[{"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/media?parent=1381"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/categories?post=1381"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/alax.info\/blog\/wp-json\/wp\/v2\/tags?post=1381"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}