Version 2 of tcom server

Updated 2012-08-30 19:57:10 by oehhar

tcom server

Use tcom as an Active-X server, e.g. create com objects with tcl.


Original documentation


There are two connection methods to propose a com object to other applications:

Running object

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 :

Server object

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:

Inproc

The COM object server is loaded as a DLL in the same process as the client application.

Local

The COM object server is loaded in another process. It might be an exe or a DLL.

Remote

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 objectInproc Server object DLLLocal or Remote Server object
TCL scriptyesyesyes
Wrapped exeyesnoyes

TCL script

The original documentation describes this implementation method.

Chin Huang explained to Jeff Godfrey on clt what really happens in the command http://www.vex.net/~cthuang/tcom/server.html :

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.

  wrapped COM server

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.

  Out of proc server with running object

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:

Preface

The com utilities must be present. One possibility is to install Visual C express. All used command line utilities should be included there.

Create a type library

To create a type library, first an interface definition file must be written. An example is below. Its properties in descending logical order are:

  • File name: com_link_process.idl
  • Library name: Application
  • Object class: Step (coclass clause)
  • Interface name: IStep
  • Method name: Process
  • Input parameter: pIn of type binary string pointer (BSTR *).
  • return value: pRet of type binary string pointer (BSTR *).
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;
        };


};

uuid

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.

generate type library file

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]

Load type library

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.

Create object

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".

Object handling procedure

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'"
        }
    }
}

Invoke the object by its class id

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

Register Program ID

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.

Invoke the object by its program id

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

  Wrapped out of proc server (work in progress)

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.