Re: Re: comtypes design question
Bruce Dodson <bruce_dodson <at> hotmail.com>
2005-10-06 22:10:38 GMT
Hi Thomas,
My notes are intermixed with yours.
"Thomas Heller" <theller <at> python.net> wrote in message
news:zmppdw2s.fsf <at> python.net...
> "Bruce Dodson" <bruce_dodson <at> hotmail.com> writes:
>
>> Cool! This sounds good to me.
>>
>> Assuming the concept of the alias module survives, a default alias name
>> could be taken from the library name within the typelib, could it not?
>> By
>> convention, this will usually be the same as the progid prefix.
>
> Do you mean something like 'ESRI_Geometry_Object_Library', from the
> example you give below?
No, that's what I am calling the library description. In makepy and in
Visual Basic's References dialog, I see the library description listed as
"ESRI Geometry Object Library", but in my code the library name is
esriGeometry. By library name, I meant from the ODL/IDL: "library
library_name { .... }".
Hmm, let me forget ArcObjects for a second and use an example you might have
seen before. Very likely, scrrun.dll is present and registered on your
computer. In Visual Basic 6, I can bring in the type information from this
DLL by going to Tools / References, and selecting "Microsoft Scripting
Runtime". But once it's loaded, the name you use in code is just
"Scripting". So in VB I can then write:
Dim fso As Scripting.FileSystemObject
Set fso = New Scripting.FileSystemObject
Also, in VB or even VBScript I can bring in the objects by progid using
CreateObject, even when it has not been referenced.
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")
In this case of course VB knows nothing about the type information, so it
provides no code completion, and uses IDispatch to resolve calls at run
time. So this is roughly equivalent to win32com.client.dynamic.Dispatch in
the pywin32 approach to COM.
Note that the the prefix, "Scripting" is the same in both places the
late-bound and early-bound versions. This is just by convention; there is
no rule that says "the progid prefix must be the same as the name recorded
in the typelib" but programmers have come to expect it.
I don't know if I'm using exactly the right terminology, but I hope that
clears up the terms that I used: library description, library name, and
progid prefix.
>> In CreateObject, I don't think there's any need to get at the coclass as
>> a
>> property of the returned instance. Also, it's kind of inconsistent: it
>> would mean instances created by CreateObject are a different type than
>> the
>> instances returned from, e.g., a method call.
>
> Access to the coclass is needed to get events, when the com object does
> not implement IProvideClassInfo2: the coclass has _outgoing_interfaces_.
Oh, I see. But I don't think you have to do it that way. In fact, I don't
think it will "suffice" to do it that way, because there will be cases where
you need to handle events for an object that you didn't instantiate through
comtypes.client.CreateObject. For example, it might be an instance that was
returned from a function call on some other object.
As far as I can tell, VB implements events by just querying the object for
IConnectionPointContainer, and using that to find the appropriate connection
point. As with most interface based programming, incompatibilities are
caught at run time.
(Aside: the VB6 "language" doesn't have a way to express which outbound
interface your code wants handle. So the programmer uses the coclass name
in the declaration: they say "I would like handle events for this coclass's
default outbound interface. This does not imply any relationship between
that coclass and the object that they eventually assign to that set of event
handlers.)
If need be, I can give you some elaborate examples to illustrate bizarre
misuses for VB's method of attaching event handlers to events.
Anyway, by the same token as VB, declare the events using a specific type,
but use QueryInterface and FindConnectionPoint to test the compatibility of
a given COM object with that interface. What do you think about something
like this as the high-level framework. It roughly matches what VB does, but
in a more Pythonic way.
First let's assume we have done the following first.
>> myCoolObjectFactory = CreateObject("myCoolLib.CoolObjectFactory")
>> myCoolLib = GetModule(myCoolObjectFactory)
>> myCoolObject = myCoolObjectFactory.giveMeSomethingCool()
Assume that there is a non-createable coclass called MyCoolClass whose
default interface is ICool and whose default outbound interface is
ICoolEvents. The object returned by makeSomethingCool supports ICool and
ICoolEvents, so for all intents and purposes it is an instance of
MyCoolClass.
Let's assume that event handlers are defined like this:
>> class
>> MyCoolEventHandler(comtypes.EventHandlerBase(myCoolLib.MyCoolClass)):
>> def OnCoolEvent(self):
>> print "dude!"
Here, the first line looks a little funny. EventHandlerBase is a function
that returns a generated class (either generated just in time, or perhaps
ahead of time - whatever works best) that has the necessary plumbing (e.g.
GetEvents and ReleaseEvents) to handle a specific outbound interface. You
can pass the function a generated coclass class object (in which case it
uses that coclass's default outbound interface) or a generated interface
class object (in which case it assumes that interface is an outbound
interface on some coclass, somewhere!). The base class provides default,
empty (pass) implementations for all of the event on that interface.
Once we have declared the event handler, we hook it up to a specific COM
object just by constructing an instance of the event handler.
>> myCoolEventHandler = MyCoolEventHandler(myCoolObject)
The constructor does several things:
1) queries myCoolObject for IConnectionPointContainer
2) uses FindConnectionPoint to get the appropriate connection point for the
outbound interface we have declared
3) hooks up our event handler instance to the connection point
4) holds a reference to the connection point and to the cookie from advise
5) of course, throws a meaningful exception if any of this fails
And the destructor tears it all down. The event handler won't outlast the
source object, because it holds a reference to the connection point which in
turn references the object.
How does that sound to you? If you have something better, that's cool too.
>> Another thought. When you were talking about how [out] parameters are
>> handled, it reminds me of a special issue that affects ArcObjects. In
>> several places in the ArcObjects interfaces, an array is expected, but
>> the typelib declares it as a single byref parameter, rather than a
>> SAFEARRAY.
>>
>> This is advantageous for C++ programmers in that it allows them to use
>> regular C / C++ arrays. For VB6, the syntax is a little
>> idiosyncratic, but since VB6's native type system is based on COM, it
>> still works out okay. However, for other languages that have to
>> marshal to / from their own type systems using auto-generated
>> wrappers, it can be a problem.
>>
>> In Python, one way to handle it is to trust the caller. If I pass an
>> array where the typelib says a single byref item is expected, the
>> wrapper should assume I know what I'm doing, and should handle this by
>> passing the underlying COM method a pointer to the first element. I
>> wouldn't expect the wrapper to automatically pack and unpack arbitrary
>> sequences, but at a minimum it needs to accept ctypes arrays and
>> safearrays.
>
> For [in] parameters, it already works this way. For [out] parameters,
> it is of course more complicated. But I fear there is not enough
> information in the type libraries to handle this automatically. There
> are at least some obscure IDL flags like [size_is()], or [string] which
> are, AFAIK, not present in the tlb files.
> It may be required to manually fix the generated code, or provide custom
> marshalling code.
Ah yes, I recall that the .NET bindings for ArcObjects are not just
auto-generated by the framework. Instead, ESRI provides their own .NET
assemblies, mostly auto-generated, but also having some minor tweaks. This
is probably one of the reasons for that.
Perhaps I will have to provide some "annotations" to comtypes at
wrapper-generation time, to help it sort this out. I don't want to maintain
a copy of the ArcObjects type information by hand (it's massive) but perhaps
a set of annotations that I can develop incrementally as I encounter cases
where there was not enough information in the typelib, and comtypes made a
reasonable guess that happened to be incorrect.
Since this is Python, no special notation is needed for the annotations.
They would just be in a dictionary or helper classes. It would contain
helpful information like, "hey, for this interface, that parameter declared
as a [out] long * is actually a pointer to an array, the array maximum size
is indicated in this parameter, and you can query the object for the
required size by running this code".
>> This would mean, however, that I might need to pass a buffer / array
>> for an [out] param, so it conflicts a bit with your recent
>> improvements. Not sure how to reconcile that...
>
> Currently it is not possible at all to pass the [out] value to a method
> call.
Hmm. I think that's an important capability, because for the sake of
performance, there are cases where I won't want to have comtypes allocate a
new buffer for each call. To get what I need, I might have to pretend the
parameter is [in, out] even though it's really [out].
> Any ideas how VB handles this stuff would be greatly appreciated.
I'll try to provide examples as we go along. I wish I could provide the
ArcObjects themselves, but they are licensed components. I have them on my
laptop and will contribute some testing for the project, and also try to
present a list of user stories to work against. As I get more familiar with
ctypes / comtypes, I will hopefully be able to make some contribution to the
code as well.
>> That's all I have for now. Hope it helps.
>
> I think so - thanks.
>
> Thomas
>
/Bruce
-------------------------------------------------------
This SF.Net email is sponsored by:
Power Architecture Resource Center: Free content, downloads, discussions,
and more. http://solutions.newsforge.com/ibmarch.tmpl