VBA, VBScript, JavaScript and DCOM

VBA, VBScript, JavaScript, are programming scripts to write and automate tasks in Office and Windows operating systems. These have component support for doing all sorts of works provided by Windows and Office. Some services which are not available or some services which are custom needed by a user cannot be fulfilled. These types of situations call for a custom made COM server. The user requires to implement the routine in the server part and thus that function can be called by a script file.

We will be building a custom made COM/DCOM server using ATL C++ framework. We will be using this ATL framework since this is much easy compared to implementing in raw C++ files.

These are prerequisite knowledge-base and required software tools you must possess before reading this article.

  • OOP programming in C++
  • Knowledge of component software (COM/DCOM)
  • Knowledge of VBA, VBScript, JavaScript (any one)
  • Visual Studio 6.0 or VS community (free) installed

DCOM server DLL using ATL C++

We will open Visual C++ and start with a new project. Visual Studio provides wizard-based project configuration and creates all the necessary files automatically as needed by the project. It takes less effort and time so it is a better choice while creating a project. We should select "ATL COM App wizard" and provide the name of our project.

atl new project

Our component will be a small calculator so we have given the name "MyATLCalc". We are creating a DCOM server as a dynamic link library executable.

atl com dll project

The wizard will finish creating all the necessary files. Our output executable file will be MyATLCalc.dll. MyATLCalc.cpp is our C++ implementation file for our DLL. MyATLCalc.IDL is the interface definition file where we will define the functions/interfaces of the calculator which will be used by our scripting language(client). MyATLCalc.mk will generate stub/proxy files for DCOM operations.

atl com dll files

At this point, VC++ editor will generate all the files and necessary functions of the DCOM DLL export function.

atl com dll functions

Our DCOM component is ready but it does not have any interface. We will add our Calculator interface using the menu option New ATL Object.

atl new object add

We will select the type as Simple Object and continue.

atl add object simple object

We have given a short name "MyCalc" as the name of the object. This will add an interface in IDL file and the editor will create a C++ class file as "CMyCalc". MyCalc is the interface and CMyCalc C++ class is the implementation of this interface.

atl com dll add object name

Now we add our first interface function as AddInt(). This function takes two integer x, y as inputs and returns the value as an integer.

atl object add method

atl add method prototype

/* Interface */
interface IMyCalc : IDispatch
{
  [id(1), helpstring("method AddInt")]
    HRESULT AddInt([in] int x, [in] int y, [out,retval] int *ret );
};

/* C++ body function */
STDMETHODIMP CMyCalc::AddInt(int x, int y, int *ret)
{
  /* TODO: Add your implementation code here */
  return S_OK;
}

Interface definition file

IMyCalc::AddInt will be implemented in C++ and it will accessed by script.

/* MyATLCalc.idl : IDL source for MyATLCalc.dll */

/* This file will be processed by the MIDL tool to */
/* produce the type library (MyATLCalc.tlb) and marshalling code. */

import "oaidl.idl";
import "ocidl.idl";
  [
    object,
    uuid(4804BE74-9489-4C31-A02C-B9C864A38E13),
    dual,
    helpstring("IMyCalc Interface"),
    pointer_default(unique)
  ]
  interface IMyCalc : IDispatch
  {
    [id(1), helpstring("method AddInt")]
    HRESULT AddInt([in] int x, [in] int y, [out,retval] int *ret );
  };

[
  uuid(4E8F5D01-17ED-41DE-A1A4-25F8088E1EA3),
  version(1.0),
  helpstring("MyATLCalc 1.0 Type Library")
]
library MYATLCALCLib
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");

  [
    uuid(0BF9611D-2CEF-4182-875C-5D63DE028529),
    helpstring("MyCalc Class")
  ]
  coclass MyCalc
  {
    [default] interface IMyCalc;
  };
};

C++ header prototype

CMyCalc is the C++ class file of the interface IMyCalc

/* CMyCalc */
class ATL_NO_VTABLE CMyCalc : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CMyCalc, &CLSID_MyCalc>,
  public IDispatchImpl<IMyCalc, &IID_IMyCalc, &LIBID_MYATLCALCLib>
{
public:
  CMyCalc()
  {
  }

DECLARE_REGISTRY_RESOURCEID(IDR_MYCALC)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CMyCalc)
  COM_INTERFACE_ENTRY(IMyCalc)
  COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

