ExportCom (iDispatch/iClassFactory/DLL) with RES-Files
Posted: Sun May 14, 2017 6:24 pm
Based on my class definition http://www.purebasic.fr/english/viewtop ... 12&t=68475 I have created this variant to create an Com-Object/DLL-Com-Server.
Special thanks to mk-soft for his solution!
Again the ClassExportCom.pbi in the second post can be used as XIncludeFile() or to create a res-file. The class.pbi from the other thread is needed.
InitExportCom([CreateLogFile])
Should be called at the beginning. It create the Modul ExportCom. A UseModule ExportCom is executed automatically.
now you should define all the classes you want to export. They must not be a SubClass from iUnknown! You can declare this class in Modules.
There are two "Pseudo Types" for return-types of the methods
_P_BStr
You should use this type, when you want to return a string. it is basically a macro of "I". You must return the string with ProcedureReturn ReturnString(str.s).
_P_Dispatch
When you want to return an Dispatch-Object, you should use this. With this an Object can return an object.
When you want to export a property, there are Methods for this, add in the Interface/EndInterface section this two lines:
PROPERTYGET_<property>.<property-type>()
PROPERTYPUT_<property>(Value..<property-type>)
It is possible to only add get without put and put without get.
You can create the methods easily with
Dispatch_PropertyGet(<class>,<property>)
Dispatch_PropertyPut(<class>,<property>)
and this should be called before the DefineClass() statement.
Because my routines use AttachProcess() and DetachProcess(), you can create optional procedures InitDLL() and ExitDLL()
LogClass(type.s,text.s[,*obj])
Create an entry in an logfile. When you specific that no log file should be created, this commands are removed automatically (like the debug command).
For type you can use this constants: #LogClass_Error, #LogClass_Info and #LogClass_Warning
The constant LogClass can be used with CompilerIf/CompilerEndif to add Code only, when a Log file is created.
You can change in the InitDLL() the global variable LogClass_FileName. Default is "DLL-Pfad+Name.log".
At the end of the File you must Export the Classes.
ExportCom()
Start the export. Between ExportCom() and EndExportCom() you should only use ExportClass()!
ExportClass(<class>, ProgramId.s, CLSID.s, Description.s, InterfaceString.s)
ProgramId
Name of the Com-Class. You should use this format
Underlines are not allowed and it should not start with a number.
CLSID
An unique ID (without {})
This small program create a CLSID
Description
A Description of the object, needed for the registration of the dll.
InterfaceString
This is a copy of Interface/EndInterface as a string. When a Method name starts with "METHOD_" it will be exported without the "METHOD_". This can be usefull to define Methods for internal use and exported use. The string must not contain all elements from the Interface. When you don't want not to export some Methods, simple remove it.
When a Method want to return an object, the class of this object must be "exportet". When you don't want to have this class public, simple set the ProgramID, CLSID and description to "".
EndExportCom()
Finalize the export. No program code should be after this line.
Additional Classes/Macros:
IUnknown
Class
IDIspatch and New_IDispatch(Obj)
Class. Create a new IDispatch-Object with an embedded Object.
IClassFactory(*vt)
Class[
GetStringFromGuid(*guid.guid)
Convert a GUID, IID, CLSID (basically they are all the same) in to a string
GetGuidFromString(guid$,*out.guid)
and convert back.
Improtant the GUID-String should be without {}.
An Example
This Code should create an Example.DLL.
Test.vbs
bzw 32bit:
Special thanks to mk-soft for his solution!
Again the ClassExportCom.pbi in the second post can be used as XIncludeFile() or to create a res-file. The class.pbi from the other thread is needed.
InitExportCom([CreateLogFile])
Should be called at the beginning. It create the Modul ExportCom. A UseModule ExportCom is executed automatically.
now you should define all the classes you want to export. They must not be a SubClass from iUnknown! You can declare this class in Modules.
There are two "Pseudo Types" for return-types of the methods
_P_BStr
You should use this type, when you want to return a string. it is basically a macro of "I". You must return the string with ProcedureReturn ReturnString(str.s).
_P_Dispatch
When you want to return an Dispatch-Object, you should use this. With this an Object can return an object.
When you want to export a property, there are Methods for this, add in the Interface/EndInterface section this two lines:
PROPERTYGET_<property>.<property-type>()
PROPERTYPUT_<property>(Value..<property-type>)
It is possible to only add get without put and put without get.
You can create the methods easily with
Dispatch_PropertyGet(<class>,<property>)
Dispatch_PropertyPut(<class>,<property>)
and this should be called before the DefineClass() statement.
Because my routines use AttachProcess() and DetachProcess(), you can create optional procedures InitDLL() and ExitDLL()
LogClass(type.s,text.s[,*obj])
Create an entry in an logfile. When you specific that no log file should be created, this commands are removed automatically (like the debug command).
For type you can use this constants: #LogClass_Error, #LogClass_Info and #LogClass_Warning
The constant LogClass can be used with CompilerIf/CompilerEndif to add Code only, when a Log file is created.
Code: Select all
CompilerIf #LogClass :dosomething: CompilerEndif
At the end of the File you must Export the Classes.
ExportCom()
Start the export. Between ExportCom() and EndExportCom() you should only use ExportClass()!
ExportClass(<class>, ProgramId.s, CLSID.s, Description.s, InterfaceString.s)
ProgramId
Name of the Com-Class. You should use this format
Code: Select all
<Program>.<Component>.<Version>
CLSID
An unique ID (without {})
This small program create a CLSID
Code: Select all
InitExportCom()
my.guid
Define a$
CoCreateGuid_(@my)
a$=GetStringFromGuid(my)
Debug a$
A Description of the object, needed for the registration of the dll.
InterfaceString
This is a copy of Interface/EndInterface as a string. When a Method name starts with "METHOD_" it will be exported without the "METHOD_". This can be usefull to define Methods for internal use and exported use. The string must not contain all elements from the Interface. When you don't want not to export some Methods, simple remove it.
When a Method want to return an object, the class of this object must be "exportet". When you don't want to have this class public, simple set the ProgramID, CLSID and description to "".
EndExportCom()
Finalize the export. No program code should be after this line.
Additional Classes/Macros:
IUnknown
Class
IDIspatch and New_IDispatch(Obj)
Class. Create a new IDispatch-Object with an embedded Object.
IClassFactory(*vt)
Class[
GetStringFromGuid(*guid.guid)
Convert a GUID, IID, CLSID (basically they are all the same) in to a string
GetGuidFromString(guid$,*out.guid)
and convert back.
Improtant the GUID-String should be without {}.
An Example
Code: Select all
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()
Test.vbs
And my Test.bat (should be started with Admin rights and you must correct the paths.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: Select all
: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: Select all
: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