Commands for accessing COM servers
This module provides commands for supporting COM clients. TWAPI's COM support is limited to client side calls only and does not provide facilities for writing COM components. Like other scripting languages like VBScript and Javascript, TWAPI's COM support is focused on Automation objects through their IDispatch and IDispatchEx interfaces. This provides the ability to script Windows applications such as Microsoft Office and Internet Explorer as well as admistrative interfaces such as Active Directory and Windows Management Instrumentation.
This documentation assumes some familiarity with COM programming concepts such as COM interfaces, COM automation, PROGID and CLSID identifier types etc.
TWAPI's COM support is implemented in layers:
A COM automation proxy object is created through the command comobj using the PROGID or CLSID of the corresponding COM class. This returns a Tcl object command that may be used to access the object's methods and properties. The example below creates a proxy object using the PROGID for Internet Explorer.
set xl [comobj Excel.Application]
To attach to an existing COM instance instead of creating a new one, the -active option may be specified.
set xl [comobj Excel.Application -active]
The above command requires an instance of Excel to be running else an error is returned.
Alternatively, the comobj_object command can be used to bind to a COM object identified by its display name. For example, the following creates a COM object for an Excel file.
set xlso [comobj_object c:\\data\\myspreadsheet.xls]
When an object is no longer required, it must be explicitly destroyed by calling the command with the -destroy subcommand.
The list of existing COM automation objects in the application can be retrieved with the comobj_instances command. The command comobj? can be used to check if a command is a COM object.
The properties and methods of a COM object can be accessed through the proxy object returned by the comobj command. The following sequence of commands shows a sample session that drives Internet Explorer through its properties and methods.
Start up Internet Explorer (note it will not be visible yet)
set ie [comobj InternetExplorer.Application -enableaaa]
Automation objects can be associated with a default property that is retrieved if no property or method are specified. This is the property marked as the default value in the COM object's IDL definition. In TWAPI this default property can be accessed using the -default method on the object.
$ie -default
Properties can be directly retrieved and modified. For example, the following commands makes its window visible by changing its Visible property.
$ie Visible true
In theory, the names of properties and methods of an automation object can overlap. Althouth TWAPI can usually figure out whether a property is being retrieved/set or a method is being called, you can explicitly control this by using the -get, -set or -call subcommands followed by the actual name. So the above call could also have been written as
$ie -set Visible true
to explicitly indicate we want to set the property called Visible as opposed to making a call to the COM object's method of the same name (if there was one).
To go to the TWAPI home page, invoke the Navigate method
$ie Navigate http://twapi.sf.net
Again, we could specify this is a method call, not a property, by explicitly indicating that as follows:
$ie -call Navigate http://twapi.sf.net
Explicitly specifying -call may also be required to disambiguate between methods that are implemented within the TWAPI comobj command object and the actual COM automation object itself. For example, the method destroy destroys the command object. If the underlying automation object also had a destroy method, you would need to invoke it as
$ie -call destroy
To continue with the example, we can read the LocationURL property to find the URL after redirection
$ie LocationURL
Ask the application to exit by calling its Quit method.
$ie Quit
Finally, destroy the proxy object and release the underlying COM object,
$ie destroy
Some COM object methods may have a large number of arguments. Usually, these have default values and do not need to be specified. However, sometimes you may want to specify one or two of these. In such cases, having to specify all preceding arguments by position can be cumbersome. Using named arguments makes this easier. To use named arguments, use -callnamedargs instead of -call. For example, suppose in the call above to Navigate, we wanted to specify the page to be displayed in a new window without specifying all intervening parameters. We could call it as follows:
$ie -callnamedargs Navigate url http://twapi.sf.net TargetFrameName _blank
In the above call, url and TargetFrameName are parameter names. The parameter names for a call are generally specified in the documentation for the object.
Some COM objects are collection of items which may even be other COM objects. The items within these collections can be accessed by their index (which is not necessarily an integer) using the Item method as shown below.
set fso [comobj Scripting.FileSystemObject] set drives [$fso Drives] set cdrive [$drives Item c:] puts "Drive C: is [lindex {{not ready} ready} [$cdrive IsReady]]" $cdrive -destroy $drives -destroy $fso -destroy
In the above example, the Drives method of the Scripting.FileSystemObject object (implemented as an automation object by the Windows Shell) returns a collection drives. The drive letter c: is used to index into the collection object drives. As an alternative to explicit indexing in this fashion, TWAPI provides the -iterate method for iterating over all items in the collection. This is similar to the VBScript For each statement. The following example illustrates this.
set fso [comobj Scripting.FileSystemObject] set drives [$fso Drives] $drives -iterate drive { set drive_letter [$drive DriveLetter] if {[$drive IsReady]} { puts "Drive $drive_letter free space: [$drive FreeSpace]" } else { puts "Drive $drive_letter is not ready." } $drive -destroy } $drives -destroy $fso -destroy
The -iterate operator on the collection then loops through each item in the collection, assigning the item to drive. In this case, each item is itself a COM automation object on which commands can be invoked. Note that the drive object generated in each iteration has to be explicitly destroyed.
In many cases, the resource represented by a COM object has to be accessed by navigating through multiple intermediary objects. For example, the following example sets all cells within a range in a spreadsheet to 12345.
set xl [comobj Excel.Application] $xl Visible true set workbooks [$xl Workbooks] set workbook [$workbooks Add] set sheets [$workbook Sheets] set sheet [$sheets Item 1] set cells [$sheet range a1 c3] $cells Value2 12345 $cells -destroy $sheet -destroy $sheets -destroy $workbook -destroy $workbooks -destroy $xl Quit $xl -destroy
In the above example, in order to get to the range we have to navigate through a hierarchy of objects, starting with the application, the workbook collection, a workbook within the collection, the worksheets collection, a worksheet within that collection and finally the cell range. After setting the value, all the created objects have to be deleted.
As an alternative, TWAPI automation proxy objects provide the -with internal method to simplify this process:
set xl [comobj Excel.Application] $xl Visible true $xl -with { Workbooks Add Sheets {Item 1} {range a1 c3} } Value2 12345 $xl -destroy
The -with method takes a list of intermediate methods and associated parameters each of which should return a new object. Each method in the method list is invoked on the object returned by the previous method and should itself return a new object. The final object created from this list is passed the remaining arguments in the command. All intermediate objects are automatically destroyed.
Some COM objects may generate notifications when certain events occur. A callback script may be registered using the -bind subcommand on a COMOBJ object. This script is invoked for every notification event generated by the COM object. The event name and parameters provided by the COM event source are appended to the script.
The following example receives events from Internet Explorer and prints them.
proc print_event args {puts "Received: [join $args ,]"} set ie [comobj InternetExplorer.Application -enableaaa] $ie Visible true set bind_id [$ie -bind print_event] $ie Navigate http://www.tcl.tk after 2000 $ie -unbind $bind_id $ie Quit $ie -destroy
Note that the script is unregistered using the -unbind subcommand when no longer needed.
In addition to the high level comobj based access to COM automation objects, TWAPI also provides object based wrappers for certain standard COM interfaces. These are provided for finer control of COM operation when the high level comobj does not provide enough flexibility. These wrapped include IUnknown, IDispatch, IDispatchEx, ITypeInfo, ITypeLib and ITypeComp. The corresponding wrapper classes are named with a Proxy suffix. For example, IUnknown is wrapped by the class IUnknownProxy.
Use of the classes requires a detailed understanding of COM programming techniques including lifetime management of objects through reference counting. Individual methods of these objects are not documented here. See the Windows SDK for details on these.
In addition to the standard methods defined by these interfaces, the proxy wrappers also implement additional helper methods that simplify working with the interfaces.
The following illustrates how these wrappers might be used:
set tlib [ITypeLibProxy_from_path c:/windows/system32/stdole2.tlb] set count [$tlib GetTypeInfoCount] for {set i 0} {$i < $count} {incr i} { if {[$tlib GetTypeInfoType $i] == 4} { puts [lindex [$tlib GetDocumentation $i] 0] } } $tlib Release
The example starts off by getting a ITypeLibProxy object which wraps the ITypeLib interface for the stdole2.tlb type library. It then iterates through to print the names of dispatch interfaces in the type library using the ITypeLib methods GetTypeInfoCount, GetTypeInfoType and GetDocumentation as documented in the Windows SDK.
Note the object is released by calling Release and not its destroy method. This follows standard COM methodology. Calling the destroy method will delete the object while there might still be other references to it.
The same may be written with the @Foreach helper method as follows:
set tlib [ITypeLibProxy_from_path c:/windows/system32/stdole2.tlb] $tlib @Foreach -type dispatch ti { puts [$ti @GetName] $ti Release } $tlib Release
The @Foreach method passes ITypeInfoProxy objects into the loop for every dispatch interface type entry in the library.
Normally, TWAPI accesses COM components by looking up the method and property definitions at run time when the method or property is actually invoked. However, in some situations it is beneficial for reasons of convenience or efficiency to define these up front. TWAPI therefore provides facilities to extract the classes, interfaces and other data definitions associated with COM components from type libraries.
The primary command used for this purpose is the generate_code_from_typelib command. During development, this command can be used to write a file containing a Tcl script that will load the appropriate class and type definitions. The Tcl source command can then be used to load the class and type definitions at run time. Alternatively, the command can also return the script itself which can then be run via the Tcl eval command to immediately define the classes and types. Both these are illustrated by the examples below.
The following command will evaluate a script for defining type definitions for Microsoft Excel and then create a new instance of Excel.
eval [twapi::generate_code_from_typelib "c:/program files/microsoft office/office12/excel.exe"] set xl [excel::Application new] $xl Visible 1
In the example, the namespace excel is created based on the internal name of the library, not the file name though they happen to be the same. Within the namespace, classes such as Application, Worksheet, are defined corresponding to various COM objects. Invoking new on these classes will create instances of a COM automation object just like in the comobj command and can be used the same way.
In addition, the above will also define arrays within the namespace corresponding to the enums defined in the type library. For example, excel::XlRgbColor will contain the values mapping rgb color names. Similarly, constant variable definitions within a module will be contained within an array of that name.
Instead of evaling the returned script, we could also have saved it to a file using the -output option. This file could then be shipped with the application and read using the Tcl source command to recreate the definitions.
generate_code_from_typelib "c:/program files/microsoft office/office12/excel.exe" -output exceldefs.tcl
The following examples illustrate other options and scenarios. Note the commands do not eval the result which is just output to the screen so you can see the effect of the various options and the generated code. The stdole2.tlb type library is used as it is small enough to be viewed without clutter.
The following creates a script for all type definitions within the type library.
generate_code_from_typelib c:/windows/system32/stdole2.tlb
The next example creates a script for all type definitions within the type library, but within the ns namespace instead of the default stdole which is the internal type library name. If an empty string was specified instead of ns, the classes and enum array would be created in the current namespace.
generate_code_from_typelib c:/windows/system32/stdole2.tlb -namespace ns
Type libraries can be quite large and sometimes you need only a subset of the definitions. The following commands restrict the generated code to only define enums and a specific COM class respectively.
generate_code_from_typelib c:/windows/system32/stdole2.tlb -type enum
generate_code_from_typelib c:/windows/system32/stdole2.tlb -type coclass -name StdPicture
The choice between pregenerating type definitions versus runtime discovery of methods depends on several factors. The former provides some convenience; for example, enum constants can be used symbolically. Also, with some components (Microsoft Installer being one example), the type information may not be available on the target system and pregeneration is the only way to access them. The other factor is performance. If only a few components and methods are used, runtime discovery may be more efficient but for heavy use of pregeneration may offer better performance.
Generally, TWAPI uses type information present in COM objects to detect parameter types and convert Tcl values appropriately. However, there are two circumstances where TWAPI has to guess the parameter type. The first case is when the COM object does not provide type information. The second case is when the parameter type is defined as a VARIANT which is a union that may be instantiated as one of several types. The COM object method may change its behaviour depending on the concret type of the passed parameter.
In languages like Visual Basic, the type of the passed parameter can be deduced because the language itself is typed. In Tcl, where "Everything Is A String", this is not possible. To deal with this situation, the application can use the tclcast command to appropriately force the passed value to a specific type. So for example, to force the passed parameter to be treated as a boolean, and not an integer, the application could call a method as follows:
$obj Visible [tclcast boolean 1]
Sometimes the COM component may not provide type information or mark a parameter as a generic VARIANT and yet expect a specific type. In such cases, even casting may not be sufficient to correctly pass parameters in the expected format. As a last resort to deal with such cases, TWAPI allows definition of prototypes for methods and properties for a COM component through the define_dispatch_prototypes command.
In the above example, instead of using a cast, a prototype for the method could be defined instead:
define_dispatch_prototypes guid {func 19 int Visible([in] bool bval)}
where guid is the GUID for the COM component and 19 is its DISPID (roughly, the index into the dispatch table). Note this requires some detailed knowledge of the COM component, like the GUID and the DISPID values for the member methods and properties.
(tclsh) 53 % clsid_to_progid "{0002DF01-0000-0000-C000-000000000046}" InternetExplorer.Application.1
-active | If true, the returned object is attached to an existing COM instance instead of creating a new one. If no instances are running, an error is generated. Other options are ignored if this option is specified. |
-disablelog BOOLEAN | If specified as true, disables logging of failures. Corresponds to the flag CLSCTX_NO_FAILURE_LOG in the SDK. |
-download BOOLEAN | If specified as true, allows downloading of code
from the Internet or Directory Service. This corresponds to setting
the flag
CLSCTX_ENABLE_CODE_DOWNLOAD. If
specified as false, disallows downloading of code from the Internet
or Directory Service. This corresponds to setting the flag
CLSCTX_DISABLE_CODE_DOWNLOAD. If this option is not specified, neither of the flags is set in the underlying call to CoCreateInstance. |
-enableaaa BOOLEAN | If specified as true, enables activate-as-activator
activations where a server process is launched under the caller's
identity. This corresponds to setting the flag
CLSCTX_ENABLE_AAA. If specified as
false, disables activate-as-activator activations. This corresponds
to setting the flag
CLSCTX_DISABLE_AAA. If this option is not specified, neither of the flags is set in the underlying call to CoCreateInstance. |
-interface IDISPATCHINTERFACE | By default, the command will bind to the object's IDispatch interface. If the object is known to support the IDispatchEx interface as well, this option may be used to specify that should be used instead. IDISPATCHINTERFACE must be IDispatch or IDispatchEx. |
-model MODELLIST | Specifies which of the COM hosting models are acceptable. MODELLIST must be a list containing one or more of the symbols localserver, remoteserver, inprocserver, inprochandler, or any signifying any model is acceptable. |
-nocustommarshal BOOLEAN | If true, the object creation will fail if it uses custom marshalling. Corresponds to the flag CLSCTX_NO_CUSTOM_MARSHAL. |
MEMBERTYPE DISPID RETTYPE NAME (PARAMS). Note PROTOTYPES is a text string, not a Tcl list. NAME is the name of the method or property. MEMBERTYPE is one of func, propget, or propput depending on whether the prototype is for a function (method), a property get operation or a property put operation. Note all three may be defined for a single name. DISPID is the dispatch index for the method or property in the component's dispatch table. This can generally be obtained from either the type library or the component's IDL definition. RETTYPE is the type of the return value (see below). PARAMS is a comma separated sequence of parameter definitions. A parameter definition has the form
?PARAMATTR? PARAMTYPE ?PARAMNAME?The parameter name PARAMNAME is optional but all parameters must be named or none must be named. The parameter attributes PARAMATTR are also optional. If specified, it is enclosed in [] and is a list of zero or more of in, out, inout and optional which indicate whether the parameter is a input parameter, an output parameter, or both, and whether it is optional. If no attributes are specified, they default to in.
define_dispatch_prototypes [name_to_iid ITwapiTest] { func 1 bstr GetBstr(int index, bstr[] strings, [out optional] int *lengthPtr) propget 2 int IntegerProp() propput 3 void IntegerProp(int) }
-name ITEMNAME | Normally the command processes all items of the specfied type. If this option is specified, the generated script only contains definitions for the specified ITEMNAME. |
-namespace NAMESPACE | By default, the enum values and classes are defined within a namespace corresponding to the library name, converted to lower case. This option may be specified to use a different namespace instead. NAMESPACE may also be specified as an empty string in which case the script will be run in the context of the namespace it is evaluated in. |
-output SCRIPTPATH | The command normally returns the generated script. If this option is specified, the generated script is written out to the specified file. SCRIPTPATH may also be specified as stdout in which case it is written out to standard output. |
-type TYPE | Normally, the generated script processes definitions of all types. If this option is specified, only definitions of that type are processed. TYPE must be one of enum, coclass, interface or module. |
(wish) 51 % progid_to_clsid InternetExplorer.Application {0002DF01-0000-0000-C000-000000000046}
Copyright © 2006-2012 Ashok P. Nadkarni