/* IMyCalc */
public:
  STDMETHOD(AddInt)(/*[in]*/ int x,
                  /*[in]*/ int y,
                  /*[out,retval]*/ int *ret);
};

C++ implementation

CMyCalc::AddInt() function body implemented here. The implementation of this function is just to add these two inputs and return the value.

/* MyCalc.cpp : Implementation of CMyCalc */
#include "stdafx.h"
#include "MyATLCalc.h"
#include "MyCalc.h"

/* CMyCalc */

STDMETHODIMP CMyCalc::AddInt(int x, int y, int *ret)
{
  *ret = x + y;
  return S_OK;
}

C++ implementation of DCOM DLL

#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "MyATLCalc.h"

#include "MyATLCalc_i.c"
#include "MyCalc.h"


CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MyCalc, CMyCalc)
END_OBJECT_MAP()

/* DLL Entry Point */
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_MYATLCALCLib);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;
}

/* Used to determine whether the DLL can be unloaded by OLE */
STDAPI DllCanUnloadNow(void)
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/* Returns a class factory to create an object of the requested type */
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

/* DllRegisterServer - Adds entries to the system registry */
STDAPI DllRegisterServer(void)
{
    /* registers object, typelib and all interfaces in typelib */
    return _Module.RegisterServer(TRUE);
}

/* DllUnregisterServer - Removes entries from the system registry */
STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}

Deploy DCOM DLL

VC++ make (.mk) file automatically register the component after a successful build. So an automatic installation will be done on the development system.

Component DLL is different from the normal DLL executables. Component DLL file cannot be used until it is registered with the operating system's registry. We need to copy the C runtime dynamic library file i.e. msvcrtxx.dll to path location. It is appropriate to copy this file to \Windows\System32.

The installation or registration of the DLL can be done using regsvr32.exe. We need to copy this component DLL to a path and execute regsvr32.exe along with this dll as an argument(example regsvr32 MyATLCalc.dll).

register COM DCOM server using regsvr32

The uninstallation or deregistration of the DLL can be done using regsvr32.exe with /u argument. (example regsvr32 /u MyATLCalc.dll).

unregister COM DCOM server using regsvr32

There are installer softwares available to do this and Installshield is one such software that comes with VS. This entire installation and uninstallation process can be done with Installshield.

DCOM Client using VB/VBA, VB Script, Javascript

Our deployment system will be ready to use once the installation is complete. Now we can use any of your desired scripting language to access this C++ DCOM server. We are calling this AddInt C++ interface function and printing the result in a message box.

call c++ from vb vba java script vb script

DCOM Client using Javascript Script

var calc, shell
calc = new ActiveXObject ("MyATLCalc.MyCalc");
shell = new ActiveXObject ("WScript.Shell");
result = calc.AddInt( 1, 2 )
shell.Popup ("Add 1 + 2 =" + result, 0, "C++ ATL Calc");
calc = null;
shell = null;
CMD Execute>c:\windows\SysWOW64\wscript test_calc.js

DCOM Client using VBA/VB Script

Dim calc 
Set calc = CreateObject ("MyATLCalc.MyCalc")
result = calc.AddInt( 1, 2 )
MsgBox "Add 1 + 2 =" & result, vbOKOnly, "C++ ATL Calc"
Set obj=nothing
CMD Execute>c:\windows\SysWOW64\wscript test_calc.vbs

DCOM Client using Visual Basic

Private Sub Form_Load()
Dim x As Integer
Dim y As Integer
Dim result As Integer
= 1
= 2
Set calc = CreateObject("MyATLCalc.MyCalc")
result = calc.AddInt(x, y)
MsgBox "Add 1 + 2 =" & CStr(result), vbOKOnly, "C++ ATL Calc"
Set calc = Nothing
End Sub

What is next?

What is variant type in script? How arrays are implemented? How enum types are supported? What is callback event to script? How to implement completion event for long task?

About our authors: Team EQA

You have viewed 1 page out of 67. Your COM/DCOM learning is 0.00% complete. Login to check your learning progress.

#