Monday, March 8, 2010

Writing Bridge Dll betwen ATL COM and TCL

I came up with a requirement where a TCL script load a COM dll and has to invoke the interfaces. As a first time user of TCL script I came to know that with Load Command we can load .dlls (not COM dll) in tcl and can call the methods of the dll. But how to call the interfaces of a COM dll is my question. With reference to my knowledge pool, I prefer to write a bridge dll between the COM dll and the TCL script.

I illustrate this with an example.

Lets consider I have a COM class CMyClass with an interface IMyClass having method MyMethod.

HRESULT MyMethod([in]int A,[in]int B, [out] int *iRetVal);

Creating a Bridge dll

Create a new project Win32 dynamic link library. I am giving a name ‘TclComBridgeDll’.

Add C++ Source and header files with name TclComBridgeDll.cpp and TclComBridgeDll.h

in the stdafx.h add

#include generic\tcl.h

The tch.h is available in the generic folder of the TCL source available at http://prdownloads.sourceforge.net/tcl/tcl858-src.zip http://www.tcl.tk/software/tcltk/download.html

Add the following in TclComBridgeDll.h

#ifdef TCLCOMBRIDGEDLL_EXPORTS
#define TCLCOMBRIDGEDLL_API __declspec(dllexport)
#else
#define TCLCOMBRIDGEDLL_API __declspec(dllimport)
#endif
//To call MyMethod method in the COM dll interface
int CallMyMethod(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
extern “C”
{
TCLCOMBRIDGEDLL_API int Tclsupport_Init(Tcl_Interp *interp);
}

Now in the implementation file TclComBridgeDll.Cpp

Add the following lines.

#include “stdafx.h”
#include “TclComBridgeDll.h”
#import “Debug\MyAtlComDll.dll” rename_namespace (“namespacename”)
using namespace namespacename;

int CallMyMethod(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[])
{
// Check the number of arguments
if (objc != 3)
{
Tcl_WrongNumArgs(interp, 1, objv, “arg arg”);
return TCL_ERROR;
}


size_t length1,length2;
char *v1 = Tcl_GetStringFromObj(objv[1], (int *) &length1);
if (v1[0] == ‘e’)
return TCL_ERROR ;

char *v2 = Tcl_GetStringFromObj(objv[2], (int *) &length2);
if (v2[0] == ‘e’)
return TCL_ERROR ;

long result = 0;

int nA = atoi(v1);
int nB = atoi(v2);

IMyClass *pImycls;
::CoInitialize(NULL);
HRESULT hr=::CoCreateInstance(__uuidof(MyClass), NULL,
CLSCTX_ALL,__uuidof(IMyClass),
(void**)(&pImycls));


try
{
int nTot;
pImycls->MyMethod(nA,nB,&nTot);
pImycls->Release();
result = nTot;
}
catch(…)
{
}

Tcl_SetObjResult(interp, Tcl_NewIntObj(result)) ;
return TCL_OK ;
}
// Note the casing on the _Init function name
TCLCOMBRIDGEDLL_API int Tclsupport_Init(Tcl_Interp *interp)
{
// Link with the stubs library to make the extension as portable as possible
if (Tcl_InitStubs(interp, “8.1″, 0) == NULL)
{ return TCL_ERROR;
}


// Declare which package and version is provided by this C code
if ( Tcl_PkgProvide(interp, “CallMyMethod”, “1.0″) != TCL_OK )
{
return TCL_ERROR ;
}

// Create a command
Tcl_CreateObjCommand(interp, “CallMyMethod”, CallMyMethod, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
return TCL_OK ;
}

Add tclstub86.lib to your linker input. It is avilable in your downloaded tcl858-src.zip

Build the solution, and it will build TclComBridgeDll.dll

Now your Bridge dll is ready to load from the TCL Script file by calling the below statements

Load TclComBridgeDll.dll

#CallMyMethod 10 20