Use tcom as an Active-X server, e.g. create com objects with tcl.
Chin Huang creates and maintains tcom. HaO has created this page on 2012-08-31 within a project with a wrapped application and without deep knowledge of COM in general. I personally made a trial and error process and was surprised that, at the end, something worked.
I hope this might be useful to someone. Feel free to change anything !
Table of contents:
There are two connection methods to propose a com object to other applications:
The server aplication is already running and creates a com object without and client contact. The com object is registered in the running object table (ROT) of windows. This table is not contained in the registry but in memory. Registry entries are only used for helper issues.
TCOM clients contact those objects using getactiveobject :
The TCL server application is started by the client and may be incorporated as DLL in the same process as the client or might be in another process.
The registry is used to inform the client application about the location of the com server.
TCOM clients contact those objects using createobject .
Here are three variants:
The COM object server is loaded as a DLL in the same process as the client application.
The COM object server is loaded in another process. It might be an exe or a DLL.
The COM object server is loaded on another machine than the client program. It might be an exe or a DLL.
The server seams to be identical. The COM machinery will transport the information.
The server may be a TCL script invoked by the tcl dll library or a wrapped exe file. Here is an overview, which method might be with which file type:
Running object | Inproc Server object DLL | Local or Remote Server object | |
---|---|---|---|
TCL script | yes | yes | yes |
Wrapped exe | yes | no | yes |
The original documentation describes this implementation method.
Chin Huang explained to Jeff Godfrey on clt what really happens in the command ::tcom::server register :
The tcom COM server implementation starts up a Tcl interpreter for each COM server, so I don't see how it can invoke the code in a wrapped application. The Tcl command ::tcom::server register Banking.tlb creates the Windows registry key HKEY_CLASSES_ROOT\\CLSID\\$clsid\\tcom with two values: "TclDLL" contains the full path to the Tcl interpreter DLL to load. "Script" contains Tcl code which the Tcl interpreter will execute to load and register the Tcl implementation of that COM class. The script is simply "package require Banking" because the Banking package encapsulates the COM class implementation.
HaO 2012-08-14: I want to build an active-x server application with a wrapped exe. On clt answered Chin Huang to Jeff Godfrey, that this might be not possible. Here is the key post:
I recently traded a few emails with tcom's author (Chin Huang) regarding this issue. Ultimately, using tcom to create a *wrapped* COM object doesn't seem possible. I've included some excerpts from our conversation below in case someone has further interest in the details. Or, maybe someone has some ideas for making this work? Thanks, Jeff Godfrey ===================================== -- Jeff to Chin -- 1. Is it possible to create a wrapped COM object using TCOM? 2. Would you happen to have any specific guidance in this regard, or have any example code? -- Chin to Jeff -- The tcom COM server implementation starts up a Tcl interpreter for each COM server, so I don't see how it can invoke the code in a wrapped application. The Tcl command ::tcom::server register Banking.tlb creates the Windows registry key HKEY_CLASSES_ROOT\\CLSID\\$clsid\\tcom with two values: "TclDLL" contains the full path to the Tcl interpreter DLL to load. "Script" contains Tcl code which the Tcl interpreter will execute to load and register the Tcl implementation of that COM class. The script is simply "package require Banking" because the Banking package encapsulates the COM class implementation. -- Jeff to Chin -- Not knowing much about it, it would seem that it might be possible to modify the values stored in the 2 mentioned registry entries in order to get COM to do the right thing in the face of a wrapped COM object. -- Chin to Jeff -- The COM server starts a Tcl interpreter and uses it to execute the Tcl implementation of the COM class. Having never developed a wrapped application, I don't know how the COM server Tcl interpreter can access Tcl code residing inside a wrapped application. -- Jeff to Chin -- Normally, in a wrapped application (starpack style), the application EXE contains the Tcl-interp. That get's bootstrapped by the EXE init process (details unknown to me) and then some "main" tcl procedure is called to start the wrapped application. So, the Tcl-app and the Tcl- interp are both in the wrapped EXE file. So, on the surface it would seem that "TclDLL" would point to the wrapped EXE itself and "Script" would point to the designated tcl procedure within the wrapped EXE. That said, I'm sure the devil is in the details and there are probably a number of stumbling blocks. -- Chin to Jeff -- You're going to have trouble here. The COM server specifically wants to load a DLL, not an EXE, containing the Tcl interpreter. It first tries to load the DLL at the path specified by "TclDLL", and failing that, tries to load the Tcl interpreter DLL installed by the ActiveState ActiveTcl installer. -- Jeff to Chin -- Understood - thanks. I guess I'll have to tackle this project using .NET... :-(
I am currently working on the issue. It will obviously not work as it is implemented.
But there exists the possibility to use an Active X com exe server (out of proc server). So far, I did not find any information about those, expect that the register with the command line switch /regserver. See below about an example which works with a pre-created object.
I suppose, there are some registry entries necessary to fulfill the task.
A running application may create itself a com object as a server and propose this object to clients.
The corresponding client code uses ::tcom::ref getactiveobject where examples may be found above.
Those objects are listed in the running object table (ROT) .
Here are my steps to create such a server:
The com utilities must be present. One possibility is to install Visual C express. All used command line utilities should be included there.
To create a type library, first an interface definition file must be written. An example is below. Its properties in descending logical order are:
import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(0C7E66F0-B50E-4B03-B784-2C89A9069B65), dual, helpstring("COM Link Step Interface"), pointer_default(unique) ] interface IStep: IDispatch { [id(1), helpstring("COM Link Process")] HRESULT Process( [in] BSTR *pIn, [out, retval] BSTR *pRet); }; [ uuid(16A8EC94-8E85-4D72-9425-B0C64E3BF6A8), version(1.0), helpstring("COM Link Process 1.0 Type Library") ] library Application { importlib("stdole32.tlb"); [ uuid(A7CB39F0-4996-4A43-AD5A-1C718D41CB98), helpstring("COM Link Step Class") ] coclass Step { [default] interface IStep; }; };
An uuid is required for the identification of all items. Those may be generated by:
C:\test>uuidgen 09e8f1b9-5d1a-409e-9a07-bba3dfbbdf5c
I experienced that it may be helpful to put them in uppercase.
To generate a type library file (.tlb) from an interface definition file, one may use midl:
C:\test>midl com_link_process.idl
The generated file com_link_processs.tlb must be copied to the server project files. In this example, it is copied in the same folder as the wrapped server executable. Thus, the file name is:
set Filename [file join [file dirname [info nameofexecutable]] com_link_process.idl]
The first step of the com server script is to import the type library:
if {[catch { package require tcom ::tcom::import $Filename } errMsg]} { tk_messageBox -message "Type library file '$Filename' missing.\n$Err" exit }
This creates the command ::Application::Step.
Then, the exposed object of the server may be created:
set objectHandle [::tcom::object create -registeractive ::Application::Step COMStep]
The procedure COMStep is invoked, if a client invokes the method Process of the step.
The MS-Visual tools contain a program ROT Viewer. The object may now be seen there by its class uuid: "A7CB39F0-4996-4A43-AD5A-1C718D41CB98".
Each call to the object invokes the object handling procedure COMStep.
proc COMStep {method args} { switch -exact -- $method { Process { return "[lindex $args 0] ok" } default { return -code error "Unknown method '$method'" } } }
Another interpreter may now access the object by its class uuid and invoke the process method:
% package require tcom % set h [::tcom::ref getactiveobject -clsid A7CB39F0-4996-4A43-AD5A-1C718D41CB98] ::tcom::handle0x02715AA9 % $h Process A A OK
To use the program ID Link3.Application instead the class ID, the following registry keys are necessary to set:
package require registry registry set {HKEY_LOCAL_MACHINE\SOFTWARE\Classes} Link3.Application "EasySoft.Link" registry set {HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Link3.Application} CLSID "{A7CB39F0-4996-4A43-AD5A-1C718D41CB98}"
The first key must be of the format vendor.component . An included space resulted in the windows api function CLSIDFromProgID to return an error.
This must be done as administrator or may be done by the installation routine.
Note: I suppose from [L1 ], that this may not be done using the class property ProgID of the idl-file. I am not shure, if a specified/repeated ProgID within the idl file is of any use.
Another interpreter may now access the object by its program id and invoke the Process method:
% package require tcom % set h [::tcom::ref getactiveobject Link3.Application] ::tcom::handle0x02715AA8 % $h Process A A OK
HaO 2012-08-22: Starting from the upper example:
When I set the following registry keys:
registry set {HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID} "{A7CB39F0-4996-4A43-AD5A-1C718D41CB98}" Link3 registry set {HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{A7CB39F0-4996-4A43-AD5A-1C718D41CB98}}\ LocalServer32 {C:\oehhar\elmicron\projekte\el1043_elmiprint\elmiprint.exe} registry set {HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{A7CB39F0-4996-4A43-AD5A-1C718D41CB98}}\ ProgID Link3.Application
and I add a registerfactory call:
set objectHandle [::tcom::object registerfactory ::Application::Step COMStep]
and now, I call it from another instance:
% set h [::tcom::ref createobject Link3.Application] ::tcom::handle0x02930090 % $h Process a
The server will be started with the command line switch -Embedding.
The Filename in
::tcom::import $Filename
may not be within a virtual file system (e.g. in the wrapped application). It must first be copied outside of the vfs, as done with dlls.