ExportCom (iDispatch/iFactory/DLL) mit RES-Files
Verfasst: 08.05.2017 18:57
Basierend auf meiner Klassendefinition http://www.purebasic.fr/german/viewtopi ... =8&t=30133 hab ich meine Variante zum erstellen von COM-Objekten/DLL gefunden. Mein Dank hier gilt mk-soft. Ohne seine Lösung wäre das hier nicht möglich.
Die ClassExportCom.pbi in zweiten Post sind wieder per XIncludeFile oder per RES-File (siehe Class-Thread, der oben verlinkt ist) nutzbar und ist so ohne weiteres zutun überall nutzbar.
InitExportCom([CreateLogFile])
Sollte ganz zu Anfang aufgerufen werden, damit werden die nötigen Routinen eingebaut und das Modul ExportCom erstellt. Ein UseModule ExportCom wird automatisch ausgeführt. In jeden Modul, das irgendeine der Funktionen von ExportCom nutzt, sollte es manuell eingefügt werden.
Als nächstes kann man ganz normal die Objekte erstellen. Sie müssen nicht irgendeine SuperClass(Parent) besitzen oder bestimmte Aufrufe enthalten. Die Klassen können auch problemlos in Modulen etc. erstellt werden.
Hier gibt es allerdings zwei PseudoTypen als "RückgabeTyp"
_P_BStr
Wenn man einen String zurückgeben will, muss man als Rückgabetyp eben _P_BStr benutzen. Das ist ein simpel-Macro auf "i". Es wird aber beim Export wichtig. Um einen String zurückzugeben, muss man ProcedureReturn ReturnString(str.s) verwenden.
_P_Dispatch
Wird benötig, wenn man ein Dispatch-Object zurückgeben will. Das ist die einzige Möglichkeit, das ein Objekt ein Objekt zurück gibt.
Wenn man eine Property exportieren will, muss man dafür Methoden benutzen. Es muss also in Interface/EndInterfacebereich ein
PROPERTYGET_<property>.<property-type>()
PROPERTYPUT_<property>(Value..<property-type>)
Es müssen nicht beide rein, wenn man nur Lesen will, reicht PROPERTYGET.
Vor DefineClass() muss man dann nur ein
Dispatch_PropertyGet(<class>,<property>)
Dispatch_PropertyPut(<class>,<property>)
einfügen, dann werden automatisch die passenden Methoden erstellt.
WICHTIG! Es gelten die Einschränkungen von DLL. D.h. bspw. keine Programmcode außerhalb von Proceduren und kein DIM/Lists/Maps außerhalb von Proceduren erstellen! Auch ein zuweisen von globalen Variablen kann problematisch sein.
Da AttachProcess() und DetachProcess() nicht mehr frei Verfügbar sind, muss man auf InitDLL() und ExitDLL() ausweichen. Sie werden als allererstes/letztes aufgerufen.
LogClass(type.s,text.s[,*obj])
Erstellt einen Eintrag in der Logdatei. Wenn keine erstellt werden soll, wird der Aufruf automatisch entfernt - ähnlich wie bei Debug.
Für Type empfehle ich die Konstanten #LogClass_Error, #LogClass_Info und #LogClass_Warning, kann aber auch Prinzipiell jeder String sein. Wenn *obj angegeben wird, dann werden noch ein paar Infos zum Objekt eingetragen.
Mit der Konstante #LogClass kann man Code nur einbinden, wenn auch tatsächlich ein Logfile erstellt wird.
In der InitDLL() kann man auch die Globale Variable LogClass_FileName ändern und so die Logdateinamen ändern. Standard ist der "DLL-Pfad+Name.log".
Wenn das alles erledigt ist, müssen wir definieren, was Exportiert werden soll.
ExportCom()
Leitet den Export ein. es sollte jetzt bis EndExportCom() nur noch ExportClass() benutzt werden!
ExportClass(<class>, ProgramId.s, CLSID.s, Description.s, InterfaceString.s)
ProgramId
ist Quasi der Name des COM-Objekts. Darüber werden sie auch gefunden.
Laut Microsoft soll sie folgendes Format haben:
<Program>.<Component>.<Version>
Unterstriche sind nicht erlaubt und darf auch nicht mit einer Ziffer beginnen.
CLSID
muss eine eindeutige ID sein, die fest steht. Sie wird ohne geschweifte Klammern übergeben!
Folgendes Mini-Programm erstellt eine ID
Description
Eine Beschreibung, die beim Registieren der DLL gespeichert wird
InterfaceString
Das ist im Prinzip der Bereich zwischen Interface und EndInterface. Diese Methoden werden samt Parameternamen übernommen!
Properties werden richtig umgesetzt, wenn sie wie oben definiert wurden. Sollte ein Methodename "METHOD_<name>" lauten, wird das "METHOD_" abgeschnitten. Damit kann man doppelte Methoden definieren, so das man für interne Routinen eine andere Methode nutzt als die exportierte. Praktisch bspw. für Methoden, die Strings zurückgeben. Will man bestimmte Methoden nicht exportieren, kann man sie hier einfach rauslöschen. Der InterfaceString muss nicht alle Elemente des Interfaces enthalten.
Sollte in einer Methode ein Objekt zurückgegeben werden, dann muss die Klasse des Objekts zwingend exportiert werden! Wenn diese Klasse nicht Public sein soll, dann einfach ProgramId, CLSID und Description leer lassen.
EndExportCom()
damit wird der Export beendet. Nach dieser Zeile sollte kein Programmcode mehr kommen!
Nebenbei gibts noch ein paar Hilfsfunktionen/Klassen:
IUnknown
Klasse, füllt eigentlich nur das Interface mit leben
New_IDispatch(Obj) und IDIspatch
Klasse. Das Klasse des eingebundenen Objects muss exportiert werden!
IClassFactory(*vt)
Erstellt eine classFactory. Wird Intern benutzt.
GetStringFromGuid(*guid.guid)
Wandelt ein GUID oder IID oder CLSID (eigentlich alles das selbe) in einen String um und gibt ihn zurück.
GetGuidFromString(guid$,*out.guid)
Und zurück.
Wichtig: Der String wird nicht mit geschweiften Klammern umschlossen.
Ein Beispiel macht alles hoffentlich etwas klarer:
Muss natürlich eine DLL erstellen
meine Test.vbs
bzw 32bit:
Die ClassExportCom.pbi in zweiten Post sind wieder per XIncludeFile oder per RES-File (siehe Class-Thread, der oben verlinkt ist) nutzbar und ist so ohne weiteres zutun überall nutzbar.
InitExportCom([CreateLogFile])
Sollte ganz zu Anfang aufgerufen werden, damit werden die nötigen Routinen eingebaut und das Modul ExportCom erstellt. Ein UseModule ExportCom wird automatisch ausgeführt. In jeden Modul, das irgendeine der Funktionen von ExportCom nutzt, sollte es manuell eingefügt werden.
Als nächstes kann man ganz normal die Objekte erstellen. Sie müssen nicht irgendeine SuperClass(Parent) besitzen oder bestimmte Aufrufe enthalten. Die Klassen können auch problemlos in Modulen etc. erstellt werden.
Hier gibt es allerdings zwei PseudoTypen als "RückgabeTyp"
_P_BStr
Wenn man einen String zurückgeben will, muss man als Rückgabetyp eben _P_BStr benutzen. Das ist ein simpel-Macro auf "i". Es wird aber beim Export wichtig. Um einen String zurückzugeben, muss man ProcedureReturn ReturnString(str.s) verwenden.
_P_Dispatch
Wird benötig, wenn man ein Dispatch-Object zurückgeben will. Das ist die einzige Möglichkeit, das ein Objekt ein Objekt zurück gibt.
Wenn man eine Property exportieren will, muss man dafür Methoden benutzen. Es muss also in Interface/EndInterfacebereich ein
PROPERTYGET_<property>.<property-type>()
PROPERTYPUT_<property>(Value..<property-type>)
Es müssen nicht beide rein, wenn man nur Lesen will, reicht PROPERTYGET.
Vor DefineClass() muss man dann nur ein
Dispatch_PropertyGet(<class>,<property>)
Dispatch_PropertyPut(<class>,<property>)
einfügen, dann werden automatisch die passenden Methoden erstellt.
WICHTIG! Es gelten die Einschränkungen von DLL. D.h. bspw. keine Programmcode außerhalb von Proceduren und kein DIM/Lists/Maps außerhalb von Proceduren erstellen! Auch ein zuweisen von globalen Variablen kann problematisch sein.
Da AttachProcess() und DetachProcess() nicht mehr frei Verfügbar sind, muss man auf InitDLL() und ExitDLL() ausweichen. Sie werden als allererstes/letztes aufgerufen.
LogClass(type.s,text.s[,*obj])
Erstellt einen Eintrag in der Logdatei. Wenn keine erstellt werden soll, wird der Aufruf automatisch entfernt - ähnlich wie bei Debug.
Für Type empfehle ich die Konstanten #LogClass_Error, #LogClass_Info und #LogClass_Warning, kann aber auch Prinzipiell jeder String sein. Wenn *obj angegeben wird, dann werden noch ein paar Infos zum Objekt eingetragen.
Mit der Konstante #LogClass kann man Code nur einbinden, wenn auch tatsächlich ein Logfile erstellt wird.
Code: Alles auswählen
CompilerIf #LogClass :dosomething: CompilerEndif
Wenn das alles erledigt ist, müssen wir definieren, was Exportiert werden soll.
ExportCom()
Leitet den Export ein. es sollte jetzt bis EndExportCom() nur noch ExportClass() benutzt werden!
ExportClass(<class>, ProgramId.s, CLSID.s, Description.s, InterfaceString.s)
ProgramId
ist Quasi der Name des COM-Objekts. Darüber werden sie auch gefunden.
Laut Microsoft soll sie folgendes Format haben:
<Program>.<Component>.<Version>
Unterstriche sind nicht erlaubt und darf auch nicht mit einer Ziffer beginnen.
CLSID
muss eine eindeutige ID sein, die fest steht. Sie wird ohne geschweifte Klammern übergeben!
Folgendes Mini-Programm erstellt eine ID
Code: Alles auswählen
InitExportCom()
my.guid
Define a$
CoCreateGuid_(@my)
a$=GetStringFromGuid(my)
Debug a$
Eine Beschreibung, die beim Registieren der DLL gespeichert wird
InterfaceString
Das ist im Prinzip der Bereich zwischen Interface und EndInterface. Diese Methoden werden samt Parameternamen übernommen!
Properties werden richtig umgesetzt, wenn sie wie oben definiert wurden. Sollte ein Methodename "METHOD_<name>" lauten, wird das "METHOD_" abgeschnitten. Damit kann man doppelte Methoden definieren, so das man für interne Routinen eine andere Methode nutzt als die exportierte. Praktisch bspw. für Methoden, die Strings zurückgeben. Will man bestimmte Methoden nicht exportieren, kann man sie hier einfach rauslöschen. Der InterfaceString muss nicht alle Elemente des Interfaces enthalten.
Sollte in einer Methode ein Objekt zurückgegeben werden, dann muss die Klasse des Objekts zwingend exportiert werden! Wenn diese Klasse nicht Public sein soll, dann einfach ProgramId, CLSID und Description leer lassen.
EndExportCom()
damit wird der Export beendet. Nach dieser Zeile sollte kein Programmcode mehr kommen!
Nebenbei gibts noch ein paar Hilfsfunktionen/Klassen:
IUnknown
Klasse, füllt eigentlich nur das Interface mit leben

