Dieses Beispiel wurde unter VisualStudio 2008 realisiert. Der Sourcecode besteht aus 2 Files:
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.
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
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
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>