COM, Delphi and Python

I decided to write a command-line COM library registration tool, choosing C++ to flex some language muscles. This tool is needed to expand my Python+Excel -based automated calibration framework.

The first hurdle was char * style versus wchar_t * style character strings. What a nightmare! There are plenty of string types in C++, and plenty seem to work well, but ONLY on the condition that you stick with that one type and don’t mix. The standard output stream cout doesn’t support wchar_t, for example.

The thing that had me really stumped for a while was that the functions LoadTypeLib() and RegisterTypeLib() were returning S_OK but nothing was being changed in the Registry. Searching in some despair in the \Delphi5\Demos directory, I came across a project called TRegSvr, which did pretty much what I wanted. That worked first time – both reg and unreg – so what was different? Automatic calls to AddRef() and Release() in the Delphi code, of course. Adding in a call to pTypeLib->Release(); fixed the problem, although it is slightly baffling why NOT releasing the interface would cause the type lib NOT to be registered. Hmm.

Then I delved into Python’s COM support again, missing (until rather late into the process) this crucial information from Mark Hammond’s Python Programming on Win32:

…Python can’t support arbitrary COM interfaces; the pythoncom module must have built-in support for the interface…

Urg. In essence, even with the COM extensions, Python can only bind to IDispatch-supporting COM objects. Why the makepy utility wasn’t extended to all interfaces (given a suitable type library) I dunno. This, compounded by Delphi’s obfuscation of its COM support (e.g. an IDispatch-based object is actually an ‘Automation Object’ not just ‘COM Object’) made things a little slooow, but hey. Oh, and that’s forgeting that, in my installation, the \gen_py folder under win32com was missing, so that the generated Python file was dumped into %TEMP%.

Here is the COM reg/unreg program as it currently stands in all its ugliness:

// RegCOMSrv.cpp : Defines the entry point for the console application.
 //
 // An application to register COM Servers and their type libraries
 //
 // At the moment, only works with EXEs, not DLLs, but see:
 // ms-help://MS.VSCC/MS.MSDNVS/com/register.htm
 //
#include "stdafx.h"
 #include
 #include
 #include
 #include
using namespace std;
/*
 CComHelper
Used to automate and guarantee the COM initialisation gubbins.
 */
 class CComHelper {
 public:
 CComHelper() {
 HRESULT hr = CoInitialize(NULL); // for some reason, CoInitializeEx() fails
 if (!SUCCEEDED(hr)) {
 throw "CoInitialize failed!";
 }
 cout << "Initialised COM" << endl;
 }
~CComHelper() {
 CoUninitialize();
 cout << "Uninitialised COM" << endl;
 }
 };
static bool REGISTER = true;
static int INDEX_SWITCH = 1;
 static int INDEX_APP = 2;
 static int INDEX_TYPELIB = 3;