New_IDispatch(Obj) und IDIspatch
Klasse. Das Klasse des eingebundenen Objects muss exportiert werden!
IClassFactory(*vt)
Erstellt eine classFactory. Wird Intern benutzt.
GetStringFromGuid(*guid.guid)
Wandelt ein GUID oder IID oder CLSID (eigentlich alles das selbe) in einen String um und gibt ihn zurück.
GetGuidFromString(guid$,*out.guid)
Und zurück.
Wichtig: Der String wird nicht mit geschweiften Klammern umschlossen.
Ein Beispiel macht alles hoffentlich etwas klarer:
Code: Alles auswählen
EnableExplicit
;Only include class.pbi, when no class.pbi.res exist!
CompilerIf Not Defined(_BaseClass,#PB_Structure)
XIncludeFile "class.pbi"
CompilerEndIf
;Only Include class_exportcom.pbi, when no class_exportcom.pbi.res exist
CompilerIf Not Defined(__ComClass,#PB_Structure)
#DoLogClass=#True
XIncludeFile "class_exportcom.pbi"
CompilerElse
InitExportCom(#True)
CompilerEndIf
;{ testobj
Interface testobj Extends IDispatch
Get._p_bstr()
set(left.i,mid.s,right.i)
PROPERTYGET_text._p_bstr()
PROPERTYPUT_text(NewText.s)
PROPERTYGET_double.d()
propertyput_double(Value.d)
propertyget_one.a()
propertyput_one(value.a)
EndInterface
Structure _testobj Extends _IDispatch
text.s
double.d
one.a
EndStructure
DeclareClass(testobj,IDispatch)
Procedure._p_bstr testobj_get(*self._testobj)
LogClass(#LogClass_Info,"Return 99",*self)
ProcedureReturn ReturnString("99")
EndProcedure:AsMethod(testobj,get)
Procedure testobj_set(*self._testobj,l.i,m.s,r.i)
LogClass(#LogClass_Info,"Get "+l+" - "+m+" - "+r,*self)
ProcedureReturn l*2
EndProcedure:AsMethod(testobj,set)
Dispatch_PropertyGet(testobj,double)
Dispatch_PropertyPut(testobj,double)
Dispatch_PropertyGet(testobj,text)
Dispatch_PropertyPut(testobj,text)
Dispatch_PropertyGet(testobj,one)
Dispatch_PropertyPut(testobj,one)
Method_QueryInterface(testobj,IDispatch,?iid_TestObj)
DefineClass(testobj,IDispatch)
DataSection
iid_TestObj: ; "A04DD0C5-9B24-4497-B573-DB5944021046"
Data.l $A04DD0C5
Data.w $9B24,$4497
Data.b $B5,$73,$DB,$59,$44,$02,$10,$46
EndDataSection
;}
DeclareModule Combi
CompilerIf Defined(class,#PB_Module):UseModule class:CompilerEndIf
UseModule ExportCom
;{ retTest
Interface RetTest Extends BaseClass
PropertyGet_text._p_BStr()
maxstr._p_bstr()
EndInterface
Structure _RetTest Extends _BaseClass
text.s
EndStructure
DeclareClass(rettest,BaseClass)
;}
;{ test2
Interface test2 Extends BaseClass
ReturnObj._p_DISPATCH(text.s)
EndInterface
Structure _test2 Extends _BaseClass
EndStructure
DeclareClass(test2,BaseClass)
;}
EndDeclareModule
Module combi
Dispatch_PropertyGet(RetTest,text)
Procedure.i rettest_maxstr(*self)
Static str.s
If str=""
str=Space(1024*1024)
EndIf
ProcedureReturn ReturnString(str)
EndProcedure:AsMethod(rettest,maxstr)
DefineClass(rettest,BaseClass)
Procedure test2_ReturnObj(*self._test2,text.s)
Protected *obj._RetTest
Protected *disp
*obj=RetTest()
*obj\text=text
*disp=new_iDispatch(*obj)
FreeObject(*obj)
ProcedureReturn *disp
EndProcedure : AsMethod(test2,ReturnObj)
DefineClass(test2,BaseClass)
EndModule
Procedure InitDLL()
LogClass_Filename=ProgramFilename()+"myCustomlogfile.txt" ; Optional!
LogClass(#logclass_info,"Here we are!")
EndProcedure
Procedure ExitDLL()
logclass(#logclass_info,"Bye")
EndProcedure
ExportCom()
ExportClass(testobj,
"gpihomeeu.Example1.1",
GetStringFromGuid(?iid_TestObj),
"GPIHOME Com Class Example",
"Get._p_bstr()"+
"set(left.i,mid.s,right.i)"+
"PROPERTYGET_text._p_bstr()"+
"PROPERTYPUT_text(NewText.s)"+
"PROPERTYGET_double.d()"+
"propertyput_double(Value.d)"+
"propertyget_one.a()"+
"propertyput_one(value.a)")
exportclass(combi::test2,
"gpihomeeu.Example2.1",
"FE9FD00C-4F1E-4E79-B514-24C97688BA87",
"GPIHOME Com Class Example2",
"ReturnObj._p_DISPATCH(text.s)")
exportclass(combi::RetTest,
"","","",
"PropertyGet_text._p_BStr()"+
"maxstr._p_BStr()")
EndExportCOM()
meine Test.vbs
und meine test.bat (die man mit Adminrechten zwecks Registierung aufrufen muss; Pfade muss man anpassen)dim obj,ret,test
set obj = createobject("gpihomeeu.Example1.1")
msgbox obj.get
ret=obj.set (12, "Muhaha", 23)
msgbox ret
obj.text = "VBS-Text-Test"
msgbox obj.text
test=obj.text
obj.double = 1.235
msgbox obj.double
obj.one =123
msgbox obj.one
dim obj2,obj3
set obj2 = CreateObject("gpihomeeu.Example2.1")
set obj3 = obj2.ReturnObj ("obj2test")
msgbox "obj3:" & obj3.text
dim xx ,i
test=obj3.maxstr
msgbox "one call - Open TaskManager and check Memory"
for i=1 to 100
xx=obj3.maxstr
next
msgbox "many calls - used memory should be the same"
set obj = Nothing
set obj2=Nothing
set obj3=nothing
set obj=CreateObject("gpihomeeu.Example1.1")
obj.double=4.5
msgbox obj.double
Code: Alles auswählen
:start
%systemroot%\System32\regsvr32.exe "C:\Users\GPI\Documents\!PureBasic\objtest\example.dll"
%windir%\System32\wscript.exe "C:\Users\GPI\Documents\!PureBasic\objtest\!Example.vbs"
%systemroot%\System32\regsvr32.exe -u "C:\Users\GPI\Documents\!PureBasic\objtest\example.dll"
pause
goto start
Code: Alles auswählen
:start
%systemroot%\Syswow64\regsvr32.exe "C:\Users\GPI\Documents\!PureBasic\objtest\example32.dll"
%windir%\Syswow64\wscript.exe "C:\Users\GPI\Documents\!PureBasic\objtest\!Example.vbs"
%systemroot%\Syswow64\regsvr32.exe -u "C:\Users\GPI\Documents\!PureBasic\objtest\example32.dll"
pause
goto start