headlogo

Erzeugen einer Active-X-DLL (COM) mit C++

Dieses Beispiel wurde unter VisualStudio 2008 realisiert. Der Sourcecode besteht aus 2 Files:

  • dllmakermain.cpp (allgemeine gültige Codes)
  • costumized.cpp (veränderbar für Anpassungen)
  • man könnte beide Files natürlich zusammenführen (werden sie über die Präprozessor-Routinen sowieso), jedoch ist das nicht so komfortabel bei späteren Code-Erweiterungen

    Eine gute Einführung in das Thema ActiveX /COM habe ich in einer Diplomarbeit im Internet gefunden (hier). Gratulation.

    C++ ist eine schwierige Sprache mit vielen Fallstricken: Selbst wenn der Code korrekt geschrieben und der Compiler nicht mehr reklamiert, muss das nicht automatisch heissen, dass die dll korrekt kompiliert wurde (selber erfahren). Ich rate dem Programmierer, 2-3 Zeilen Code zu schreiben und danach eine komplette Testroutine laufen zu lassen

    Im Vergleich zu einem Java-Applett steht der Aufwand zum Ertrag in keinem Verhältnis. Folgender Sourcecode stellt das absolute Minimum dar.

    Step 1: dll erzeugen

    Wir kompillieren 2 Files: "dllmakermain.cpp"

    //unter VS2008:
    //1. neues projekt -> visual c++ -> win32 projekt
    //2. Assistent unter Anwendungseinstellungen
    //											= dll
    //											= leeres Projekt
    //3. hinzufügen -> vorhandenes Element -> dllmakermain.cpp
    //										-> costumized.cpp
    //										-> debugging.cpp (fakultativ)
    //4. hinzufügen -> neues Element -> def-Datei -> Name "dllmaker.def"
    //5. copypaste ->
    //				LIBRARY	"dllmaker"
    //				EXPORTS
    //				DllGetClassObject private   
    //				DllRegisterServer private
    //				DllUnregisterServer private
    //				DllCanUnloadNow private
    
    //folgender Code ist universell und muss nicht geändert werden:
    
    #include <windows.h> //standart
    #include <tchar.h>  //zur Stringkonvertierung (__T) -> define __T(x) L ## x
    #include <stdio.h>
    
    
    //struct {long, short, short, 8x uchar array}
    IID idispID = {0x00020400,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};//immer dieselbe ID
    IID iunownID = {0x00000000,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};//immer dieselbe ID
    IID ifactID = {0x00000001,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}}; //immer dieselbe id
    
    LPCTSTR concat(LPCTSTR* S) //const wchar-T* -concatenation-Helper
    {int len = wcslen(S[0])+wcslen(S[1])+wcslen(S[2])+1; wchar_t* res = new wchar_t[len];_snwprintf(res,len,__T("%s%s%s"),S[0],S[1],S[2]);LPCTSTR r = res; return r;}
    
    
    #define GUID_SECTION
    #include "costumized.cpp"
    #undef GUID_SECTION
    
    
    #define COSTUME_CODE
    #include "costumized.cpp"
    #undef COSTUME_CODE
    
    //#define DEBUGGING
    //#include "debugging.cpp"
    
    //ist jene UniversalKlasse mit der Javascript arbeiten wird
    class TheObject : IDispatch{
    		ULONG count;
    		CLSID objectID;
    		CLSID baseID;
    
    	public:
    		TheObject(void){}
    		~TheObject(void){}
    		TheObject(CLSID oid1,CLSID oid2){objectID = oid1; baseID = oid2;count = 0;}
    		//IUnknown
    		virtual STDMETHODIMP QueryInterface(REFIID riid, void** ppv){
    	if(riid == objectID || riid == baseID)
    		{
    			*ppv =  static_cast<IDispatch*>(this);
    			static_cast<IDispatch*>(*ppv) -> AddRef();
    		}
    		else
    		{
    			*ppv = 0;
    			return E_NOINTERFACE;
    		}
    
    		return S_OK;
    		}
    		virtual STDMETHODIMP_(ULONG) AddRef(void){ return ++count;}
    		virtual STDMETHODIMP_(ULONG) Release(void){count--; if (count == 0) delete this; return count;}
    
    		//IDispatch
    		virtual STDMETHODIMP GetTypeInfoCount(UINT* i){*i = 0; return S_OK;}
    		virtual STDMETHODIMP GetTypeInfo(UINT,LCID,ITypeInfo **){return E_NOTIMPL;}
    
    		virtual STDMETHODIMP GetIDsOfNames(const IID & iidNull ,LPOLESTR * methodes,UINT anzahlMethoden,LCID winstandart ,DISPID * memberIDs){
    
    
    			#define METHODE_ID_SECTION
    			#include "costumized.cpp"
    			#undef METHODE_ID_SECTION
    
    			if (arraySize >= anzahlMethoden)
    			{
    				for (int i = 0; i < anzahlMethoden; i++)
    				{
    					for (int j = 0; j < arraySize; j++)
    					{
    						if (wcscmp(methodeList[j].theName,methodes[i])==0)
    						{
    							memberIDs[i] = methodeList[j].theID;
    							break;
    						}
    					}
    				}
    
    				return S_OK;
    			}else 
    				return E_INVALIDARG;
    
    		}
    		virtual STDMETHODIMP Invoke(DISPID memberID,const IID & iidNull,LCID winstandart,WORD methodeType,DISPPARAMS * args,VARIANT * returnVal,EXCEPINFO * null1 ,UINT * null2){
    
    
    			switch (memberID)
    			{
    
    			#define INVOKE_SECTION
    			#include "costumized.cpp"
    			#undef INVOKE_SECTION
    			}
    			return E_INVALIDARG;
    		
    		}
    };
    
    
    //dient dazu die (richtige) Universal-Klasse zu instanzieren
    class ObjektManager : public IClassFactory
    {
    	CLSID factoryID;
    	CLSID objectID;
    	CLSID baseID;
    	ULONG count;
    public:
    	ObjektManager(void){} //zwingende Implementationen
    	~ObjektManager(void){}//ICLASSFactory erbt noch von IUnnown
    	ObjektManager(CLSID fid1,CLSID fid2,CLSID fid3){factoryID = fid1; baseID = fid2; objectID = fid3; count = 0;}
    
    	virtual STDMETHODIMP_(ULONG) AddRef(void){return ++count;}
    
    	virtual STDMETHODIMP_(ULONG) Release(void){
    		count--; 
    		if (count == 0) delete this; 
    		return count;
    	}
    	virtual STDMETHODIMP LockServer(BOOL bLock) {return  E_NOTIMPL; }
    
    	virtual STDMETHODIMP  CreateInstance(IUnknown *pUnkOuter, REFIID riid, LPVOID* ppv)
    	{
    		if(pUnkOuter !=NULL)
    			return CLASS_E_NOAGGREGATION;
    
    		TheObject *hwp = new TheObject(objectID,baseID);
    		if(hwp == NULL)	return E_OUTOFMEMORY;
    
    		return hwp->QueryInterface(riid, ppv);
    	}
    
    	virtual STDMETHODIMP QueryInterface(REFIID riid, void **ppv){
    		if(riid == factoryID || riid == baseID)
    		{
    			*ppv =  static_cast<IClassFactory*>(this);
    			static_cast<IClassFactory*>(*ppv) -> AddRef();
    		}
    		else
    		{
    			*ppv = 0;
    			return E_NOINTERFACE;
    		}
    
    		return S_OK;
    	}
    };
    
    // Sektion dll-API
    // dient dazu, um die DLL in die Registry einzutragen und den Objekt-Manager zu instanzieren
    
    
    HKEY hkey = NULL;
    #define TNUL __T("")
    //#define LOCAL_PFAD __T("C:\\Dokumente und Einstellungen\\mrcoffee\\Desktop\\fürexperimente\\")
    #define EXPL_PFAD __T("C:\\WINDOWS\\Downloaded Program Files\\")
    
    struct{
        BOOL isAkey;		//zum Unterscheiden ob key oder Value
    	HKEY hotkey;		//rootkey
    	LPCTSTR key[3];		//Registry Key Name (dreiteiliger String)
    	LPCTSTR valName[3];	//Registry Value Name
    	LPCTSTR val[3];		//Value des Value
    } regEntries []= {
    	{TRUE,HKEY_CLASSES_ROOT,{PROGID,__T("\\CLSID"),TNUL},{0,0,0},{0,0,0}},
    	{FALSE,0,{0,0,0},{TNUL,TNUL,TNUL},{CLASSID,TNUL,TNUL}},
    
    	{TRUE,HKEY_CLASSES_ROOT,{__T("CLSID\\"),CLASSID,__T("\\InprocServer32")},{0,0,0},{0,0,0}},
    	{FALSE,0,{0,0,0},{TNUL,TNUL,TNUL},{EXPL_PFAD,DLLNAME,TNUL}},
    	{FALSE,0,{0,0,0},{__T("ThreadingModel"),TNUL,TNUL},{__T("Apartment"),TNUL,TNUL}},
    
    	{TRUE,HKEY_LOCAL_MACHINE,{__T("SOFTWARE\\Classes\\"),PROGID,__T("\\CLSID")},{0,0,0},{0,0,0}},
    	{FALSE,0,{0,0,0},{TNUL,TNUL,TNUL},{CLASSID,TNUL,TNUL}},
    
    	{TRUE,HKEY_LOCAL_MACHINE,{__T("SOFTWARE\\Classes\\CLSID\\"),CLASSID,__T("\\InprocServer32")},{0,0,0},{0,0,0}},
    	{FALSE,0,{0,0,0},{__T("ThreadingModel"),TNUL,TNUL},{__T("Apartment"),TNUL,TNUL}},
    	{FALSE,0,{0,0,0},{TNUL,TNUL,TNUL},{EXPL_PFAD,DLLNAME,TNUL}}
    };
    
    
    
    //läuft, wenn in der cmd-Konsole "regsvr32 /u dllmaker.dll" aufgerufen wird
    	STDAPI DllUnregisterServer(void)
    	{
    		
    		for (int i = 0; i < RTL_NUMBER_OF(regEntries); i++)
    		{
    			if (regEntries[i].isAkey == TRUE)
    			{
    				  //return _Module.UnregisterServer(TRUE);
    				RegOpenKeyEx(regEntries[i].hotkey, (LPCWSTR)concat(regEntries[i].key), NULL, DELETE, &hkey);
    				RegDeleteKey(regEntries[i].hotkey, (LPCWSTR)concat(regEntries[i].key));
    			}
    		}
    		return 0;
    	}
    
    	//läuft, wenn in der cmd-Konsole "regsvr32 dllmaker.dll" aufgerufen wird
    	STDAPI DllRegisterServer(void)
    	{
    		int noe = RTL_NUMBER_OF(regEntries); 
    
    		for (int i = 0; i < noe; i++)
    		{
    			if (regEntries[i].isAkey ==TRUE)
    				RegCreateKeyEx(regEntries[i].hotkey, concat(regEntries[i].key), 0, NULL,  
    										REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, 
    											NULL,   &hkey,  NULL);
    
    			if (regEntries[i].isAkey ==FALSE)
    			{
    				TCHAR* valBuffer = new TCHAR[200] ;
    				_sntprintf_s(valBuffer, 200, 200, concat(regEntries[i].val)); //konvertierung
    				RegSetValueEx(hkey, concat(regEntries[i].valName), 0,  REG_SZ, (byte *)valBuffer, 
    										(DWORD)(wcslen((const wchar_t*)valBuffer) + 1)*2);
    			}
    
    			if (i != (noe-1) && regEntries[i+1].isAkey ==TRUE) RegCloseKey(hkey);
    		}
    
    		return 0;
    	}
    
    	//instanziert den Objekt-Manager (wird vom System automatisch aufgerufen)
    	STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppv)
    	{
    		if(rclsid != guid) return CLASS_E_CLASSNOTAVAILABLE;
    	
    		ObjektManager* pRegClass = new ObjektManager(ifactID,iunownID,idispID);
    		pRegClass ->QueryInterface (riid, ppv);
    
    		return S_OK;
    	}
    
    	//beim dll-unload (automatischer Aufruf durch System)
    	STDAPI DllCanUnloadNow(void)
    	{
    		return (threadIsBusy==FALSE)? S_OK:S_FALSE;
    	}
    
    //ende Sektion dll-API
    
    // Automatismen, wenn die DLL erstmals zur Laufzeit geladen wird (Loadlibrary)
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    		//CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startMessageBox, NULL, 0, NULL);
    		break;
    	case DLL_THREAD_ATTACH:
    	case DLL_THREAD_DETACH:
    	case DLL_PROCESS_DETACH:
    		break;
    	}
    	return TRUE;
    }
    

    und der Sourcecode "costumized.cpp".

    #ifdef GUID_SECTION
    
    //für STDAPI DllGetClassObject-Methode ( struct {long, short, short, 8x uchar array} )
    CLSID guid = {0x64009241,0x4306,0x4971,{0xB6,0xE9,0x18,0x4C,0xA1,0x20,0x8F,0x06}};
    
    //für registry-einträge
    #define PROGID __T("markus.dllmaker") //namespace.Class
    #define CLASSID __T("{64009241-4306-4971-B6E9-184CA1208F06}")
    #define DLLNAME __T("dllmaker.dll")
    
    BOOL threadIsBusy = FALSE; //relevant evtl bei Thread-Programmierung
    
    #endif
    
    
    // Methode-ID-Section -> Zuordnen von Methoden oder Properties zu ID's (beispiel)
    #ifdef METHODE_ID_SECTION
    
    			int arraySize = 2;  //gesamtanzahl aller Methoden + Properties
    
    			const struct{
    				long theID;		//ID der Methode oder Property
    				LPOLESTR theName;	//Name der Methode oder Property
    			} methodeList []= {
    				{2,__T("VerifyBytes")},
    				{7,__T("Version")}
    			};
    
    #endif
    
    // Invoke Section -> aufruf von Methoden anhand der ID (beispiel)
    #ifdef INVOKE_SECTION
    
    //beispiel einer Methode
    case 2:			//the Member-ID of methode or Prop
    		if ( //verifiziere korrekte Methoden-Signatur:
    			(methodeType == DISPATCH_METHOD || methodeType == 0x3) &&  // (weil Aufruf komischerweise statt 0x1 0x3 ist)
    			args->cArgs == 1 &&
    			args->rgvarg[0].vt == VT_I4   // = Integer
    			)
    		{
    				if (returnVal == NULL) returnVal = new VARIANT;
    				returnVal->vt = VT_BSTR; //integer
    				//Implementation
    				returnVal->bstrVal = testString(args->rgvarg[0].intVal);
    
    				return S_OK;
    		}
    
    //beispiel von gettter/setter-Properties
    case 7:			//the Member-ID of methode or Prop
    		if (//verifiziere korrekte Methoden-Signatur
    			methodeType == DISPATCH_PROPERTYGET &&   
    			returnVal != NULL
    			)
    		{
    				returnVal->vt = VT_I4; //integer
    				//Implementation
    				returnVal->intVal = version;
    
    				return S_OK;
    		}
    
    		if (//verifiziere korrekte Methoden-Signatur
    			methodeType == DISPATCH_PROPERTYPUT &&   
    			args->cNamedArgs == 1 &&
    			args->rgvarg[0].vt == VT_I4
    			)
    		{
    				//Implementation
    				version = args->rgvarg[0].intVal;
    
    				return S_OK;
    		}
    
    #endif
    
    // mein Code
    #ifdef COSTUME_CODE
    //costumers Attributes
    int version = 1234;
    
    //costumers Methoden
    BSTR testString(int i)
    {
    		//wchar_t* zahl = new char[4];
    		//_itow(i,zahl,10); //2 = binarysystem 10 = dezimal 16 = hex
    
    		wchar_t* text = __T("Alter von nasebööge änni");
    
    		LPOLESTR str = new OLECHAR[60];
    	    _snwprintf(str,60,__T("%s  %d"),text,i);
    
    		return str;
    }
    #endif
    

    Step 2: Cabinet-File erzeugen

    wir erzeugen eine inf-Datei (dllmaker.inf) mit folgendem Inhalt:

    [version]
    signature="$CHICAGO$"
    AdvancedINF=2.0
    
    [Add.Code]
    dllmaker.dll=dllmaker.dll
    dllmaker.inf=dllmaker.inf
    
    [dllmaker.dll]
    file=thiscab
    clsid={64009241-4306-4971-B6E9-184CA1208F06}
    RegisterServer=yes
    FileVersion=1,0,0,0
    
    [dllmaker.inf]
    file=thiscab
    

    wir erzeugen eine Datei "sample.ddf"

    ;*** Sample Source Code MakeCAB Directive file example
    ;
    .OPTION EXPLICIT     ; Generate errors 
    .Set CabinetNameTemplate=SampleCab.cab       
    .set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory
    .Set CompressionType=MSZIP;** All files are compressed in cabinet files
    .Set UniqueFiles="OFF"
    .Set Cabinet=on
    .Set DiskDirectory1=SAMPLECAB.CAB
    dllmaker.inf
    dllmaker.dll
    ;*** <the end>
    

    Danach legen wir die dllmaker.dll, sample.ddf und dllmaker.inf in dasselbe Verzeichnis und führen folgende Batch-Datei aus:

    makecab /f sample.ddf
    move SAMPLECAB.CAB\SampleCab.cab .\dllmaker.cab
    rmdir SAMPLECAB.CAB
    del setup.*
    pause
    

    jetzt sollten wir eine .cab-Datei haben

    Step 3: Testlauf über HTML und Javascript

    Damit ist der Proof of concept getan:

    <object name="dllmaker" width=0 height=0
    classid="clsid:64009241-4306-4971-B6E9-184CA1208F06"    
    standby="Loading DLLmaker..."
    type="application/x-oleobject"
    codebase="dllmaker.cab">
    </object>
    
    
    <p id="something">This text will get updated by Javascript.</p>
    
    <script type="text/javascript" language="javascript">
    
    
    var p = document.getElementById("something");
    
    //zum Test, ob installation gelungen
    p.innerHTML = "dllmaker Version = " + document.dllmaker.Version + " hat sich unter C:\WINDOWS\Downloaded Program Files eingenistet";
    
    // wenn einmal installiert, ist object-Tag überflüssig und es reicht folgende Initialisierung:
    var obj2 = new ActiveXObject("markus.dllmaker");
    
    
    //testläufe
    alert(obj2.VerifyBytes(40));
    obj2.Version = 4321; //property überschrieben
    alert(obj2.Version);
    
    //ole-string / javascript-string-concatenation und javascript-arrays als übergabe-Parameter an OLE-dll werden nicht unterstützt
    </script>
    

    Download

    Hier