int _tmain(int argc, _TCHAR* argv[])
 {
 cout << "RegCOMSvr.exe - Copyright 2004 (c) Matthew P Skelton"
 << endl << endl;
if (argc < 4) {
 cout << "Usage: RegCOMSrv " << endl << "e.g. 'RegComSrv /r MyApp.exe MyApp.tlb'" << endl << " 'RegComSrv /u MyApp.exe MyApp.tlb'" << endl << "where /r Registers and /u Unregisters both COM server and TypeLib" << endl;
 return 1;
 }
CComHelper _com_helper; // destroyed at end of main()
 bool mode;
 ITypeLib** ppTypeLib = NULL;
 ITypeLib* pTypeLib = NULL;
 TLIBATTR* pTLibAttr = NULL;
wchar_t theApp[255];
 wchar_t theTypeLib[255];
wchar_t* w_theApp = theApp;
 wchar_t* w_theTypeLib = theTypeLib;
//OLECHAR o_theTypeLib[255];
 //LPCOLESTR po_theTypeLib = o_theTypeLib;
//MultiByteToWideChar(CP_ACP, 0, argv[INDEX_APP], strlen(argv[INDEX_APP]), theApp, 255);
 mbstowcs(theApp, argv[INDEX_APP], 255);
 //theApp = argv[INDEX_APP];
 //wstring s_theApp(theApp, wcslen(theApp));
 string s_theApp = argv[INDEX_APP];
 cout << "App: " << s_theApp.c_str() << endl;
//MultiByteToWideChar(CP_ACP, 0, argv[INDEX_TYPELIB], strlen(argv[INDEX_TYPELIB]), theTypeLib, 255);
 mbstowcs(theTypeLib, argv[INDEX_TYPELIB], 255);
 //theTypeLib = argv[INDEX_TYPELIB];
 //wstring s_theTypeLib(theTypeLib, wcslen(theTypeLib));
 string s_theTypeLib = argv[INDEX_TYPELIB];
 cout << "TypeLib: " << s_theTypeLib.c_str() << endl;
//wcscpy(o_theTypeLib, theTypeLib);
if (0 == strcmp(argv[INDEX_SWITCH], "/r")) {
 mode = REGISTER;
 cout << "Registering..." << endl;
 }
 else {
 mode = !REGISTER;
 cout << "Unregistering..." <Release() before app exits !!!
 if (SUCCEEDED(hr)){
 cout << "LoadTypeLib() succeeded" << endl;
 //cout << **ppTypeLib
 }
 else {
 cout << "LoadTypeLib() failed with result: " << hex << hr <Release();
 return 2;
 }
if (mode) {
 //HRESULT hr = LoadTypeLibEx(o_theTypeLib, REGKIND_REGISTER, ppTypeLib);
cout << "Contents of 'o_theTypeLib' as passed to LoadTypeLib" << endl;
 int max_chars = wcslen(theTypeLib);
 for (int i=0;i<max_chars;i++) {
 cout << (char)(theTypeLib[i]);
 }
 cout << endl << endl;
hr = RegisterTypeLib(pTypeLib, theTypeLib, NULL);
 if (SUCCEEDED(hr)){
 cout << "RegisterTypeLib() succeeded" << endl;
 //cout << **ppTypeLib
 }
 else {
 cout << "RegisterTypeLib() failed with result: " << hex << hr <Release();
 return 4;
 }
/*
 hr = LoadTypeLibEx(theTypeLib, REGKIND_REGISTER, ppTypeLib);
 if (SUCCEEDED(hr)){
 cout << "LoadTypeLibEx() succeeded" << endl;
 //cout << **ppTypeLib
 }
 else {
 cout << "LoadTypeLibEx() failed with result: " << hex << hr << endl;
 return 2;
 }
 */
 }
 else {
 //cout << "UnRegisterTypeLib() rather tricky. Not unregistering at this time™." <GetLibAttr(&pTLibAttr);
 // __try {
 if (SUCCEEDED(hr)) {
 hr = UnRegisterTypeLib(pTLibAttr->guid, pTLibAttr->wMajorVerNum, pTLibAttr->wMinorVerNum, pTLibAttr->lcid, pTLibAttr->syskind);
 if (SUCCEEDED(hr)) {
 cout << "UnRegisterTypeLib() succeeded" << endl;
 }
 else {
 cout << "UnRegisterTypeLib() failed with code" << hex << hr <Release();
 return 7;
 }
 }
 else {
 cout <GetLibAttr() failed" <Release();
 return 6;
 }
 // }
 // __finally {
 pTypeLib->ReleaseTLibAttr(pTLibAttr);
 // }
 }
 /*
 NOTE: These calls to pTypeLib->Release are ESSENTIAL for this app to work properly
It seems that if we don't explicitly call it, COM does not update the registry after the
 program exits.
 */
 pTypeLib->Release();
int error;
 char szEXE[MAX_PATH];
wsprintf(
 szEXE,
 "%s %s",
 s_theApp.c_str(),
 mode ? "/REGSERVER" : "/UNREGSERVER");
cout << "Command line is: " << szEXE << endl;
error = WinExec(szEXE, SW_HIDE);
 if (error < 32) {
 cout << "(Un)RegServer failed." << endl;
 return 3;
 }
 else {
 cout << "(Un)RegServer succeeded." <

Another thing which was confusing earlier on was that VBA can create IUnknown-based objects, so long as the Reference (to the type library) is added first. It resolves the ProgID to the actual interface definition. Non-IDispatch objects must be typed correctly, thus:

Option Explicit
 ' seems like we have to declare this explicitly and include
 ' a Reference to it - this is because it is not IDispatch-based
 Dim obj As TestPythonCOM.TestPython ' <<

IDispatch-based objects can be declared with just Dim obj As Object. Anyhow, the solution is to create an Automation-style (i.e. IDispatch) enabled COM object, which will be controllable by both Excel and Python. COM Events can be hooked by Python (using win32com.client.DispatchWithEvents()), so the operator can hit a button in Excel, firing an event in the application/COM Server, which triggers code in Python, which then updates Excel! Eventually, the manual interventaion will be unneccesary, and the sched event scheduler class can be used instead, allowing Python to snag the data from the app when it sees fit.

For the VBA side of things, as related to the Delphi server, there is an excellent site at http://www.gekko-software.nl/Delphi/, particularly the article entitled Firing events. It shows how to declare a WithEvents class instance which will be used to sink events from the server.

Join the discussion...

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.