Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

TCOM: error "Type mismatch" for output collection parameter

138 views
Skip to first unread message

Harald Oehlmann

unread,
Feb 2, 2018, 9:45:22 AM2/2/18
to
Dear TCL'ers,

sorry, hard and hopeless question, but maybe, someone has an idea.

TCL controls an Active-X Server (Bar Tender bar code software).

The Visual Basic example says:

Dim btMsgs As BarTender.Messages = Nothing
btFormat.Print("Job1", True, -1, btMsgs)

The point is the parameter "btMsgs". This parameter is output only and
may return a list of Message objects. On input, it should be an
uninitialized object (written in the documentation).
I suppose, this is a collection of strings.

So, I tried to pass the uninitialized collection, but I always get the
result "type missmatch":

set msglist [list ""];$d Print "Job1" [expr {1==1}] -1 msglist
set msglist [list ""];
set msglist [binary format "x" 0];...
set msglist [::tcom::na];...
unset msglist;...
array set msglist {};...

So, I did not find the right format of the input variable.

VSStudio 2005 Prints as signature:

Function Print(Optional ByVal PrintJobName As String = "", Optional
ByVal WaitForSpoolJobToComplete As Boolean = True, Optional ByVal
TimeoutMs As Integer = -1, Optional ByRef Messages As BarTender.Messages
= Nothing) As BarTender.BtPrintResult

The Messages object type is a collection of Message (subclass of
IEnumerable) with methods GetMessage, Item and Count.

The Message object has a set of fields (Message, Type, etc).

The Message and Messages Objects are defined in the class BarTender.

They both have an interface IBTMessages and IBTMessage

Is there a way to get an instance of the Message object ?

So something like:
% set m [::tcom::ref createobject BarTender.Messages]
0x800401f3 {Invalid class string}

I also tried to read the type library which works.

% ::tcom::import {C:\Program Files\Seagull\BarTender Suite\BarTend.exe}
BarTender

Then, I may get a handle to an Application object (which is the main
entry point, and which also works for tcom::ref createobject
BarTender.Application):

% set ta [::BarTender::Application]
::tcom::handle0x0500E188

But not for the Messages object:
% set m [::BarTender::Messages]
0x80040154 {Class not registered}

Well, if anybody has an idea, this would be greatly appreciated.

Thank you,
Harald

Harald Oehlmann

unread,
Apr 17, 2018, 11:03:03 AM4/17/18
to
Am 02.02.2018 um 15:44 schrieb Harald Oehlmann:
> TCL controls an Active-X Server (Bar Tender bar code software).
>
> The Visual Basic example says:
>
> Dim btMsgs As BarTender.Messages = Nothing
> btFormat.Print("Job1", True, -1, btMsgs)
>
> The point is the parameter "btMsgs". This parameter is output only and
> may return a list of Message objects. On input, it should be an
> uninitialized object (written in the documentation).
> I suppose, this is a collection of strings.

Answering on my own message, I am one step further.
The parameter to pass is a com object.
Now, I try with twapi.

When I generate the commands for the typelib using
twapi::generate_code_from_typelib, I get:

