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>