...
namespace eval bartender {
...
twapi::class create Messages {
superclass ::twapi::Automation
constructor {args} {
set ifc [twapi::com_create_instance
"{2B52174E-AAA4-443D-945F-568F60610F55}" -interface IDispatch -raw {*}$args]
next [twapi::IDispatchProxy new $ifc
"{2B52174E-AAA4-443D-945F-568F60610F55}"]
if {[string length "{365EE8DD-7FBD-44CC-8BCA-FF81E54FDF9B}"]} {
my -interfaceguid "{365EE8DD-7FBD-44CC-8BCA-FF81E54FDF9B}"
}
}
}
...

So, twapi has generated a class. I try to create an object of this class:
% set hmsgs [::bartender::Messages new]
Class not registered
% set ifc [twapi::com_create_instance
"{2B52174E-AAA4-443D-945F-568F60610F55}" -interface IDispatch -raw]
Class not registered

Is there any additional action to do to make this work ?
Or is it my task to register the class somewhere ?

Thank you for any idea,
Harald



Ashok

unread,
Apr 18, 2018, 1:41:43 AM4/18/18
to
On 4/17/2018 8:31 PM, Harald Oehlmann wrote:> Is there any additional
action to do to make this work ?> Or is it my task to register the class
somewhere ?> > Thank you for any idea,
The COM server application is supposed to do the registration. Since
Bartender.Application is registered but Messages is not, this generally
implies you are not supposed to directly create a Messages object.
Rather some sequence of method calls starting with Bartender.Application
should give you a Messages object.

My guess looking at your code is that the expected way of working is
something like

Create a Bartender.Application object bar
From bar get a btFormat (not clear in you example how you do that)
From btFormat get a Messages collection object via the Print method

So your problem boils down to passing that last output parameter to the
Print method. I'm not sure about tcom, but in twapi either the name of
an unset variable or passing [outvar varname] is supposed to do the trick.

Something like

set barApp [comobj Bartender.Application]
set btFormat [$barApp SomeMethodToGetBtFormat...]
unset -nocomplain msgs
$btFormat Print "Job1" 1 -1 msgs

Some experimentation might be required :-(

/Ashok

Harald Oehlmann

unread,
Apr 18, 2018, 10:41:58 AM4/18/18
to
Ashok,

thank you for the answer. All your analysis is correct.
The only issue is,

Am 18.04.2018 um 07:41 schrieb Ashok:
> So your problem boils down to passing that last output parameter to the
> Print method. I'm not sure about tcom, but in twapi either the name of
> an unset variable or passing [outvar varname] is supposed to do the trick.
>
> Something like
> unset -nocomplain msgs
> $btFormat Print "Job1" 1 -1 msgs

So, some tries:
% $StateRun(COM_hDocu) -call Print "Job1" 1 -1 [twapi::outvar varname]
Parameter error. Offending parameter position 4. Typenkonflikt.
% set msgname [twapi::outvar lmsgs]
lmsgs
% $StateRun(COM_hDocu) -call Print "Job1" 1 -1 msgs
Parameter error. Offending parameter position 4. Typenkonflikt.
% unset -nocomplain msglist
% $StateRun(COM_hDocu) -call Print "Job1" 1 -1 msglist
Parameter error. Offending parameter position 4. Typenkonflikt.

When I search the output of "generate_code_from_typelib" for "Print", I get:
::twapi::dispatch_prototype_set {{A27F12A1-4305-11D2-BE48-004005A04EDF}}
Print 0 1 {48 0 1 {29 50334848} {{8 {49 {8 {}}}} {11 {49 {11 1}}} {3 {49
{3 -1}}} {{26 {26 {29 coclass
{{2B52174E-AAA4-443D-945F-568F60610F55}}}}} {50 {3 0}}}} {PrintJobName
WaitForSpoolJobToComplete TimeoutMs Messages}}

The interface of Messages is:
# Dispatch Interface IBtMessages
# IBtMessages Methods
::twapi::dispatch_prototype_set {{365EE8DD-7FBD-44CC-8BCA-FF81E54FDF9B}}
QueryInterface 0 1 {1610612736 0 1 24 {{{26 {29 record
{{00000000-0000-0000-0000-000000000000}}}} 1} {{26 {26 24}} 2}} {riid
ppvObj}}
...

and the created creator is:
# Coclass Messages
twapi::class create Messages {
superclass ::twapi::Automation
constructor {args} {
set ifc [twapi::com_create_instance
"{2B52174E-AAA4-443D-945F-568F60610F55}" -interface IDispatch -raw {*}$args]
next [twapi::IDispatchProxy new $ifc
"{2B52174E-AAA4-443D-945F-568F60610F55}"]
if {[string length "{365EE8DD-7FBD-44CC-8BCA-FF81E54FDF9B}"]} {
my -interfaceguid "{365EE8DD-7FBD-44CC-8BCA-FF81E54FDF9B}"
}
}
}

I don't see, that it is output only. The documentation says, that it is
input/output, but must be passed in as uninitialized object.

VB.net example:
Dim btMsgs As BarTender.Messages = Nothing
btFormat.Print("Job1", True, -1, btMsgs)

C# example:
BarTender.Messages btMsgs;
btFormat.Print("Job1", true, -1, out btMsgs);

Interesting is the "out" keyword here.

Some other tries:

% set hMsg [twapi::tclcast empty ""]
% $StateRun(COM_hDocu) -call Print "Job1" 1 -1 hMsg
also with
% set hMsg [twapi::vt_empty]
% set hMsg [twapi::vt_null]

I suppose, the input type should be "VT_DISPATCH" or "VT_UNKNOWN", but
there is no TCL version for that.

I tried to find out what the equivalent in C++ of the examples in VB/C# is.

This pages says that VB Nothing is vt_empty:
https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-objects
ok...

Anyway, no step further so far.

Thank you anyway,
Harald

Ashok

unread,
Apr 20, 2018, 12:10:30 PM4/20/18
to
Looking at the documentation, I think the problem is that they do not
the GetTypeInfo interface that is required to get type information for
the method calls. I suspect both twapi and tcom fail for this reason. It
is possible to use the type library but hooking up the object to the
information from the type library is a bit tricky. I'll try to take a
look over the weekend.

/Ashok

Harald Oehlmann

unread,
Apr 20, 2018, 12:14:42 PM4/20/18
to
This is highly appreciated.
I have also supposed that. Thats why I tried to load the type library
into tcom and twapi. Unfortunately, this did not help.
I supposed, all the "setinterface" calls would avoid the live-lookup of
the type.
If I try the discovery calls (twapi -print method), there is just no output.

Thank you,
Harald

pal...@yahoo.com

unread,
Apr 23, 2018, 11:31:48 PM4/23/18
to
I've enhanced twapi to support user defined types (UDT's) which was the issue here because it appears Bartender provides a type library which uses UDT's but does not implement the ITypeInfo interface for getting runtime information from the object.

You can download the required 4.3b0 from the Development directory in twapi sourceforge area.

Below is an explanatory session.

Generate the type library interface and read it into the interp. Obviously
this generation is a one-time operation.

% generate_code_from_typelib d:/Programs/Seagull/BarTender\ Suite/bartend.exe -output bartend.tcl
% source bartend.tcl

Create the toplevel application object

% set bar [comobj Bartender.Application]
::oo::Obj701

Because there is no runtime information, we have to now explicitly "declare"
the type of the object. This is similar to the Visual Basic statement
"dim bar as Bartender.Application" except that because Tcl does not have
typed variables, we have to make the declaration on the object itself.

% $bar -instanceof bartender::Application

Get the Formats object from the application.

% set fmts [$bar Formats]
::oo::Obj703

Again, we have to declare the object type. This is just an alternative
way to declare that is functionally identical to the -instanceof method above.

% bartender::declare Formats $fmts

Now open the document and type it

% set doc [$fmts Open d:/temp/test.btw 0 ""]
::oo::Obj705
% $doc -instanceof bartender::Format

Invoke the Print method. msgs is an unset variable since it is purely an
output parameter.

% $doc Print Job1 1 -1 msgs
0

TWAPI returns output parameter values in raw form so we need to convert it
into a comobj using variant_value. Then, type it.
% set msgs [variant_value $msgs 0 0]
::oo::Obj709
% $msgs -instanceof bartender::Messages

Retrieve the first message and display it.
% set msg [$msgs Item 1]
::oo::Obj711
% $msg -instanceof bartender::Message
% $msg Message
BarTender successfully sent the print job to the spooler.

Job Name: Job1
Document: test.btw
Printer: Microsoft Print to PDF
%

A bit long-winded, yes, because of the need for type declarations.
But no more so than the corresponding visual basic which also require
the Dim declarations.

Let me know of any issues

/Ashok

Harald Oehlmann

unread,
Apr 24, 2018, 2:18:48 AM4/24/18
to
Great !
I am on travel at the moment, check tomorrow !

Thanks,
Harald

Harald Oehlmann

unread,
Apr 25, 2018, 10:38:48 AM4/25/18
to
Hi Ashok,

the solution works very good, thank you.
To provide this feels like a great gift to me, thank you !

For me, the only additional command necessary was:
bartender::declare Formats $fmts

But the other "-instanceof" and declare commands add additional comfort.
For example, that '""' (two double quotes) is recognized as an empty
string as a parameter. I still had always set '[string range " " 0 0]'
for the empty string, which was also necessary for tcom.

Example:
set hDocu [$hDocs Open $NativeName 1 ""]
is only accepted after:
bartender::declare Formats $hDocs
otherwise, the following format is required:
set hDocu [$hDocs Open $NativeName 1 [string range " " 0 0]]

In consequence, the modification brings high value to twapi com support.

So thank you again, this is highly appreciated,
Harald

Am 24.04.2018 um 05:31 schrieb pal...@yahoo.com:

Harald Oehlmann

unread,
Apr 25, 2018, 10:46:52 AM4/25/18
to
Am 24.04.2018 um 05:31 schrieb pal...@yahoo.com:
> % set bar [comobj Bartender.Application]
> ::oo::Obj701
>
> Because there is no runtime information, we have to now explicitly "declare"
> the type of the object. This is similar to the Visual Basic statement
> "dim bar as Bartender.Application" except that because Tcl does not have
> typed variables, we have to make the declaration on the object itself.
>
> % $bar -instanceof bartender::Application

What about the following alternative for the upper commands:
set bar [::bartender::Application new]
Is this equivalent to both upper commands at once or is the
"-instanceof" call still required?

When I look to the generated code, only parameter -interfaceguid is set.

Thank you,
Harald

Ashok

unread,
Apr 26, 2018, 3:57:47 AM4/26/18
to
If you create the object as

set bar [comobj Bartender.Application]

then you need do do

$bar -instanceof bartender::Application.

However, if you create it using New

set bar [bartender::Application new]

then you should not have to do the -instanceof since the interface guid
is already encoded in that class as you saw.

/Ashok

Harald Oehlmann

unread,
Apr 26, 2018, 5:22:56 AM4/26/18
to
Thank you for the clarification.
Thus "-instanceof" is basically the same as "-interfaceguid".

Thank you,
Harald
0 new messages