Bump cherrypy from 18.8.0 to 18.9.0 (#2266)

* Bump cherrypy from 18.8.0 to 18.9.0

Bumps [cherrypy](https://github.com/cherrypy/cherrypy) from 18.8.0 to 18.9.0.
- [Changelog](https://github.com/cherrypy/cherrypy/blob/main/CHANGES.rst)
- [Commits](https://github.com/cherrypy/cherrypy/compare/v18.8.0...v18.9.0)

---
updated-dependencies:
- dependency-name: cherrypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update cherrypy==18.9.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2024-03-24 15:25:44 -07:00 committed by GitHub
commit faef9a94c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
673 changed files with 159850 additions and 11583 deletions

View file

@ -0,0 +1,104 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>Generated Python COM Support</TITLE>
<META NAME="Version" CONTENT="8.0.3410">
<META NAME="Date" CONTENT="10/11/96">
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY TEXT="#000000" LINK="#0000ff" VLINK="#800080" BGCOLOR="#ffffff">
<P><IMG SRC="image/pycom_blowing.gif" WIDTH=549 HEIGHT=99 ALT="Python and COM - Blowing the others away"></P>
<H1>Generated Python COM Support</H1>
<P>This file describes how the Python COM extensions support "generated files". The information contained here is for expert Python users, and people who need to take advantage of the advanced features of the support. More general information is available in the <A HREF="QuickStartClientCom.html">Quick Start to Client Side COM</A> documentation.</P>
<H2>Introduction</H2>
<P>Generated Python COM support means that a .py file exists behind a particular COM object. This .py file is created by a generation process from a COM type library.</P>
<P>This documentation talks about the process of the creation of the .py files.</P>
<H2>Design Goals</H2>
<P>The main design goal is that the Python programmer need not know much about the type library they wish to work with. They need not know the name of a specific Python module to use a type library. COM uses an IID, version and LCID to identify a type library. Therefore, the Python programmer only need know this information to obtain a Python module.</P>
<H2>How to generate support files</H2>
<P>Support files can be generated either "off-line" by the makepy utility, or in custom Python code.</P>
<P>Using makepy is in many ways far simpler - you simply pick the type library and you are ready to go! The <A HREF="QuickStartClientCom.html">Quick Start to Client Side COM</A> documentation describes this process.</P>
<P>Often however, you will want to use code to ensure the type library has been processed. This document describes that process.</P>
<H2>Usage</H2>
<P>The win32com.client.gencache module implements all functionality. As described above, if you wish to generate support from code, you need to know the IID, version and LCID of the type library.</P>
<P>The following functions are defined. The best examples of their usage is probably in the Pythonwin OCX Demos, and the COM Test Suite (particularly testMSOffice.py)</P>
<P>Note that the gencache.py file supports being run from the command line, and provides some utilities for managing the cache. Run the file to see usage options.</P>
<H2>Using makepy to help with the runtime generation</H2>
<P>makepy supports a "-i" option, to print information about a type library. When you select a type library, makepy will print out 2 lines of code that you cant paste into your application. This will then allow your module to generate the makepy .py file at runtime, but will only take you a few seconds!</P>
<H2>win32com.client.gencache functions</H2>
<H3>def MakeModuleForTypelib(typelibCLSID, lcid, major, minor, progressInstance = None):</H3>
<P>Generate support for a type library.</P>
<P>Given the IID, LCID and version information for a type library, generate and import the necessary support files.</P>
<B><P>Returns</P>
</B><P>The Python module. No exceptions are caught.</P>
<B><P>Params</P>
</B><I><P>typelibCLSID</I><BR>
IID of the type library.</P>
<I><P>major</I><BR>
Integer major version.</P>
<I><P>minor</I><BR>
Integer minor version.</P>
<I><P>lcid</I><BR>
Integer LCID for the library.</P>
<I><P>progressInstance</I><BR>
A class instance to use as the progress indicator, or None to use the default GUI one.&nbsp;</P>
<H3>def EnsureModule(typelibCLSID, lcid, major, minor, progressInstance = None):</H3>
<P>Ensure Python support is loaded for a type library, generating if necessary.</P>
<P>Given the IID, LCID and version information for a type library, check and if necessary generate, then import the necessary support files.</P>
<P>Returns:</P>
<P>The Python module. No exceptions are caught during the generate process.</P>
<B><P>Params</P>
</B><I><P>typelibCLSID</I><BR>
IID of the type library.</P>
<I><P>major</I><BR>
Integer major version.</P>
<I><P>minor</I><BR>
Integer minor version.</P>
<I><P>lcid</I><BR>
Integer LCID for the library.</P>
<I><P>progressInstance</I><BR>
A class instance to use as the progress indicator, or None to use the default GUI one.&nbsp;</P>
<P>&nbsp;</P>
<H3>def GetClassForProgID(<I>progid</I>):</H3>
<P>Get a Python class for a Program ID</P>
<P>Given a Program ID, return a Python class which wraps the COM object</P>
<B><P>Returns</P>
</B><P>The Python class, or None if no module is available.</P>
<B><P>Params</P>
</B><I><P>progid<BR>
</I>A COM ProgramID or IID (eg, "Word.Application")</P>
<P>&nbsp;</P>
<H3>def GetModuleForProgID(progid):</H3>
<P>Get a Python module for a Program ID</P>
<P>Given a Program ID, return a Python module which contains the class which wraps the COM object.</P>
<B><P>Returns</P>
</B><P>The Python module, or None if no module is available.</P>
<B><P>Params:</P>
</B><I><P>progid <BR>
</I>A COM ProgramID or IID (eg, "Word.Application")</P>
<P>&nbsp;</P>
<H3>def GetModuleForCLSID(clsid):</H3>
<P>Get a Python module for a CLSID</P>
<P>Given a CLSID, return a Python module which contains the class which wraps the COM object.</P>
<B><P>Returns</P>
</B><P>The Python module, or None if no module is available.</P>
<B><P>Params</P>
</B><I><P>progid<BR>
</I>A COM CLSID (ie, not the description)</P>
<P>&nbsp;</P>
<H3>def GetModuleForTypelib(typelibCLSID, lcid, major, minor):</H3>
<P>Get a Python module for a type library ID</P>
<B><P>Returns</P>
</B><P>An imported Python module, else None</P>
<B><P>Params</B>:</P>
<I><P>typelibCLSID</I><BR>
IID of the type library.</P>
<I><P>major</I><BR>
Integer major version.</P>
<I><P>minor</I><BR>
Integer minor version</P>
<I><P>lcid</I><BR>
Integer LCID for the library.</P></BODY>
</HTML>

View file

@ -0,0 +1,90 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>Untitled</TITLE>
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080">
<H1><IMG SRC="image/pycom_blowing.gif" WIDTH=549 HEIGHT=99 ALT="Python and COM - Blowing the others away"></H1>
<H1>Python and COM - Implementation Details </H1>
<H2>Introduction </H2>
<P>This document describes the technical implementation of the COM support in Python. It is primarily concerned with the underlying C++ interface to COM, although general Python issues are touched. </P>
<P>This document is targeted at people who wish to maintain/enhance the standard COM support (typically by writing extension modules). For information on using Python and COM from a Python programmers perspective, please see the <A HREF="docindex.html">documentation index</A>. </P>
<H2>General COM Support. </H2>
<P>COM support in Python can be broken into 2 general areas - C++ support, and Python support. C++ support exists in the core PythonCOM module (plus any PythonCOM extension modules). Python support exists in the .py files that accompany the core module. </P>
<H2>Naming Conventions </H2>
<P>The naming conventions used by Python code will be: </P>
<UL>
<LI>The Python "New Import" (ni) module will be used, allowing packages, or nested modules. </LI>
<LI>The package name will be "win32com". </LI>
<LI>The core module name will be "pythoncom" (ie, "win32com.pythoncom") </LI></UL>
<P>The rest of the naming conventions are yet to be worked out. </P>
<H2>Core COM support. </H2>
<P>This section is involved with the core C++ support in "pythoncom". </P>
<P>The organisation of PythonCOM support falls into 3 discrete areas. </P>
<H3>COM Client Support </H3>
<P>This is the ability to manipulate other COM objects via their exposed interface. This includes use of IDispatch (eg using Python to start Microsoft Word, open a file, and print it.) but also all client side IUnknown derived objects fall into this category, including ITypeLib and IConnectionPoint support. </P>
<H3>COM Server Support </H3>
<P>This is ability for Python to create COM Servers, which can be manipulated by another COM client. This includes server side IDispatch (eg, Visual Basic starting a Python interpreter, and asking it to evaluate some code) but also all supported server side IUnknown derived classes. </P>
<H3>Python/COM type and value conversion </H3>
<P>This is internal code used by the above areas to managed the conversion to and from Python/COM types and values. This includes code to convert an arbitrary Python object into a COM variant, manages return types, and a few other helpers. </P>
<H2>COM Structures and Python Types </H2>
<P>OLE supports many C level structures for the COM API, which must be mapped to Python. </P>
<H3>VARIANT </H3>
<P>Variants are never exposed as such to Python programs. The internal framework always converts all variants to and from Python types. In some cases, type descriptions may be used, which force specific mappings, although in general the automatic conversion works fine. </P>
<H3>TYPEDESC </H3>
<P>A tuple, containing the elements of the C union. This union will be correctly decoded by the support code. </P>
<H3>ELEMDESC </H3>
<P>A tuple of TYPEDESC and PARAMDESC objects. </P>
<H3>FUNCDESC </H3>
<P>A funcdesc is a large and unwieldy tuple. Documentation to be supplied. </P>
<H3>IID/CLSID </H3>
<P>A native IID in Python is a special type, defined in pythoncom. Whenever a CLSID/IID is required, typically either an object, a tuple of type "iii(iiiiiiii)" or string can be used. </P>
<P>Helper functions are available to convert to and from IID/CLSID and strings. </P>
<H2>COM Framework </H2>
<P>Both client and server side support have a specific framework in place to assist in supporting the widest possible set of interfaces. The framework allows external extension DLLs to be written, which extend the interfaces available to the Python user. </P>
<P>This allows the core PythonCOM module to support a wide set of common interfaces, and other extensions to support anything obscure. </P>
<H3>Client Framework </H3>
<H4>QueryInterface and Types </H4>
<P>When the only support required by Python is IDispatch, everything is simple - every object returned from QueryInterface is a PyIDispatch object. But this does not extend to other types, such as ITypeLib, IConnectionPoint etc., which are required for full COM support. </P>
<P>For example, consider the following C++ psuedo-code: </P>
<CODE><P>IConnectionPoint *conPt;<BR>
someIDispatch-&gt;QueryInterface(IID_IConnectionPoint, (void **)&amp;conPt);<BR>
// Note the IID_ and type of the * could be anything!</CODE> </P>
<P>This cast, and knowledge of a specific IID_* to type must be simulated in Python. </P>
<P>Python/COM will therefore maintain a map of UID's to Python type objects. Whenever QueryInterface is called, Python will lookup this map, to determine if the object type is supported. If the object is supported, then an object of that type will be returned. If the object is not supported, then a PyIUnknown object will be returned. </P>
<P>Note that PyIDispatch will be supported by the core engine. Therefore: </P>
<CODE><P>&gt;&gt;&gt; disp=someobj.QueryInterface(win32com.IID_Dispatch) </P>
</CODE><P>will return a PyIDispatch object, whereas </P>
<CODE><P>&gt;&gt;&gt; unk=someobj.QueryInterface(SomeUnknownIID) # returns PyIUnknown<BR>
&gt;&gt;&gt; disp=unk.QueryInterface(win32com.IID_Dispatch) <BR>
&gt;&gt;&gt; unk.Release() # Clean up now, rather than waiting for unk death.</CODE> </P>
<P>Is needed to convert to an IDispatch object. </P>
<H4>Core Support </H4>
<P>The core COM support module will support the IUnknown, IDispatch, ITypeInfo, ITypeLib and IConnectionPointContainer and IConnectionPoint interfaces. This implies the core COM module supports 6 different OLE client object types, mapped to the 6 IID_*'s representing the objects. (The IConnection* objects allow for Python to repsond to COM events) </P>
<P>A psuedo-inheritance scheme is used. The Python types are all derived from the Python IUnknown type (PyIUnknown). Therefore all IUnknown methods are automatically available to all types, just as it should be. The PyIUnknown type manages all object reference counts and destruction. </P>
<H4>Extensibility </H4>
<P>To provide the above functionality, a Python map is provided, which maps from a GUID to a Python type object. </P>
<P>The advantage of this scheme is an external extension modules can hook into the core support. For example, imagine the following code: </P>
<CODE><P>&gt;&gt;&gt; import myextracom # external .pyd supporting some interface.<BR>
# myextracom.pyd will do the equivilent of</CODE> </P>
<CODE><P># pythoncom.mapSupportedTypes(myextracom.IID_Extra, myextracom.ExtraType) <BR>
&gt;&gt;&gt; someobj.QueryInterface(myextracom.IID_Extra)</CODE> </P>
<P>Would correctly return an object defined in the extension module. </P>
<H3>Server Framework </H3>
<H4>General Framework </H4>
<P>A server framework has been put in place which provides the following features: </P>
<P>All Interfaces provide VTBL support - this means that the Servers exposed by Python are callable from C++ and other compiled languages. </P>
<P>Supports full "inproc" servers. This means that no external .EXE is needed making Python COM servers available in almost all cases. </P>
<P>An extensible model which allows for extension modules to provide server support for interfaces defined in that module. A map is provided which maps from a GUID to a function pointer which creates the interface. </P>
<H3>Python and Variant Types Conversion </H3>
<P>In general, Python and COM are both "type-less". COM is type-less via the VARIANT object, which supports many types, and Python is type-less due to its object model. </P>
<P>There are a number of areas where Python and OLE clash. </P>
<H4>Parameters and conversions. </H4>
<P>For simple calls, there are 2 helpers available which will convert to and from PyObjects and VARIANTS. The call to convert a Python object to a VARIANT is simple in that it returns a VARIANT of the most appropriate type for the Python object - ie, the type of the Python object determines the resulting VARIANT type. </P>
<P>There are also more complex conversion routines available, wrapped in a C++ helper class. Typically, these helpers are used whenever a specific variant type is known (eg, when an ITypeInfo is available for the object being used). In this case, all efforts are made to convert the Python type to the requested variant type - ie, in this situation, the VARIANT type determines how the Python object is coerced. In addition, this code supports the use of "ByRef" and pointer paramaters, providing and freeing any buffers necessary for the call. </P></BODY>
</HTML>

View file

@ -0,0 +1,82 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>Quick Start to Client side COM and Python</TITLE>
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080">
<H1>Quick Start to Client side COM and Python</H1>
<H2>Introduction</H2>
<P>This documents how to quickly start using COM from Python. It is not a thorough discussion of the COM system, or of the concepts introduced by COM.</P>
<P>Other good information on COM can be found in various conference tutorials - please see <A HREF="http://starship.python.net/crew/mhammond/conferences">the collection of Mark's conference tutorials</A></P>
<P>For information on implementing COM objects using Python, please see <A HREF="http://www.python.org/windows/win32com/QuickStartServerCom.html">a Quick Start to Server side COM and Python</A></P>
<P>In this document we discuss the following topics:</P>
<UL>
<LI><A HREF="#Using">Using a COM object from Python</A> </LI>
<LI><A HREF="#WhatObjects">How do I know which objects are available?</A> </LI>
<LI><A HREF="#StaticDispatch">Static Dispatch/Type Safe objects (using the new improved makepy.py)</A></LI>
<LI><A HREF="#UsingComConstants">Using COM Constants with makepy.</A></LI></UL>
<H2>Quick Start</H2>
<H3><A NAME="Using">To use a COM object from Python</A></H3>
<CODE><P>import win32com.client<BR>
o = win32com.client.Dispatch("Object.Name")<BR>
o.Method()<BR>
o.property = "New Value"<BR>
print o.property</P>
</CODE><P>Example</P>
<CODE><P>o = win32com.client.Dispatch("Excel.Application")<BR>
o.Visible = 1<BR>
o.Workbooks.Add() # for office 97 95 a bit different!<BR>
o.Cells(1,1).Value = "Hello"</CODE> </P>
<P>And we will see the word "Hello" appear in the top cell. </P>
<H3><A NAME="WhatObjects">How do I know which methods and properties are available?</A></H3>
<P>Good question. This is hard! You need to use the documentation with the products, or possibly a COM browser. Note however that COM browsers typically rely on these objects registering themselves in certain ways, and many objects to not do this. You are just expected to know.</P>
<H4>The Python COM browser</H4>
<P>PythonCOM comes with a basic COM browser that may show you the information you need. Note that this package requires Pythonwin (ie, the MFC GUI environment) to be installed for this to work.</P>
<P>There are far better COM browsers available - I tend to use the one that comes with MSVC, or this one!</P>
<P>To run the browser, simply select it from the Pythonwin <I>Tools</I> menu, or double-click on the file <I>win32com\client\combrowse.py</I></P>
<H2><A NAME="StaticDispatch">Static Dispatch (or Type Safe) objects</A></H2>
<P>In the above examples, if we printed the '<CODE>repr(o)</CODE>' object above, it would have resulted in</P>
<CODE><P>&lt;COMObject Excel.Application&gt;</P>
</CODE><P>This reflects that the object is a generic COM object that Python has no special knowledge of (other than the name you used to create it!). This is known as a "dynamic dispatch" object, as all knowledge is built dynamically. The win32com package also has the concept of <I>static dispatch</I> objects, which gives Python up-front knowledge about the objects that it is working with (including arguments, argument types, etc)</P>
<P>In a nutshell, Static Dispatch involves the generation of a .py file that contains support for the specific object. For more overview information, please see the documentation references above.</P>
<P>The generation and management of the .py files is somewhat automatic, and involves one of 2 steps:</P>
<UL>
<LI>Using <I>makepy.py</I> to select a COM library. This process is very similar to Visual Basic, where you select from a list of all objects installed on your system, and once selected the objects are magically useable. </LI></UL>
<P>or</P>
<UL>
<LI>Use explicit code to check for, and possibly generate, support at run-time. This is very powerful, as it allows the developer to avoid ensuring the user has selected the appropriate type library. This option is extremely powerful for OCX users, as it allows Python code to sub-class an OCX control, but the actual sub-class can be generated at run-time. Use <I>makepy.py</I> with a </I>-i</I> option to see how to include this support in your Python code.</LI></UL>
<P>The <I>win32com.client.gencache</I> module manages these generated files. This module has <A HREF="GeneratedSupport.html">some documentation of its own</A>, but you probably don't need to know the gory details!</P>
<H3>How do I get at the generated module?</H3>
<P>You will notice that the generated file name is long and cryptic - obviously not designed for humans to work with! So how do you get at the module object for the generated code?</P>
<P>Hopefully, the answer is <I>you shouldn't need to</I>. All generated file support is generally available directly via <I>win32com.client.Dispatch</I> and <I>win32com.client.constants</I>. But should you ever really need the Python module object, the win32com.client.gencache module has functions specifically for this. The functions GetModuleForCLSID and GetModuleForProgID both return Python module objects that you can use in your code. See the docstrings in the gencache code for more details.</P>
<H3>To generate Python Sources supporting a COM object</H3>
<H4>Example using Microsoft Office 97.</H4>
<P>Either:</P>
<UL>
<LI>Run '<CODE>win32com\client\makepy.py</CODE>' (eg, run it from the command window, or double-click on it) and a list will be presented. Select the Type Library '<CODE>Microsoft Word 8.0 Object Library</CODE>' </LI>
<LI>From a command prompt, run the command '<CODE>makepy.py "Microsoft Word 8.0 Object Library"</CODE>' (include the double quotes). This simply avoids the selection process. </LI>
<LI>If you desire, you can also use explicit code to generate it just before you need to use it at runtime. Run <CODE>'makepy.py -i "Microsoft Word 8.0 Object Library"</CODE>' (include the double quotes) to see how to do this.</LI></UL>
<P>And that is it! Nothing more needed. No special import statements needed! Now, you simply need say</P>
<CODE><P>&gt;&gt;&gt; import win32com.client</P>
<P>&gt;&gt;&gt; w=win32com.client.Dispatch("Word.Application")</P>
<P>&gt;&gt;&gt; w.Visible=1</P>
<P>&gt;&gt;&gt; w</P>
<P>&lt;win32com.gen_py.Microsoft Word 8.0 Object Library._Application&gt;</P>
</CODE><P>Note that now Python knows the explicit type of the object.</P>
<H3><A NAME="UsingComConstants">Using COM Constants</A></H3>
<P>Makepy automatically installs all generated constants from a type library in an object called <I>win32com.clients.constants</I>. You do not need to do anything special to make these constants work, other than create the object itself (ie, in the example above, the constants relating to <I>Word</I> would automatically be available after the <CODE>w=win32com.client.Dispatch("Word.Application</CODE>") statement<CODE>.</P>
</CODE><P>For example, immediately after executing the code above, you could execute the following:</P>
<CODE><P>&gt;&gt;&gt; w.WindowState = win32com.client.constants.wdWindowStateMinimize</P>
</CODE><P>and Word will Minimize.</P></BODY>
</HTML>

View file

@ -0,0 +1,195 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>Quick Start to Server Side COM and Python</TITLE>
<META NAME="Version" CONTENT="8.0.3410">
<META NAME="Date" CONTENT="10/11/96">
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY TEXT="#000000" LINK="#0000ff" VLINK="#800080" BGCOLOR="#ffffff">
<H1>Quick Start to Server side COM and Python</H1>
<H2>Introduction</H2>
<P>This documents how to quickly start implementing COM objects in Python. It is not a thorough discussion of the COM system, or of the concepts introduced by COM.</P>
<P>For more details information on Python and COM, please see the <A HREF="http://www.python.org/windows/win32com/COMTutorial/index.htm">COM Tutorial given by Greg Stein and Mark Hammond at SPAM 6 (HTML format)</A> or download the same tutorial <A HREF="http://www.python.org/windows/win32com/COMTutorial.ppt">in PowerPoint format.</A></P>
<P>For information on using external COM objects from Python, please see <A HREF="QuickStartClientCom.html">a Quick Start to Client side COM and Python</A>.</P>
<P>In this document we discuss the <A HREF="#core">core functionality</A>, <A HREF="#Registering">registering the server</A>, <A HREF="#testing">testing the class</A>, <A HREF="#debugging">debugging the class</A>, <A HREF="#Exception">exception handling</A> and <A HREF="#Policies">server policies</A> (phew!)</P>
<H2><A NAME="core">Implement the core functionality</A></H2>
<H3><A NAME="Using">Implement a stand-alone Python class with your functionality</A></H3>
<CODE><P>class HelloWorld:</P><DIR>
<DIR>
<P>def __init__(self):</P><DIR>
<DIR>
<P>self.softspace = 1</P>
<P>self.noCalls = 0</P></DIR>
</DIR>
<P>def Hello(self, who):</P><DIR>
<DIR>
<P>self.noCalls = self.noCalls + 1</P>
<P># insert "softspace" number of spaces</P>
<P>return "Hello" + " " * self.softspace + who</P></DIR>
</DIR>
</DIR>
</DIR>
</CODE><P>This is obviously a very simple server. In particular, custom error handling would be needed for a production class server. In addition, there are some contrived properties just for demonstration purposes.</P>
<H3>Make Unicode concessions</H3>
<P>At this stage, Python and Unicode dont really work well together. All strings which come from COM will actually be Unicode objects rather than string objects.</P>
<P>To make this code work in a COM environment, the last line of the "Hello" method must become:</P><DIR>
<DIR>
<DIR>
<DIR>
<CODE><P>return "Hello" + " " * self.softspace + str(who)</P></DIR>
</DIR>
</DIR>
</DIR>
</CODE><P>Note the conversion of the "who" to "str(who)". This forces the Unicode object into a native Python string object.</P>
<P>For details on how to debug COM Servers to find this sort of error, please see <A HREF="#debugging">debugging the class</A></P>
<H3>Annotate the class with win32com specific attributes</H3>
<P>This is not a complete list of names, simply a list of properties used by this sample.</P>
<TABLE CELLSPACING=0 BORDER=0 CELLPADDING=7 WIDTH=637>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P><B>Property Name</B></TD>
<TD WIDTH="66%" VALIGN="TOP">
<B><P>Description</B></TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P>_public_methods_</TD>
<TD WIDTH="66%" VALIGN="TOP">
<P>List of all method names exposed to remote COM clients</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P>_public_attrs_</TD>
<TD WIDTH="66%" VALIGN="TOP">
<P>List of all attribute names exposed to remote COM clients</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP" HEIGHT=5>
<P>_readonly_attrs_</TD>
<TD WIDTH="66%" VALIGN="TOP" HEIGHT=5>
<P>List of all attributes which can be accessed, but not set.</TD>
</TR>
</TABLE>
<P>We change the class header to become:</P>
<CODE><P>class HelloWorld:</P><DIR>
<DIR>
<P>_public_methods_ = ['Hello']</P>
<P>_public_attrs_ = ['softspace', 'noCalls']</P>
<P>_readonly_attrs_ = ['noCalls']</P>
<P>def __init__(self):</P>
<P>[Same from here…]</P></DIR>
</DIR>
</CODE><H3><A NAME="Registering">Registering and assigning a CLSID for the object</A></H3>
<P>COM requires that all objects use a unique CLSID and be registered under a "user friendly" name. This documents the process.</P>
<H4>Generating the CLSID</H4>
<P>Microsoft Visual C++ comes with various tools for generating CLSID's, which are quite suitable. Alternatively, the pythoncom module exports the function CreateGuid() to generate these identifiers.</P>
<CODE><P>&gt;&gt;&gt; import pythoncom<BR>
&gt;&gt;&gt; print pythoncom.CreateGuid()<BR>
{7CC9F362-486D-11D1-BB48-0000E838A65F}</P>
</CODE><P>Obviously the GUID that you get will be different than that displayed here.</P>
<H4>Preparing for registration of the Class</H4>
<P>The win32com package allows yet more annotations to be applied to a class, allowing registration to be effected with 2 lines in your source file. The registration annotations used by this sample are:</P>
<TABLE CELLSPACING=0 BORDER=0 CELLPADDING=7 WIDTH=636>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P><B>Property Name</B></TD>
<TD WIDTH="66%" VALIGN="TOP">
<B><P>Description</B></TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P>_reg_clsid_</TD>
<TD WIDTH="66%" VALIGN="TOP">
<P>The CLSID of the COM object</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP">
<P>_reg_progid_</TD>
<TD WIDTH="66%" VALIGN="TOP">
<P>The "program ID", or Name, of the COM Server. This is the name the user usually uses to instantiate the object</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP" HEIGHT=5>
<P>_reg_desc_</TD>
<TD WIDTH="66%" VALIGN="TOP" HEIGHT=5>
<P>Optional: The description of the COM Server. Used primarily for COM browsers. If not specified, the _reg_progid_ is used as the description.</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP" HEIGHT=5>
<P>_reg_class_spec_</TD>
<TD WIDTH="66%" VALIGN="TOP" HEIGHT=5>
<P>Optional: A string which represents how Python can create the class instance. The string is of format<BR>
[package.subpackage.]module.class</P>
<P>The portion up to the class name must be valid for Python to "import", and the class portion must be a valid attribute in the specified class.</P>
<P>This is optional from build 124 of Pythoncom., and has been removed from this sample.</TD>
</TR>
<TR><TD WIDTH="34%" VALIGN="TOP" HEIGHT=5>
<P>_reg_remove_keys_</TD>
<TD WIDTH="66%" VALIGN="TOP" HEIGHT=5>
<P>Optional: A list of tuples of extra registry keys to be removed when uninstalling the server. Each tuple is of format ("key", root), where key is a string, and root is one of the win32con.HKEY_* constants (this item is optional, defaulting to HKEY_CLASSES_ROOT)</TD>
</TR>
</TABLE>
<P>Note there are quite a few other keys available. Also note that these annotations are <I>not</I> required - they just make registration simple. Helper functions in the module <CODE>win32com.server.register</CODE> allow you to explicitly specify each of these attributes without attaching them to the class.</P>
<P>The header of our class now becomes:</P>
<CODE><P>class HelloWorld:</P><DIR>
<DIR>
<P>_reg_clsid_ = "{7CC9F362-486D-11D1-BB48-0000E838A65F}"</P>
<P>_reg_desc_ = "Python Test COM Server"</P>
<P>_reg_progid_ = "Python.TestServer"</P>
<P>_public_methods_ = ['Hello']</P>
<P>[same from here]</P></DIR>
</DIR>
</CODE><H4>Registering the Class</H4>
<P>The idiom that most Python COM Servers use is that they register themselves when run as a script (ie, when executed from the command line.) Thus the standard "<CODE>if __name__=='__main___':</CODE>" technique works well.</P>
<P>win32com.server.register contains a number of helper functions. The easiest to use is "<CODE>UseCommandLine</CODE>".</P>
<P>Registration becomes as simple as:</P>
<CODE><P>if __name__=='__main__':<BR>
&#9;# ni only for 1.4!<BR>
&#9;import ni, win32com.server.register <BR>
&#9;win32com.server.register.UseCommandLine(HelloWorld)</P>
</CODE><P>Running the script will register our test server.</P>
<H2><A NAME="testing">Testing our Class</A></H2>
<P>For the purposes of this demonstration, we will test the class using Visual Basic. This code should run under any version of Visual Basic, including VBA found in Microsoft Office. Any COM compliant package could be used alternatively. VB has been used just to prove there is no "smoke and mirrors. For information on how to test the server using Python, please see the <A HREF="QuickStartClientCom.html">Quick Start to Client side COM</A> documentation.</P>
<P>This is not a tutorial in VB. The code is just presented! Run it, and it will work!</P>
<H2><A NAME="debugging">Debugging the COM Server</A></H2>
<P>When things go wrong in COM Servers, there is often nowhere useful for the Python traceback to go, even if such a traceback is generated.</P>
<P>Rather than discuss how it works, I will just present the procedure to debug your server:</P>
<B><P>To register a debug version of your class</B>, run the script (as above) but pass in a "<CODE>--debug</CODE>" parameter. Eg, for the server above, use the command line "<CODE>testcomserver.py --debug</CODE>".</P>
<B><P>To see the debug output generated</B> (and any print statements you may choose to add!) you can simply select the "Remote Debug Trace Collector" from the Pythonwin Tools menu, or run the script "win32traceutil.py" from Windows Explorer or a Command Prompt.</P>
<H2><A NAME="Exception">Exception Handling </A></H2>
<P>Servers need to be able to provide exception information to their client. In some cases, it may be a simple return code (such as E_NOTIMPLEMENTED), but often it can contain much richer information, describing the error on detail, and even a help file and topic where more information can be found. </P>
<P>We use Python class based exceptions to provide this information. The COM framework will examine the exception, and look for certain known attributes. These attributes will be copied across to the COM exception, and passed back to the client. </P>
<P>The following attributes are supported, and correspond to the equivalent entry in the COM Exception structure:<BR>
<CODE>scode, code, description, source, helpfile and helpcontext</P>
</CODE><P>To make working with exceptions easier, there is a helper module "win32com.server.exception.py", which defines a single class. An example of its usage would be: </P>
<CODE><P>raise COMException(desc="Must be a string",scode=winerror.E_INVALIDARG,helpfile="myhelp.hlp",...)</CODE> </P>
<P>(Note the <CODE>COMException class supports (and translates) "desc" as a shortcut for "description", but the framework requires "description")</P>
</CODE><H2><A NAME="Policies">Server Policies</A></H2>
<P>This is information about how it all hangs together. The casual COM author need not know this. </P>
<P>Whenever a Python Server needs to be created, the C++ framework first instantiates a "policy" object. This "policy" object is the gatekeeper for the COM Server - it is responsible for creating the underlying Python object that is the server (ie, your object), and also for translating the underlying COM requests for the object. </P>
<P>This policy object handles all of the underlying COM functionality. For example, COM requires all methods and properties to have unique numeric ID's associated with them. The policy object manages the creation of these ID's for the underlying Python methods and attributes. Similarly, when the client whishes to call a method with ID 123, the policy object translates this back to the actual method, and makes the call. </P>
<P>It should be noted that the operation of the "policy" object could be dictated by the Python object - the policy object has many defaults, but the actual Python class can always dictate its operation. </P>
<H3>Default Policy attributes </H3>
<P>The default policy object has a few special attributes that define who the object is exposed to COM. The example above shows the _public_methods attribute, but this section describes all such attributes in detail. </P>
<H5>_public_methods_ </H5>
<P>Required list of strings, containing the names of all methods to be exposed to COM. It is possible this will be enhanced in the future (eg, possibly '*' will be recognised to say all methods, or some other ideas…) </P>
<H5>_public_attrs_ </H5>
<P>Optional list of strings containing all attribute names to be exposed, both for reading and writing. The attribute names must be valid instance variables. </P>
<H5>_readonly_attrs_ </H5>
<P>Optional list of strings defining the name of attributes exposed read-only. </P>
<H5>_com_interfaces_ </H5>
<P>Optional list of IIDs exposed by this object. If this attribute is missing, IID_IDispatch is assumed (ie, if not supplied, the COM object will be created as a normal Automation object.</P>
<P>and actual instance attributes: </P>
<P>_dynamic_ : optional method </P>
<P>_value_ : optional attribute </P>
<P>_query_interface_ : optional method </P>
<P>_NewEnum : optional method </P>
<P>_Evaluate : optional method </P></BODY>
</HTML>

View file

@ -0,0 +1,22 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>win32com Documentation Index</TITLE>
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080">
<H1><IMG SRC="image/pycom_blowing.gif" WIDTH=549 HEIGHT=99 ALT="Python and COM - Blowing the others away"></H1>
<H1>PythonCOM Documentation Index</H1>
<P>The following documentation is available</P>
<P><A HREF="QuickStartClientCom.html">A Quick Start to Client Side COM</A> (including makepy)</P>
<P><A HREF="QuickStartServerCom.html">A Quick Start to Server Side COM</A></P>
<P><A HREF="GeneratedSupport.html">Information on generated Python files (ie, what makepy generates)</A></P>
<P><A HREF="variant.html">An advanced VARIANT object which can give more control over parameter types</A></P>
<P><A HREF="package.html">A brief description of the win32com package structure</A></P>
<P><A HREF="PythonCOM.html">Python COM Implementation documentation</A></P>
<P><A HREF="misc.html">Misc stuff I dont know where to put anywhere else</A></P>
<H3>ActiveX Scripting</H3>
<P><A HREF="../../win32comext/axscript/demos/client/ie/demo.htm">ActiveX Scripting Demos</A></P></BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

View file

@ -0,0 +1,31 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>win32com</TITLE>
<META NAME="Template" CONTENT="C:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY TEXT="#000000" LINK="#0000ff" VLINK="#0000ff">
<DIR>
<P><!-- Enclose the entire page in UL, so bullets don't indent. --></P>
<H1><IMG SRC="image/pycom_blowing.gif" WIDTH=549 HEIGHT=99></H1>
<H2>Python and COM</H2>
<H3>Introduction</H3>
<P>Python has an excellent interface to COM (also known variously as OLE2, ActiveX, etc).</P>
<P>The Python COM package can be used to interface to almost any COM program (such as the MS-Office suite), write servers that can be hosted by any COM client (such as Visual Basic or C++), and has even been used to provide the core ActiveX Scripting Support. </P>
<UL>
<LI>Note that win32com is now released in the win32all installation package. The <A HREF="../win32all/win32all.exe">installation EXE can be downloaded</A>, or you <A HREF="../win32all/">can jump to the win32all readme</A> for more details. </LI>
<LI>Here is the <A HREF="win32com_src.zip">win32com source code</A> in a zip file. </LI></UL>
</DIR>
<DIR>
<H3>Documentation</H3>
<P><A HREF="ActiveXScripting.html">Preliminary Active Scripting and Debugging documentation</A> is available.</P>
<P>2 Quick-Start guides have been provided, which also contain other links. See the <A HREF="QuickStartClientCom.html">Quick Start for Client side COM</A> and the <A HREF="QuickStartServerCom.html">Quick Start for Server side COM</A> </P>
</P></DIR>
</DIR>
</BODY>
</HTML>

View file

@ -0,0 +1,18 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>Misc win32com Stuff</TITLE>
<META NAME="Version" CONTENT="8.0.3410">
<META NAME="Date" CONTENT="10/11/96">
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\HTML.DOT">
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#ffffff">
<H1>Misc stuff I dont know where to put anywhere else</H1>
<H4>Client Side Dispatch</H4>
<P>Using win32com.client.Dispatch automatically invokes all the win32com client side "smarts", including automatic usage of generated .py files etc.</P>
<P>If you wish to avoid that, and use truly "dynamic" objects (ie, there is generated .py support available, but you wish to avoid it), you can use win32com.client.dynamic.Dispatch</P>
<B><P>_print_details_() method</B><BR>
If win32com.client.dynamic.Dispatch is used, the objects have a _print_details_() method available, which prints all relevant knowledge about an object (for example, all methods and properties). For objects that do not expose runtime type information, _print_details_ may not list anything.</P></BODY>
</HTML>

View file

@ -0,0 +1,37 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<META NAME="Generator" CONTENT="Microsoft Word 97">
<TITLE>The win32com package</TITLE>
<META NAME="Template" CONTENT="D:\Program Files\Microsoft Office\Office\html.dot">
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080">
<H1><IMG SRC="image/pycom_blowing.gif" WIDTH=549 HEIGHT=99 ALT="Python and COM - Blowing the others away"></H1>
<H1>The win32com package </H1>
<FONT SIZE=2><P>This document describes the win32com package in general terms.</FONT> </P>
<FONT SIZE=2><P>The COM support can be thought of as existing in 2 main portions - the C++ support code (the core PythonCOM module), and helper code, implemented in Python. The total package is known as "win32com".</FONT> </P>
<FONT SIZE=2><P>The win32com support is stand-alone. It does not require Pythonwin.</FONT> </P>
<H2>The win32com package </H2>
<FONT SIZE=2><P>To facilitate an orderly framework, the Python "ni" module has been used, and the entire package is known as "win32com". As is normal for such packages, win32com itself does not provide any functionality. Some of the modules are described below:</FONT> </P>
<UL>
<B><FONT SIZE=2><LI>win32com.pythoncom - core C++ support</B>. <BR>
This module is rarely used directly by programmers - instead the other "helper" module are used, which themselves draw on the core pythoncom services.</FONT> </LI>
<B><FONT SIZE=2><LI>win32com.client package<BR>
</B>Support for COM clients used by Python. Some of the modules in this package allow for dynamic usage of COM clients, a module for generating .py files for certain COM servers, etc.</FONT> </LI>
<B><FONT SIZE=2><LI>win32com.server package<BR>
</B>Support for COM servers written in Python. The modules in this package provide most of the underlying framework for magically turning Python classes into COM servers, exposing the correct public methods, registering your server in the registry, etc. </LI>
<B><LI>win32com.axscript<BR>
</B>ActiveX Scripting implementation for Python.</FONT> </LI>
<B><FONT SIZE=2><LI>win32com.axdebug<BR>
</B>Active Debugging implementation for Python</FONT> </LI>
<B><FONT SIZE=2><LI>win32com.mapi<BR>
</B>Utilities for working with MAPI and the Microsoft Exchange Server</LI></UL>
</FONT><P>&nbsp;</P>
<H2>The pythoncom module </H2>
<FONT SIZE=2><P>The pythoncom module is the underlying C++ support for all COM related objects. In general, Python programmers will not use this module directly, but use win32com helper classes and functions. </P>
<P>This module exposes a C++ like interface to COM - there are objects implemented in pythoncom that have methods "QueryInterface()", "Invoke()", just like the C++ API. If you are using COM in C++, you would not call a method directly, you would use pObject-&gt;Invoke( …, MethodId, argArray…). Similarly, if you are using pythoncom directly, you must also use the Invoke method to call an object's exposed method.</FONT> </P>
<FONT SIZE=2><P>There are some Python wrappers for hiding this raw interface, meaning you should almost never need to use the pythoncom module directly. These helpers translate a "natural" looking interface (eg, obj.SomeMethod()) into the underlying Invoke call.</P></FONT></BODY>
</HTML>

View file

@ -0,0 +1,162 @@
<HTML>
<HEAD>
<TITLE>win32com.client.VARIANT</TITLE>
</HEAD>
<BODY>
<H2>Introduction</H2>
<p>
win32com attempts to provide a seamless COM interface and hide many COM
implementation details, including the use of COM VARIANT structures. This
means that in most cases, you just call a COM object using normal Python
objects as parameters and get back normal Python objects as results.
</p>
<p>
However, in some cases this doesn't work very well, particularly when using
"dynamic" (aka late-bound) objects, or when using "makepy" (aka early-bound)
objects which only declare a parameter is a VARIANT.
</p>
<p>
The <code>win32com.client.VARIANT</code> object is designed to overcome these
problems.
</p>
<h2>Drawbacks</h2>
The primary issue with this approach is that the programmer must learn more
about COM VARIANTs than otherwise - they need to know concepts such as
variants being <em>byref</em>, holding arrays, or that some may hold 32bit
unsigned integers while others hold 64bit signed ints, and they need to
understand this in the context of a single method call. In short, this is
a relatively advanced feature. The good news though is that use of these
objects should never cause your program to hard-crash - the worst you should
expect are Python or COM exceptions being thrown.
<h2>The VARIANT object</h2>
The VARIANT object lives in <code>win32com.client</code>. The constructor
takes 2 parameters - the 'variant type' and the value. The 'variant type' is
an integer and can be one or more of the <code>pythoncom.VT_*</code> values,
possibly or'd together.
<p>For example, to create a VARIANT object which defines a byref array of
32bit integers, you could use:
<pre>
>>> from win32com.client import VARIANT
>>> import pythoncom
>>> v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_ARRAY | pythoncom.VT_I4,
... [1,2,3,4])
>>> v
win32com.client.VARIANT(24579, [1, 2, 3, 4])
>>>
</pre>
This variable can then be used whereever a COM VARIANT is expected.
<h2>Example usage with dynamic objects.</h2>
For this example we will use the COM object used for win32com testing,
<code>PyCOMTest.PyCOMTest</code>. This object defines a method which is
defined in IDL as:
<pre>
HRESULT DoubleInOutString([in,out] BSTR *str);
</pre>
As you can see, it takes a single string parameter which is also used as
an "out" parameter - the single parameter will be updated after the call.
The implementation of the method simply "doubles" the string.
<p>If the object has a type-library, this method works fine with makepy
generated support. For example:
<pre>
>>> from win32com.client.gencache import EnsureDispatch
>>> ob = EnsureDispatch("PyCOMTest.PyCOMTest")
>>> ob.DoubleInOutString("Hello")
u'HelloHello'
>>>
</pre>
However, if makepy support is not available the method does not work as
expected. For the next example we will use <code>DumbDispatch</code> to
simulate the object not having a type-library.
<pre>
>>> import win32com.client.dynamic
>>> ob = win32com.client.dynamic.DumbDispatch("PyCOMTest.PyCOMTest")
>>> ob.DoubleInOutString("Hello")
>>>
</pre>
As you can see, no result came back from the function. This is because
win32com has no type information available to use, so doesn't know the
parameter should be passed as a <code>byref</code> parameter. To work
around this, we can use the <code>VARIANT</code> object.
<p>The following example explicitly creates a VARIANT object with a
variant type of a byref string and a value 'Hello'. After making the
call with this VARIANT the value is updated.
<pre>
>>> import win32com.client.dynamic
>>> from win32com.client import VARIANT
>>> import pythoncom
>>> ob = win32com.client.dynamic.DumbDispatch("PyCOMTest.PyCOMTest")
>>> variant = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, "Hello")
>>> variant.value # check the value before the call.
'Hello'
>>> ob.DoubleInOutString(variant)
>>> variant.value
u'HelloHello'
>>>
</pre>
<h2>Usage with generated objects</h2>
In most cases, objects with makepy support (ie, 'generated' objects) don't
need to use the VARIANT object - the type information means win32com can guess
the right thing to pass. However, in some cases the VARIANT object can still
be useful.
Imagine a poorly specified object with IDL like:
<pre>
HRESULT DoSomething([in] VARIANT value);
</pre>
But also imagine that the object has a limitation that if the parameter is an
integer, it must be a 32bit unsigned value - any other integer representation
will fail.
<p>If you just pass a regular Python integer to this function, it will
generally be passed as a 32bit signed integer and given the limitation above,
will fail. The VARIANT object allows you to work around the limitation - just
create a variant object <code>VARIANT(pythoncom.VT_UI4, int_value)</code> and
pass that - the function will then be called with the explicit type you
specified and will succeed.
<p>Note that you can not use a VARIANT object to override the types described
in a type library. If a makepy generated class specifies that a VT_UI2 is
expected, attempting to pass a VARIANT object will fail. In this case you
would need to hack around the problem. For example, imagine <code>ob</code>
was a COM object which a method called <code>foo</code> and you wanted to
override the type declaration for <code>foo</code> by passing a VARIANT.
You could do something like:
<pre>
>>> import win32com.client.dynamic
>>> from win32com.client import VARIANT
>>> import pythoncom
>>> dumbob = win32com.client.dynamic.DumbDispatch(ob)
>>> variant = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, "Hello")
>>> dumbob.foo(variant)
</pre>
The code above converts the makepy supported <code>ob</code> into a
'dumb' (ie, non-makepy supported) version of the object, which will then
allow you to use VARIANT objects for the problematic methods.
</BODY>
</HTML>

30
lib/win32com/License.txt Normal file
View file

@ -0,0 +1,30 @@
Unless stated in the specfic source file, this work is
Copyright (c) 1996-2008, Greg Stein and Mark Hammond.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither names of Greg Stein, Mark Hammond nor the name of contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

132
lib/win32com/__init__.py Normal file
View file

@ -0,0 +1,132 @@
#
# Initialization for the win32com package
#
import os
import sys
import pythoncom
import win32api
# flag if we are in a "frozen" build.
_frozen = getattr(sys, "frozen", 1 == 0)
# pythoncom dumbly defaults this to zero - we believe sys.frozen over it.
if _frozen and not getattr(pythoncom, "frozen", 0):
pythoncom.frozen = sys.frozen
# Add support for an external "COM Extensions" path.
# Concept is that you can register a seperate path to be used for
# COM extensions, outside of the win32com directory. These modules, however,
# look identical to win32com built-in modules.
# This is the technique that we use for the "standard" COM extensions.
# eg "win32com.mapi" or "win32com.axscript" both work, even though they do not
# live under the main win32com directory.
__gen_path__ = ""
__build_path__ = None
### TODO - Load _all_ \\Extensions subkeys - for now, we only read the default
### Modules will work if loaded into "win32comext" path.
def SetupEnvironment():
HKEY_LOCAL_MACHINE = -2147483646 # Avoid pulling in win32con for just these...
KEY_QUERY_VALUE = 0x1
# Open the root key once, as this is quite slow on NT.
try:
keyName = "SOFTWARE\\Python\\PythonCore\\%s\\PythonPath\\win32com" % sys.winver
key = win32api.RegOpenKey(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE)
except (win32api.error, AttributeError):
key = None
try:
found = 0
if key is not None:
try:
__path__.append(win32api.RegQueryValue(key, "Extensions"))
found = 1
except win32api.error:
# Nothing registered
pass
if not found:
try:
__path__.append(
win32api.GetFullPathName(__path__[0] + "\\..\\win32comext")
)
except win32api.error:
# Give up in disgust!
pass
# For the sake of developers, we also look up a "BuildPath" key
# If extension modules add support, we can load their .pyd's from a completely
# different directory (see the comments below)
try:
if key is not None:
global __build_path__
__build_path__ = win32api.RegQueryValue(key, "BuildPath")
__path__.append(__build_path__)
except win32api.error:
# __build_path__ neednt be defined.
pass
global __gen_path__
if key is not None:
try:
__gen_path__ = win32api.RegQueryValue(key, "GenPath")
except win32api.error:
pass
finally:
if key is not None:
key.Close()
# A Helper for developers. A sub-package's __init__ can call this help function,
# which allows the .pyd files for the extension to live in a special "Build" directory
# (which the win32com developers do!)
def __PackageSupportBuildPath__(package_path):
# See if we have a special directory for the binaries (for developers)
if not _frozen and __build_path__:
package_path.append(__build_path__)
if not _frozen:
SetupEnvironment()
# If we don't have a special __gen_path__, see if we have a gen_py as a
# normal module and use that (ie, "win32com.gen_py" may already exist as
# a package.
if not __gen_path__:
try:
import win32com.gen_py
# hrmph - 3.3 throws: TypeError: '_NamespacePath' object does not support indexing
# attempting to get __path__[0] - but I can't quickly repro this stand-alone.
# Work around it by using an iterator.
__gen_path__ = next(iter(sys.modules["win32com.gen_py"].__path__))
except ImportError:
# If a win32com\gen_py directory already exists, then we use it
# (gencache doesn't insist it have an __init__, but our __import__
# above does!
__gen_path__ = os.path.abspath(os.path.join(__path__[0], "gen_py"))
if not os.path.isdir(__gen_path__):
# We used to dynamically create a directory under win32com -
# but this sucks. If the dir doesn't already exist, we we
# create a version specific directory under the user temp
# directory.
__gen_path__ = os.path.join(
win32api.GetTempPath(),
"gen_py",
"%d.%d" % (sys.version_info[0], sys.version_info[1]),
)
# we must have a __gen_path__, but may not have a gen_py module -
# set that up.
if "win32com.gen_py" not in sys.modules:
# Create a "win32com.gen_py", but with a custom __path__
import types
gen_py = types.ModuleType("win32com.gen_py")
gen_py.__path__ = [__gen_path__]
sys.modules[gen_py.__name__] = gen_py
del types
gen_py = sys.modules["win32com.gen_py"]
# get rid of these for module users
del os, sys, win32api, pythoncom

View file

@ -0,0 +1,57 @@
"""Manages a dictionary of CLSID strings to Python classes.
Primary use of this module is to allow modules generated by
makepy.py to share classes. @makepy@ automatically generates code
which interacts with this module. You should never need to reference
this module directly.
This module only provides support for modules which have been previously
been imported. The gencache module provides some support for loading modules
on demand - once done, this module supports it...
As an example, the MSACCESS.TLB type library makes reference to the
CLSID of the Database object, as defined in DAO3032.DLL. This
allows code using the MSAccess wrapper to natively use Databases.
This obviously applies to all cooperating objects, not just DAO and
Access.
"""
mapCLSIDToClass = {}
def RegisterCLSID(clsid, pythonClass):
"""Register a class that wraps a CLSID
This function allows a CLSID to be globally associated with a class.
Certain module will automatically convert an IDispatch object to an
instance of the associated class.
"""
mapCLSIDToClass[str(clsid)] = pythonClass
def RegisterCLSIDsFromDict(dict):
"""Register a dictionary of CLSID's and classes.
This module performs the same function as @RegisterCLSID@, but for
an entire dictionary of associations.
Typically called by makepy generated modules at import time.
"""
mapCLSIDToClass.update(dict)
def GetClass(clsid):
"""Given a CLSID, return the globally associated class.
clsid -- a string CLSID representation to check.
"""
return mapCLSIDToClass[clsid]
def HasClass(clsid):
"""Determines if the CLSID has an associated class.
clsid -- the string CLSID to check
"""
return clsid in mapCLSIDToClass

View file

@ -0,0 +1,714 @@
# This module exists to create the "best" dispatch object for a given
# object. If "makepy" support for a given object is detected, it is
# used, otherwise a dynamic dispatch object.
# Note that if the unknown dispatch object then returns a known
# dispatch object, the known class will be used. This contrasts
# with dynamic.Dispatch behaviour, where dynamic objects are always used.
import sys
import pythoncom
import pywintypes
from . import dynamic, gencache
_PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
def __WrapDispatch(
dispatch,
userName=None,
resultCLSID=None,
typeinfo=None,
UnicodeToString=None,
clsctx=pythoncom.CLSCTX_SERVER,
WrapperClass=None,
):
"""
Helper function to return a makepy generated class for a CLSID if it exists,
otherwise cope by using CDispatch.
"""
assert UnicodeToString is None, "this is deprecated and will go away"
if resultCLSID is None:
try:
typeinfo = dispatch.GetTypeInfo()
if (
typeinfo is not None
): # Some objects return NULL, some raise exceptions...
resultCLSID = str(typeinfo.GetTypeAttr()[0])
except (pythoncom.com_error, AttributeError):
pass
if resultCLSID is not None:
from . import gencache
# Attempt to load generated module support
# This may load the module, and make it available
klass = gencache.GetClassForCLSID(resultCLSID)
if klass is not None:
return klass(dispatch)
# Return a "dynamic" object - best we can do!
if WrapperClass is None:
WrapperClass = CDispatch
return dynamic.Dispatch(dispatch, userName, WrapperClass, typeinfo, clsctx=clsctx)
def GetObject(Pathname=None, Class=None, clsctx=None):
"""
Mimic VB's GetObject() function.
ob = GetObject(Class = "ProgID") or GetObject(Class = clsid) will
connect to an already running instance of the COM object.
ob = GetObject(r"c:\blah\blah\foo.xls") (aka the COM moniker syntax)
will return a ready to use Python wrapping of the required COM object.
Note: You must specifiy one or the other of these arguments. I know
this isn't pretty, but it is what VB does. Blech. If you don't
I'll throw ValueError at you. :)
This will most likely throw pythoncom.com_error if anything fails.
"""
if clsctx is None:
clsctx = pythoncom.CLSCTX_ALL
if (Pathname is None and Class is None) or (
Pathname is not None and Class is not None
):
raise ValueError(
"You must specify a value for Pathname or Class, but not both."
)
if Class is not None:
return GetActiveObject(Class, clsctx)
else:
return Moniker(Pathname, clsctx)
def GetActiveObject(Class, clsctx=pythoncom.CLSCTX_ALL):
"""
Python friendly version of GetObject's ProgID/CLSID functionality.
"""
resultCLSID = pywintypes.IID(Class)
dispatch = pythoncom.GetActiveObject(resultCLSID)
dispatch = dispatch.QueryInterface(pythoncom.IID_IDispatch)
return __WrapDispatch(dispatch, Class, resultCLSID=resultCLSID, clsctx=clsctx)
def Moniker(Pathname, clsctx=pythoncom.CLSCTX_ALL):
"""
Python friendly version of GetObject's moniker functionality.
"""
moniker, i, bindCtx = pythoncom.MkParseDisplayName(Pathname)
dispatch = moniker.BindToObject(bindCtx, None, pythoncom.IID_IDispatch)
return __WrapDispatch(dispatch, Pathname, clsctx=clsctx)
def Dispatch(
dispatch,
userName=None,
resultCLSID=None,
typeinfo=None,
UnicodeToString=None,
clsctx=pythoncom.CLSCTX_SERVER,
):
"""Creates a Dispatch based COM object."""
assert UnicodeToString is None, "this is deprecated and will go away"
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch, userName, clsctx)
return __WrapDispatch(dispatch, userName, resultCLSID, typeinfo, clsctx=clsctx)
def DispatchEx(
clsid,
machine=None,
userName=None,
resultCLSID=None,
typeinfo=None,
UnicodeToString=None,
clsctx=None,
):
"""Creates a Dispatch based COM object on a specific machine."""
assert UnicodeToString is None, "this is deprecated and will go away"
# If InProc is registered, DCOM will use it regardless of the machine name
# (and regardless of the DCOM config for the object.) So unless the user
# specifies otherwise, we exclude inproc apps when a remote machine is used.
if clsctx is None:
clsctx = pythoncom.CLSCTX_SERVER
if machine is not None:
clsctx = clsctx & ~pythoncom.CLSCTX_INPROC
if machine is None:
serverInfo = None
else:
serverInfo = (machine,)
if userName is None:
userName = clsid
dispatch = pythoncom.CoCreateInstanceEx(
clsid, None, clsctx, serverInfo, (pythoncom.IID_IDispatch,)
)[0]
return Dispatch(dispatch, userName, resultCLSID, typeinfo, clsctx=clsctx)
class CDispatch(dynamic.CDispatch):
"""
The dynamic class used as a last resort.
The purpose of this overriding of dynamic.CDispatch is to perpetuate the policy
of using the makepy generated wrapper Python class instead of dynamic.CDispatch
if/when possible.
"""
def _wrap_dispatch_(
self, ob, userName=None, returnCLSID=None, UnicodeToString=None
):
assert UnicodeToString is None, "this is deprecated and will go away"
return Dispatch(ob, userName, returnCLSID, None)
def __dir__(self):
return dynamic.CDispatch.__dir__(self)
def CastTo(ob, target, typelib=None):
"""'Cast' a COM object to another interface"""
# todo - should support target being an IID
mod = None
if (
typelib is not None
): # caller specified target typelib (TypelibSpec). See e.g. selecttlb.EnumTlbs().
mod = gencache.MakeModuleForTypelib(
typelib.clsid, typelib.lcid, int(typelib.major, 16), int(typelib.minor, 16)
)
if not hasattr(mod, target):
raise ValueError(
"The interface name '%s' does not appear in the "
"specified library %r" % (target, typelib.ver_desc)
)
elif hasattr(target, "index"): # string like
# for now, we assume makepy for this to work.
if "CLSID" not in ob.__class__.__dict__:
# Eeek - no makepy support - try and build it.
ob = gencache.EnsureDispatch(ob)
if "CLSID" not in ob.__class__.__dict__:
raise ValueError("Must be a makepy-able object for this to work")
clsid = ob.CLSID
# Lots of hoops to support "demand-build" - ie, generating
# code for an interface first time it is used. We assume the
# interface name exists in the same library as the object.
# This is generally the case - only referenced typelibs may be
# a problem, and we can handle that later. Maybe <wink>
# So get the generated module for the library itself, then
# find the interface CLSID there.
mod = gencache.GetModuleForCLSID(clsid)
# Get the 'root' module.
mod = gencache.GetModuleForTypelib(
mod.CLSID, mod.LCID, mod.MajorVersion, mod.MinorVersion
)
# Find the CLSID of the target
target_clsid = mod.NamesToIIDMap.get(target)
if target_clsid is None:
raise ValueError(
"The interface name '%s' does not appear in the "
"same library as object '%r'" % (target, ob)
)
mod = gencache.GetModuleForCLSID(target_clsid)
if mod is not None:
target_class = getattr(mod, target)
# resolve coclass to interface
target_class = getattr(target_class, "default_interface", target_class)
return target_class(ob) # auto QI magic happens
raise ValueError
class Constants:
"""A container for generated COM constants."""
def __init__(self):
self.__dicts__ = [] # A list of dictionaries
def __getattr__(self, a):
for d in self.__dicts__:
if a in d:
return d[a]
raise AttributeError(a)
# And create an instance.
constants = Constants()
# A helpers for DispatchWithEvents - this becomes __setattr__ for the
# temporary class.
def _event_setattr_(self, attr, val):
try:
# Does the COM object have an attribute of this name?
self.__class__.__bases__[0].__setattr__(self, attr, val)
except AttributeError:
# Otherwise just stash it away in the instance.
self.__dict__[attr] = val
# An instance of this "proxy" is created to break the COM circular references
# that exist (ie, when we connect to the COM events, COM keeps a reference
# to the object. Thus, the Event connection must be manually broken before
# our object can die. This solves the problem by manually breaking the connection
# to the real object as the proxy dies.
class EventsProxy:
def __init__(self, ob):
self.__dict__["_obj_"] = ob
def __del__(self):
try:
# If there is a COM error on disconnection we should
# just ignore it - object probably already shut down...
self._obj_.close()
except pythoncom.com_error:
pass
def __getattr__(self, attr):
return getattr(self._obj_, attr)
def __setattr__(self, attr, val):
setattr(self._obj_, attr, val)
def DispatchWithEvents(clsid, user_event_class):
"""Create a COM object that can fire events to a user defined class.
clsid -- The ProgID or CLSID of the object to create.
user_event_class -- A Python class object that responds to the events.
This requires makepy support for the COM object being created. If
this support does not exist it will be automatically generated by
this function. If the object does not support makepy, a TypeError
exception will be raised.
The result is a class instance that both represents the COM object
and handles events from the COM object.
It is important to note that the returned instance is not a direct
instance of the user_event_class, but an instance of a temporary
class object that derives from three classes:
* The makepy generated class for the COM object
* The makepy generated class for the COM events
* The user_event_class as passed to this function.
If this is not suitable, see the getevents function for an alternative
technique of handling events.
Object Lifetimes: Whenever the object returned from this function is
cleaned-up by Python, the events will be disconnected from
the COM object. This is almost always what should happen,
but see the documentation for getevents() for more details.
Example:
>>> class IEEvents:
... def OnVisible(self, visible):
... print "Visible changed:", visible
...
>>> ie = DispatchWithEvents("InternetExplorer.Application", IEEvents)
>>> ie.Visible = 1
Visible changed: 1
>>>
"""
# Create/Get the object.
disp = Dispatch(clsid)
if not disp.__class__.__dict__.get(
"CLSID"
): # Eeek - no makepy support - try and build it.
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
gencache.EnsureModule(tla[0], tla[1], tla[3], tla[4], bValidateFile=0)
# Get the class from the module.
disp_class = gencache.GetClassForProgID(str(disp_clsid))
except pythoncom.com_error:
raise TypeError(
"This COM object can not automate the makepy process - please run makepy manually for this object"
)
else:
disp_class = disp.__class__
# If the clsid was an object, get the clsid
clsid = disp_class.CLSID
# Create a new class that derives from 3 classes - the dispatch class, the event sink class and the user class.
# XXX - we are still "classic style" classes in py2x, so we need can't yet
# use 'type()' everywhere - revisit soon, as py2x will move to new-style too...
try:
from types import ClassType as new_type
except ImportError:
new_type = type # py3k
events_class = getevents(clsid)
if events_class is None:
raise ValueError("This COM object does not support events.")
result_class = new_type(
"COMEventClass",
(disp_class, events_class, user_event_class),
{"__setattr__": _event_setattr_},
)
instance = result_class(
disp._oleobj_
) # This only calls the first base class __init__.
events_class.__init__(instance, instance)
if hasattr(user_event_class, "__init__"):
user_event_class.__init__(instance)
return EventsProxy(instance)
def WithEvents(disp, user_event_class):
"""Similar to DispatchWithEvents - except that the returned
object is *not* also usable as the original Dispatch object - that is
the returned object is not dispatchable.
The difference is best summarised by example.
>>> class IEEvents:
... def OnVisible(self, visible):
... print "Visible changed:", visible
...
>>> ie = Dispatch("InternetExplorer.Application")
>>> ie_events = WithEvents(ie, IEEvents)
>>> ie.Visible = 1
Visible changed: 1
Compare with the code sample for DispatchWithEvents, where you get a
single object that is both the interface and the event handler. Note that
the event handler instance will *not* be able to use 'self.' to refer to
IE's methods and properties.
This is mainly useful where using DispatchWithEvents causes
circular reference problems that the simple proxy doesn't deal with
"""
disp = Dispatch(disp)
if not disp.__class__.__dict__.get(
"CLSID"
): # Eeek - no makepy support - try and build it.
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
gencache.EnsureModule(tla[0], tla[1], tla[3], tla[4], bValidateFile=0)
# Get the class from the module.
disp_class = gencache.GetClassForProgID(str(disp_clsid))
except pythoncom.com_error:
raise TypeError(
"This COM object can not automate the makepy process - please run makepy manually for this object"
)
else:
disp_class = disp.__class__
# Get the clsid
clsid = disp_class.CLSID
# Create a new class that derives from 2 classes - the event sink
# class and the user class.
try:
from types import ClassType as new_type
except ImportError:
new_type = type # py3k
events_class = getevents(clsid)
if events_class is None:
raise ValueError("This COM object does not support events.")
result_class = new_type("COMEventClass", (events_class, user_event_class), {})
instance = result_class(disp) # This only calls the first base class __init__.
if hasattr(user_event_class, "__init__"):
user_event_class.__init__(instance)
return instance
def getevents(clsid):
"""Determine the default outgoing interface for a class, given
either a clsid or progid. It returns a class - you can
conveniently derive your own handler from this class and implement
the appropriate methods.
This method relies on the classes produced by makepy. You must use
either makepy or the gencache module to ensure that the
appropriate support classes have been generated for the com server
that you will be handling events from.
Beware of COM circular references. When the Events class is connected
to the COM object, the COM object itself keeps a reference to the Python
events class. Thus, neither the Events instance or the COM object will
ever die by themselves. The 'close' method on the events instance
must be called to break this chain and allow standard Python collection
rules to manage object lifetimes. Note that DispatchWithEvents() does
work around this problem by the use of a proxy object, but if you use
the getevents() function yourself, you must make your own arrangements
to manage this circular reference issue.
Beware of creating Python circular references: this will happen if your
handler has a reference to an object that has a reference back to
the event source. Call the 'close' method to break the chain.
Example:
>>>win32com.client.gencache.EnsureModule('{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}',0,1,1)
<module 'win32com.gen_py.....
>>>
>>> class InternetExplorerEvents(win32com.client.getevents("InternetExplorer.Application.1")):
... def OnVisible(self, Visible):
... print "Visibility changed: ", Visible
...
>>>
>>> ie=win32com.client.Dispatch("InternetExplorer.Application.1")
>>> events=InternetExplorerEvents(ie)
>>> ie.Visible=1
Visibility changed: 1
>>>
"""
# find clsid given progid or clsid
clsid = str(pywintypes.IID(clsid))
# return default outgoing interface for that class
klass = gencache.GetClassForCLSID(clsid)
try:
return klass.default_source
except AttributeError:
# See if we have a coclass for the interfaces.
try:
return gencache.GetClassForCLSID(klass.coclass_clsid).default_source
except AttributeError:
return None
# A Record object, as used by the COM struct support
def Record(name, object):
"""Creates a new record object, given the name of the record,
and an object from the same type library.
Example usage would be:
app = win32com.client.Dispatch("Some.Application")
point = win32com.client.Record("SomeAppPoint", app)
point.x = 0
point.y = 0
app.MoveTo(point)
"""
# XXX - to do - probably should allow "object" to already be a module object.
from . import gencache
object = gencache.EnsureDispatch(object)
module = sys.modules[object.__class__.__module__]
# to allow us to work correctly with "demand generated" code,
# we must use the typelib CLSID to obtain the module
# (otherwise we get the sub-module for the object, which
# does not hold the records)
# thus, package may be module, or may be module's parent if demand generated.
package = gencache.GetModuleForTypelib(
module.CLSID, module.LCID, module.MajorVersion, module.MinorVersion
)
try:
struct_guid = package.RecordMap[name]
except KeyError:
raise ValueError(
"The structure '%s' is not defined in module '%s'" % (name, package)
)
return pythoncom.GetRecordFromGuids(
module.CLSID, module.MajorVersion, module.MinorVersion, module.LCID, struct_guid
)
############################################
# The base of all makepy generated classes
############################################
class DispatchBaseClass:
def __init__(self, oobj=None):
if oobj is None:
oobj = pythoncom.new(self.CLSID)
elif isinstance(oobj, DispatchBaseClass):
try:
oobj = oobj._oleobj_.QueryInterface(
self.CLSID, pythoncom.IID_IDispatch
) # Must be a valid COM instance
except pythoncom.com_error as details:
import winerror
# Some stupid objects fail here, even tho it is _already_ IDispatch!!??
# Eg, Lotus notes.
# So just let it use the existing object if E_NOINTERFACE
if details.hresult != winerror.E_NOINTERFACE:
raise
oobj = oobj._oleobj_
self.__dict__["_oleobj_"] = oobj # so we dont call __setattr__
def __dir__(self):
lst = (
list(self.__dict__.keys())
+ dir(self.__class__)
+ list(self._prop_map_get_.keys())
+ list(self._prop_map_put_.keys())
)
try:
lst += [p.Name for p in self.Properties_]
except AttributeError:
pass
return list(set(lst))
# Provide a prettier name than the CLSID
def __repr__(self):
# Need to get the docstring for the module for this class.
try:
mod_doc = sys.modules[self.__class__.__module__].__doc__
if mod_doc:
mod_name = "win32com.gen_py." + mod_doc
else:
mod_name = sys.modules[self.__class__.__module__].__name__
except KeyError:
mod_name = "win32com.gen_py.unknown"
return "<%s.%s instance at 0x%s>" % (
mod_name,
self.__class__.__name__,
id(self),
)
# Delegate comparison to the oleobjs, as they know how to do identity.
def __eq__(self, other):
other = getattr(other, "_oleobj_", other)
return self._oleobj_ == other
def __ne__(self, other):
other = getattr(other, "_oleobj_", other)
return self._oleobj_ != other
def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
return self._get_good_object_(
self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
user,
resultCLSID,
)
def __getattr__(self, attr):
args = self._prop_map_get_.get(attr)
if args is None:
raise AttributeError(
"'%s' object has no attribute '%s'" % (repr(self), attr)
)
return self._ApplyTypes_(*args)
def __setattr__(self, attr, value):
if attr in self.__dict__:
self.__dict__[attr] = value
return
try:
args, defArgs = self._prop_map_put_[attr]
except KeyError:
raise AttributeError(
"'%s' object has no attribute '%s'" % (repr(self), attr)
)
self._oleobj_.Invoke(*(args + (value,) + defArgs))
def _get_good_single_object_(self, obj, obUserName=None, resultCLSID=None):
return _get_good_single_object_(obj, obUserName, resultCLSID)
def _get_good_object_(self, obj, obUserName=None, resultCLSID=None):
return _get_good_object_(obj, obUserName, resultCLSID)
# XXX - These should be consolidated with dynamic.py versions.
def _get_good_single_object_(obj, obUserName=None, resultCLSID=None):
if _PyIDispatchType == type(obj):
return Dispatch(obj, obUserName, resultCLSID)
return obj
def _get_good_object_(obj, obUserName=None, resultCLSID=None):
if obj is None:
return None
elif isinstance(obj, tuple):
obUserNameTuple = (obUserName,) * len(obj)
resultCLSIDTuple = (resultCLSID,) * len(obj)
return tuple(map(_get_good_object_, obj, obUserNameTuple, resultCLSIDTuple))
else:
return _get_good_single_object_(obj, obUserName, resultCLSID)
class CoClassBaseClass:
def __init__(self, oobj=None):
if oobj is None:
oobj = pythoncom.new(self.CLSID)
dispobj = self.__dict__["_dispobj_"] = self.default_interface(oobj)
# See comments below re the special methods.
for maybe in [
"__call__",
"__str__",
"__int__",
"__iter__",
"__len__",
"__nonzero__",
]:
if hasattr(dispobj, maybe):
setattr(self, maybe, getattr(self, "__maybe" + maybe))
def __repr__(self):
return "<win32com.gen_py.%s.%s>" % (__doc__, self.__class__.__name__)
def __getattr__(self, attr):
d = self.__dict__["_dispobj_"]
if d is not None:
return getattr(d, attr)
raise AttributeError(attr)
def __setattr__(self, attr, value):
if attr in self.__dict__:
self.__dict__[attr] = value
return
try:
d = self.__dict__["_dispobj_"]
if d is not None:
d.__setattr__(attr, value)
return
except AttributeError:
pass
self.__dict__[attr] = value
# Special methods don't use __getattr__ etc, so explicitly delegate here.
# Note however, that not all are safe to let bubble up - things like
# `bool(ob)` will break if the object defines __int__ but then raises an
# attribute error - eg, see #1753.
# It depends on what the wrapped COM object actually defines whether these
# will exist on the underlying object, so __init__ explicitly checks if they
# do and if so, wires them up.
def __maybe__call__(self, *args, **kwargs):
return self.__dict__["_dispobj_"].__call__(*args, **kwargs)
def __maybe__str__(self, *args):
return self.__dict__["_dispobj_"].__str__(*args)
def __maybe__int__(self, *args):
return self.__dict__["_dispobj_"].__int__(*args)
def __maybe__iter__(self):
return self.__dict__["_dispobj_"].__iter__()
def __maybe__len__(self):
return self.__dict__["_dispobj_"].__len__()
def __maybe__nonzero__(self):
return self.__dict__["_dispobj_"].__nonzero__()
# A very simple VARIANT class. Only to be used with poorly-implemented COM
# objects. If an object accepts an arg which is a simple "VARIANT", but still
# is very pickly about the actual variant type (eg, isn't happy with a VT_I4,
# which it would get from a Python integer), you can use this to force a
# particular VT.
class VARIANT(object):
def __init__(self, vt, value):
self.varianttype = vt
self._value = value
# 'value' is a property so when set by pythoncom it gets any magic wrapping
# which normally happens for result objects
def _get_value(self):
return self._value
def _set_value(self, newval):
self._value = _get_good_object_(newval)
def _del_value(self):
del self._value
value = property(_get_value, _set_value, _del_value)
def __repr__(self):
return "win32com.client.VARIANT(%r, %r)" % (self.varianttype, self._value)

View file

@ -0,0 +1,789 @@
"""Contains knowledge to build a COM object definition.
This module is used by both the @dynamic@ and @makepy@ modules to build
all knowledge of a COM object.
This module contains classes which contain the actual knowledge of the object.
This include parameter and return type information, the COM dispid and CLSID, etc.
Other modules may use this information to generate .py files, use the information
dynamically, or possibly even generate .html documentation for objects.
"""
#
# NOTES: DispatchItem and MapEntry used by dynamic.py.
# the rest is used by makepy.py
#
# OleItem, DispatchItem, MapEntry, BuildCallList() is used by makepy
import datetime
import string
import sys
from keyword import iskeyword
import pythoncom
import winerror
from pywintypes import TimeType
# It isn't really clear what the quoting rules are in a C/IDL string and
# literals like a quote char and backslashes makes life a little painful to
# always render the string perfectly - so just punt and fall-back to a repr()
def _makeDocString(s):
if sys.version_info < (3,):
s = s.encode("mbcs")
return repr(s)
error = "PythonCOM.Client.Build error"
class NotSupportedException(Exception):
pass # Raised when we cant support a param type.
DropIndirection = "DropIndirection"
NoTranslateTypes = [
pythoncom.VT_BOOL,
pythoncom.VT_CLSID,
pythoncom.VT_CY,
pythoncom.VT_DATE,
pythoncom.VT_DECIMAL,
pythoncom.VT_EMPTY,
pythoncom.VT_ERROR,
pythoncom.VT_FILETIME,
pythoncom.VT_HRESULT,
pythoncom.VT_I1,
pythoncom.VT_I2,
pythoncom.VT_I4,
pythoncom.VT_I8,
pythoncom.VT_INT,
pythoncom.VT_NULL,
pythoncom.VT_R4,
pythoncom.VT_R8,
pythoncom.VT_NULL,
pythoncom.VT_STREAM,
pythoncom.VT_UI1,
pythoncom.VT_UI2,
pythoncom.VT_UI4,
pythoncom.VT_UI8,
pythoncom.VT_UINT,
pythoncom.VT_VOID,
]
NoTranslateMap = {}
for v in NoTranslateTypes:
NoTranslateMap[v] = None
class MapEntry:
"Simple holder for named attibutes - items in a map."
def __init__(
self,
desc_or_id,
names=None,
doc=None,
resultCLSID=pythoncom.IID_NULL,
resultDoc=None,
hidden=0,
):
if type(desc_or_id) == type(0):
self.dispid = desc_or_id
self.desc = None
else:
self.dispid = desc_or_id[0]
self.desc = desc_or_id
self.names = names
self.doc = doc
self.resultCLSID = resultCLSID
self.resultDocumentation = resultDoc
self.wasProperty = (
0 # Have I been transformed into a function so I can pass args?
)
self.hidden = hidden
def __repr__(self):
return (
"MapEntry(dispid={s.dispid}, desc={s.desc}, names={s.names}, doc={s.doc!r}, "
"resultCLSID={s.resultCLSID}, resultDocumentation={s.resultDocumentation}, "
"wasProperty={s.wasProperty}, hidden={s.hidden}"
).format(s=self)
def GetResultCLSID(self):
rc = self.resultCLSID
if rc == pythoncom.IID_NULL:
return None
return rc
# Return a string, suitable for output - either "'{...}'" or "None"
def GetResultCLSIDStr(self):
rc = self.GetResultCLSID()
if rc is None:
return "None"
return repr(
str(rc)
) # Convert the IID object to a string, then to a string in a string.
def GetResultName(self):
if self.resultDocumentation is None:
return None
return self.resultDocumentation[0]
class OleItem:
typename = "OleItem"
def __init__(self, doc=None):
self.doc = doc
if self.doc:
self.python_name = MakePublicAttributeName(self.doc[0])
else:
self.python_name = None
self.bWritten = 0
self.bIsDispatch = 0
self.bIsSink = 0
self.clsid = None
self.co_class = None
class DispatchItem(OleItem):
typename = "DispatchItem"
def __init__(self, typeinfo=None, attr=None, doc=None, bForUser=1):
OleItem.__init__(self, doc)
self.propMap = {}
self.propMapGet = {}
self.propMapPut = {}
self.mapFuncs = {}
self.defaultDispatchName = None
self.hidden = 0
if typeinfo:
self.Build(typeinfo, attr, bForUser)
def _propMapPutCheck_(self, key, item):
ins, outs, opts = self.CountInOutOptArgs(item.desc[2])
if ins > 1: # if a Put property takes more than 1 arg:
if opts + 1 == ins or ins == item.desc[6] + 1:
newKey = "Set" + key
deleteExisting = 0 # This one is still OK
else:
deleteExisting = 1 # No good to us
if key in self.mapFuncs or key in self.propMapGet:
newKey = "Set" + key
else:
newKey = key
item.wasProperty = 1
self.mapFuncs[newKey] = item
if deleteExisting:
del self.propMapPut[key]
def _propMapGetCheck_(self, key, item):
ins, outs, opts = self.CountInOutOptArgs(item.desc[2])
if ins > 0: # if a Get property takes _any_ in args:
if item.desc[6] == ins or ins == opts:
newKey = "Get" + key
deleteExisting = 0 # This one is still OK
else:
deleteExisting = 1 # No good to us
if key in self.mapFuncs:
newKey = "Get" + key
else:
newKey = key
item.wasProperty = 1
self.mapFuncs[newKey] = item
if deleteExisting:
del self.propMapGet[key]
def _AddFunc_(self, typeinfo, fdesc, bForUser):
assert fdesc.desckind == pythoncom.DESCKIND_FUNCDESC
id = fdesc.memid
funcflags = fdesc.wFuncFlags
try:
names = typeinfo.GetNames(id)
name = names[0]
except pythoncom.ole_error:
name = ""
names = None
doc = None
try:
if bForUser:
doc = typeinfo.GetDocumentation(id)
except pythoncom.ole_error:
pass
if id == 0 and name:
self.defaultDispatchName = name
invkind = fdesc.invkind
# We need to translate any Alias', Enums, structs etc in result and args
typerepr, flag, defval = fdesc.rettype
# sys.stderr.write("%s result - %s -> " % (name, typerepr))
typerepr, resultCLSID, resultDoc = _ResolveType(typerepr, typeinfo)
# sys.stderr.write("%s\n" % (typerepr,))
fdesc.rettype = typerepr, flag, defval, resultCLSID
# Translate any Alias or Enums in argument list.
argList = []
for argDesc in fdesc.args:
typerepr, flag, defval = argDesc
# sys.stderr.write("%s arg - %s -> " % (name, typerepr))
arg_type, arg_clsid, arg_doc = _ResolveType(typerepr, typeinfo)
argDesc = arg_type, flag, defval, arg_clsid
# sys.stderr.write("%s\n" % (argDesc[0],))
argList.append(argDesc)
fdesc.args = tuple(argList)
hidden = (funcflags & pythoncom.FUNCFLAG_FHIDDEN) != 0
if invkind == pythoncom.INVOKE_PROPERTYGET:
map = self.propMapGet
# This is not the best solution, but I dont think there is
# one without specific "set" syntax.
# If there is a single PUT or PUTREF, it will function as a property.
# If there are both, then the PUT remains a property, and the PUTREF
# gets transformed into a function.
# (in vb, PUT=="obj=other_obj", PUTREF="set obj=other_obj
elif invkind in (pythoncom.INVOKE_PROPERTYPUT, pythoncom.INVOKE_PROPERTYPUTREF):
# Special case
existing = self.propMapPut.get(name, None)
if existing is not None:
if existing.desc[4] == pythoncom.INVOKE_PROPERTYPUT: # Keep this one
map = self.mapFuncs
name = "Set" + name
else: # Existing becomes a func.
existing.wasProperty = 1
self.mapFuncs["Set" + name] = existing
map = self.propMapPut # existing gets overwritten below.
else:
map = self.propMapPut # first time weve seen it.
elif invkind == pythoncom.INVOKE_FUNC:
map = self.mapFuncs
else:
map = None
if not map is None:
# if map.has_key(name):
# sys.stderr.write("Warning - overwriting existing method/attribute %s\n" % name)
map[name] = MapEntry(fdesc, names, doc, resultCLSID, resultDoc, hidden)
# any methods that can't be reached via DISPATCH we return None
# for, so dynamic dispatch doesnt see it.
if fdesc.funckind != pythoncom.FUNC_DISPATCH:
return None
return (name, map)
return None
def _AddVar_(self, typeinfo, vardesc, bForUser):
### need pythoncom.VARFLAG_FRESTRICTED ...
### then check it
assert vardesc.desckind == pythoncom.DESCKIND_VARDESC
if vardesc.varkind == pythoncom.VAR_DISPATCH:
id = vardesc.memid
names = typeinfo.GetNames(id)
# Translate any Alias or Enums in result.
typerepr, flags, defval = vardesc.elemdescVar
typerepr, resultCLSID, resultDoc = _ResolveType(typerepr, typeinfo)
vardesc.elemdescVar = typerepr, flags, defval
doc = None
try:
if bForUser:
doc = typeinfo.GetDocumentation(id)
except pythoncom.ole_error:
pass
# handle the enumerator specially
map = self.propMap
# Check if the element is hidden.
hidden = (vardesc.wVarFlags & 0x40) != 0 # VARFLAG_FHIDDEN
map[names[0]] = MapEntry(
vardesc, names, doc, resultCLSID, resultDoc, hidden
)
return (names[0], map)
else:
return None
def Build(self, typeinfo, attr, bForUser=1):
self.clsid = attr[0]
self.bIsDispatch = (attr.wTypeFlags & pythoncom.TYPEFLAG_FDISPATCHABLE) != 0
if typeinfo is None:
return
# Loop over all methods
for j in range(attr[6]):
fdesc = typeinfo.GetFuncDesc(j)
self._AddFunc_(typeinfo, fdesc, bForUser)
# Loop over all variables (ie, properties)
for j in range(attr[7]):
fdesc = typeinfo.GetVarDesc(j)
self._AddVar_(typeinfo, fdesc, bForUser)
# Now post-process the maps. For any "Get" or "Set" properties
# that have arguments, we must turn them into methods. If a method
# of the same name already exists, change the name.
for key, item in list(self.propMapGet.items()):
self._propMapGetCheck_(key, item)
for key, item in list(self.propMapPut.items()):
self._propMapPutCheck_(key, item)
def CountInOutOptArgs(self, argTuple):
"Return tuple counting in/outs/OPTS. Sum of result may not be len(argTuple), as some args may be in/out."
ins = out = opts = 0
for argCheck in argTuple:
inOut = argCheck[1]
if inOut == 0:
ins = ins + 1
out = out + 1
else:
if inOut & pythoncom.PARAMFLAG_FIN:
ins = ins + 1
if inOut & pythoncom.PARAMFLAG_FOPT:
opts = opts + 1
if inOut & pythoncom.PARAMFLAG_FOUT:
out = out + 1
return ins, out, opts
def MakeFuncMethod(self, entry, name, bMakeClass=1):
# If we have a type description, and not varargs...
if entry.desc is not None and (len(entry.desc) < 6 or entry.desc[6] != -1):
return self.MakeDispatchFuncMethod(entry, name, bMakeClass)
else:
return self.MakeVarArgsFuncMethod(entry, name, bMakeClass)
def MakeDispatchFuncMethod(self, entry, name, bMakeClass=1):
fdesc = entry.desc
doc = entry.doc
names = entry.names
ret = []
if bMakeClass:
linePrefix = "\t"
defNamedOptArg = "defaultNamedOptArg"
defNamedNotOptArg = "defaultNamedNotOptArg"
defUnnamedArg = "defaultUnnamedArg"
else:
linePrefix = ""
defNamedOptArg = "pythoncom.Missing"
defNamedNotOptArg = "pythoncom.Missing"
defUnnamedArg = "pythoncom.Missing"
defOutArg = "pythoncom.Missing"
id = fdesc[0]
s = (
linePrefix
+ "def "
+ name
+ "(self"
+ BuildCallList(
fdesc,
names,
defNamedOptArg,
defNamedNotOptArg,
defUnnamedArg,
defOutArg,
)
+ "):"
)
ret.append(s)
if doc and doc[1]:
ret.append(linePrefix + "\t" + _makeDocString(doc[1]))
resclsid = entry.GetResultCLSID()
if resclsid:
resclsid = "'%s'" % resclsid
else:
resclsid = "None"
# Strip the default values from the arg desc
retDesc = fdesc[8][:2]
argsDesc = tuple([what[:2] for what in fdesc[2]])
# The runtime translation of the return types is expensive, so when we know the
# return type of the function, there is no need to check the type at runtime.
# To qualify, this function must return a "simple" type, and have no byref args.
# Check if we have byrefs or anything in the args which mean we still need a translate.
param_flags = [what[1] for what in fdesc[2]]
bad_params = [
flag
for flag in param_flags
if flag & (pythoncom.PARAMFLAG_FOUT | pythoncom.PARAMFLAG_FRETVAL) != 0
]
s = None
if len(bad_params) == 0 and len(retDesc) == 2 and retDesc[1] == 0:
rd = retDesc[0]
if rd in NoTranslateMap:
s = "%s\treturn self._oleobj_.InvokeTypes(%d, LCID, %s, %s, %s%s)" % (
linePrefix,
id,
fdesc[4],
retDesc,
argsDesc,
_BuildArgList(fdesc, names),
)
elif rd in [pythoncom.VT_DISPATCH, pythoncom.VT_UNKNOWN]:
s = "%s\tret = self._oleobj_.InvokeTypes(%d, LCID, %s, %s, %s%s)\n" % (
linePrefix,
id,
fdesc[4],
retDesc,
repr(argsDesc),
_BuildArgList(fdesc, names),
)
s = s + "%s\tif ret is not None:\n" % (linePrefix,)
if rd == pythoncom.VT_UNKNOWN:
s = s + "%s\t\t# See if this IUnknown is really an IDispatch\n" % (
linePrefix,
)
s = s + "%s\t\ttry:\n" % (linePrefix,)
s = (
s
+ "%s\t\t\tret = ret.QueryInterface(pythoncom.IID_IDispatch)\n"
% (linePrefix,)
)
s = s + "%s\t\texcept pythoncom.error:\n" % (linePrefix,)
s = s + "%s\t\t\treturn ret\n" % (linePrefix,)
s = s + "%s\t\tret = Dispatch(ret, %s, %s)\n" % (
linePrefix,
repr(name),
resclsid,
)
s = s + "%s\treturn ret" % (linePrefix)
elif rd == pythoncom.VT_BSTR:
s = "%s\t# Result is a Unicode object\n" % (linePrefix,)
s = (
s
+ "%s\treturn self._oleobj_.InvokeTypes(%d, LCID, %s, %s, %s%s)"
% (
linePrefix,
id,
fdesc[4],
retDesc,
repr(argsDesc),
_BuildArgList(fdesc, names),
)
)
# else s remains None
if s is None:
s = "%s\treturn self._ApplyTypes_(%d, %s, %s, %s, %s, %s%s)" % (
linePrefix,
id,
fdesc[4],
retDesc,
argsDesc,
repr(name),
resclsid,
_BuildArgList(fdesc, names),
)
ret.append(s)
ret.append("")
return ret
def MakeVarArgsFuncMethod(self, entry, name, bMakeClass=1):
fdesc = entry.desc
names = entry.names
doc = entry.doc
ret = []
argPrefix = "self"
if bMakeClass:
linePrefix = "\t"
else:
linePrefix = ""
ret.append(linePrefix + "def " + name + "(" + argPrefix + ", *args):")
if doc and doc[1]:
ret.append(linePrefix + "\t" + _makeDocString(doc[1]))
if fdesc:
invoketype = fdesc[4]
else:
invoketype = pythoncom.DISPATCH_METHOD
s = linePrefix + "\treturn self._get_good_object_(self._oleobj_.Invoke(*(("
ret.append(
s + str(entry.dispid) + ",0,%d,1)+args)),'%s')" % (invoketype, names[0])
)
ret.append("")
return ret
# Note - "DispatchItem" poorly named - need a new intermediate class.
class VTableItem(DispatchItem):
def Build(self, typeinfo, attr, bForUser=1):
DispatchItem.Build(self, typeinfo, attr, bForUser)
assert typeinfo is not None, "Cant build vtables without type info!"
meth_list = (
list(self.mapFuncs.values())
+ list(self.propMapGet.values())
+ list(self.propMapPut.values())
)
meth_list.sort(key=lambda m: m.desc[7])
# Now turn this list into the run-time representation
# (ready for immediate use or writing to gencache)
self.vtableFuncs = []
for entry in meth_list:
self.vtableFuncs.append((entry.names, entry.dispid, entry.desc))
# A Lazy dispatch item - builds an item on request using info from
# an ITypeComp. The dynamic module makes the called to build each item,
# and also holds the references to the typeinfo and typecomp.
class LazyDispatchItem(DispatchItem):
typename = "LazyDispatchItem"
def __init__(self, attr, doc):
self.clsid = attr[0]
DispatchItem.__init__(self, None, attr, doc, 0)
typeSubstMap = {
pythoncom.VT_INT: pythoncom.VT_I4,
pythoncom.VT_UINT: pythoncom.VT_UI4,
pythoncom.VT_HRESULT: pythoncom.VT_I4,
}
def _ResolveType(typerepr, itypeinfo):
# Resolve VT_USERDEFINED (often aliases or typed IDispatches)
if type(typerepr) == tuple:
indir_vt, subrepr = typerepr
if indir_vt == pythoncom.VT_PTR:
# If it is a VT_PTR to a VT_USERDEFINED that is an IDispatch/IUnknown,
# then it resolves to simply the object.
# Otherwise, it becomes a ByRef of the resolved type
# We need to drop an indirection level on pointer to user defined interfaces.
# eg, (VT_PTR, (VT_USERDEFINED, somehandle)) needs to become VT_DISPATCH
# only when "somehandle" is an object.
# but (VT_PTR, (VT_USERDEFINED, otherhandle)) doesnt get the indirection dropped.
was_user = type(subrepr) == tuple and subrepr[0] == pythoncom.VT_USERDEFINED
subrepr, sub_clsid, sub_doc = _ResolveType(subrepr, itypeinfo)
if was_user and subrepr in [
pythoncom.VT_DISPATCH,
pythoncom.VT_UNKNOWN,
pythoncom.VT_RECORD,
]:
# Drop the VT_PTR indirection
return subrepr, sub_clsid, sub_doc
# Change PTR indirection to byref
return subrepr | pythoncom.VT_BYREF, sub_clsid, sub_doc
if indir_vt == pythoncom.VT_SAFEARRAY:
# resolve the array element, and convert to VT_ARRAY
subrepr, sub_clsid, sub_doc = _ResolveType(subrepr, itypeinfo)
return pythoncom.VT_ARRAY | subrepr, sub_clsid, sub_doc
if indir_vt == pythoncom.VT_CARRAY: # runtime has no support for this yet.
# resolve the array element, and convert to VT_CARRAY
# sheesh - return _something_
return pythoncom.VT_CARRAY, None, None
if indir_vt == pythoncom.VT_USERDEFINED:
try:
resultTypeInfo = itypeinfo.GetRefTypeInfo(subrepr)
except pythoncom.com_error as details:
if details.hresult in [
winerror.TYPE_E_CANTLOADLIBRARY,
winerror.TYPE_E_LIBNOTREGISTERED,
]:
# an unregistered interface
return pythoncom.VT_UNKNOWN, None, None
raise
resultAttr = resultTypeInfo.GetTypeAttr()
typeKind = resultAttr.typekind
if typeKind == pythoncom.TKIND_ALIAS:
tdesc = resultAttr.tdescAlias
return _ResolveType(tdesc, resultTypeInfo)
elif typeKind in [pythoncom.TKIND_ENUM, pythoncom.TKIND_MODULE]:
# For now, assume Long
return pythoncom.VT_I4, None, None
elif typeKind == pythoncom.TKIND_DISPATCH:
clsid = resultTypeInfo.GetTypeAttr()[0]
retdoc = resultTypeInfo.GetDocumentation(-1)
return pythoncom.VT_DISPATCH, clsid, retdoc
elif typeKind in [pythoncom.TKIND_INTERFACE, pythoncom.TKIND_COCLASS]:
# XXX - should probably get default interface for CO_CLASS???
clsid = resultTypeInfo.GetTypeAttr()[0]
retdoc = resultTypeInfo.GetDocumentation(-1)
return pythoncom.VT_UNKNOWN, clsid, retdoc
elif typeKind == pythoncom.TKIND_RECORD:
return pythoncom.VT_RECORD, None, None
raise NotSupportedException("Can not resolve alias or user-defined type")
return typeSubstMap.get(typerepr, typerepr), None, None
def _BuildArgList(fdesc, names):
"Builds list of args to the underlying Invoke method."
# Word has TypeInfo for Insert() method, but says "no args"
numArgs = max(fdesc[6], len(fdesc[2]))
names = list(names)
while None in names:
i = names.index(None)
names[i] = "arg%d" % (i,)
# We've seen 'source safe' libraries offer the name of 'ret' params in
# 'names' - although we can't reproduce this, it would be insane to offer
# more args than we have arg infos for - hence the upper limit on names...
names = list(map(MakePublicAttributeName, names[1 : (numArgs + 1)]))
name_num = 0
while len(names) < numArgs:
names.append("arg%d" % (len(names),))
# As per BuildCallList(), avoid huge lines.
# Hack a "\n" at the end of every 5th name - "strides" would be handy
# here but don't exist in 2.2
for i in range(0, len(names), 5):
names[i] = names[i] + "\n\t\t\t"
return "," + ", ".join(names)
valid_identifier_chars = string.ascii_letters + string.digits + "_"
def demunge_leading_underscores(className):
i = 0
while className[i] == "_":
i += 1
assert i >= 2, "Should only be here with names starting with '__'"
return className[i - 1 :] + className[: i - 1]
# Given a "public name" (eg, the name of a class, function, etc)
# make sure it is a legal (and reasonable!) Python name.
def MakePublicAttributeName(className, is_global=False):
# Given a class attribute that needs to be public, convert it to a
# reasonable name.
# Also need to be careful that the munging doesnt
# create duplicates - eg, just removing a leading "_" is likely to cause
# a clash.
# if is_global is True, then the name is a global variable that may
# overwrite a builtin - eg, "None"
if className[:2] == "__":
return demunge_leading_underscores(className)
elif className == "None":
# assign to None is evil (and SyntaxError in 2.4, even though
# iskeyword says False there) - note that if it was a global
# it would get picked up below
className = "NONE"
elif iskeyword(className):
# most keywords are lower case (except True, False etc in py3k)
ret = className.capitalize()
# but those which aren't get forced upper.
if ret == className:
ret = ret.upper()
return ret
elif is_global and hasattr(__builtins__, className):
# builtins may be mixed case. If capitalizing it doesn't change it,
# force to all uppercase (eg, "None", "True" become "NONE", "TRUE"
ret = className.capitalize()
if ret == className: # didn't change - force all uppercase.
ret = ret.upper()
return ret
# Strip non printable chars
return "".join([char for char in className if char in valid_identifier_chars])
# Given a default value passed by a type library, return a string with
# an appropriate repr() for the type.
# Takes a raw ELEMDESC and returns a repr string, or None
# (NOTE: The string itself may be '"None"', which is valid, and different to None.
# XXX - To do: Dates are probably screwed, but can they come in?
def MakeDefaultArgRepr(defArgVal):
try:
inOut = defArgVal[1]
except IndexError:
# something strange - assume is in param.
inOut = pythoncom.PARAMFLAG_FIN
if inOut & pythoncom.PARAMFLAG_FHASDEFAULT:
# times need special handling...
val = defArgVal[2]
if isinstance(val, datetime.datetime):
# VARIANT <-> SYSTEMTIME conversions always lose any sub-second
# resolution, so just use a 'timetuple' here.
return repr(tuple(val.utctimetuple()))
if type(val) is TimeType:
# must be the 'old' pywintypes time object...
year = val.year
month = val.month
day = val.day
hour = val.hour
minute = val.minute
second = val.second
msec = val.msec
return (
"pywintypes.Time((%(year)d, %(month)d, %(day)d, %(hour)d, %(minute)d, %(second)d,0,0,0,%(msec)d))"
% locals()
)
return repr(val)
return None
def BuildCallList(
fdesc,
names,
defNamedOptArg,
defNamedNotOptArg,
defUnnamedArg,
defOutArg,
is_comment=False,
):
"Builds a Python declaration for a method."
# Names[0] is the func name - param names are from 1.
numArgs = len(fdesc[2])
numOptArgs = fdesc[6]
strval = ""
if numOptArgs == -1: # Special value that says "var args after here"
firstOptArg = numArgs
numArgs = numArgs - 1
else:
firstOptArg = numArgs - numOptArgs
for arg in range(numArgs):
try:
argName = names[arg + 1]
namedArg = argName is not None
except IndexError:
namedArg = 0
if not namedArg:
argName = "arg%d" % (arg)
thisdesc = fdesc[2][arg]
# See if the IDL specified a default value
defArgVal = MakeDefaultArgRepr(thisdesc)
if defArgVal is None:
# Out params always get their special default
if (
thisdesc[1] & (pythoncom.PARAMFLAG_FOUT | pythoncom.PARAMFLAG_FIN)
== pythoncom.PARAMFLAG_FOUT
):
defArgVal = defOutArg
else:
# Unnamed arg - always allow default values.
if namedArg:
# Is a named argument
if arg >= firstOptArg:
defArgVal = defNamedOptArg
else:
defArgVal = defNamedNotOptArg
else:
defArgVal = defUnnamedArg
argName = MakePublicAttributeName(argName)
# insanely long lines with an 'encoding' flag crashes python 2.4.0
# keep 5 args per line
# This may still fail if the arg names are insane, but that seems
# unlikely. See also _BuildArgList()
if (arg + 1) % 5 == 0:
strval = strval + "\n"
if is_comment:
strval = strval + "#"
strval = strval + "\t\t\t"
strval = strval + ", " + argName
if defArgVal:
strval = strval + "=" + defArgVal
if numOptArgs == -1:
strval = strval + ", *" + names[-1]
return strval
if __name__ == "__main__":
print("Use 'makepy.py' to generate Python code - this module is just a helper")

View file

@ -0,0 +1,619 @@
"""A utility for browsing COM objects.
Usage:
Command Prompt
Use the command *"python.exe combrowse.py"*. This will display
display a fairly small, modal dialog.
Pythonwin
Use the "Run Script" menu item, and this will create the browser in an
MDI window. This window can be fully resized.
Details
This module allows browsing of registered Type Libraries, COM categories,
and running COM objects. The display is similar to the Pythonwin object
browser, and displays the objects in a hierarchical window.
Note that this module requires the win32ui (ie, Pythonwin) distribution to
work.
"""
import sys
import pythoncom
import win32api
import win32con
import win32ui
from pywin.tools import browser
from win32com.client import util
class HLIRoot(browser.HLIPythonObject):
def __init__(self, title):
super().__init__(name=title)
def GetSubList(self):
return [
HLIHeadingCategory(),
HLI_IEnumMoniker(
pythoncom.GetRunningObjectTable().EnumRunning(), "Running Objects"
),
HLIHeadingRegisterdTypeLibs(),
]
def __cmp__(self, other):
return cmp(self.name, other.name)
class HLICOM(browser.HLIPythonObject):
def GetText(self):
return self.name
def CalculateIsExpandable(self):
return 1
class HLICLSID(HLICOM):
def __init__(self, myobject, name=None):
if type(myobject) == type(""):
myobject = pythoncom.MakeIID(myobject)
if name is None:
try:
name = pythoncom.ProgIDFromCLSID(myobject)
except pythoncom.com_error:
name = str(myobject)
name = "IID: " + name
HLICOM.__init__(self, myobject, name)
def CalculateIsExpandable(self):
return 0
def GetSubList(self):
return []
class HLI_Interface(HLICOM):
pass
class HLI_Enum(HLI_Interface):
def GetBitmapColumn(self):
return 0 # Always a folder.
def CalculateIsExpandable(self):
if self.myobject is not None:
rc = len(self.myobject.Next(1)) > 0
self.myobject.Reset()
else:
rc = 0
return rc
pass
class HLI_IEnumMoniker(HLI_Enum):
def GetSubList(self):
ctx = pythoncom.CreateBindCtx()
ret = []
for mon in util.Enumerator(self.myobject):
ret.append(HLI_IMoniker(mon, mon.GetDisplayName(ctx, None)))
return ret
class HLI_IMoniker(HLI_Interface):
def GetSubList(self):
ret = []
ret.append(browser.MakeHLI(self.myobject.Hash(), "Hash Value"))
subenum = self.myobject.Enum(1)
ret.append(HLI_IEnumMoniker(subenum, "Sub Monikers"))
return ret
class HLIHeadingCategory(HLICOM):
"A tree heading for registered categories"
def GetText(self):
return "Registered Categories"
def GetSubList(self):
catinf = pythoncom.CoCreateInstance(
pythoncom.CLSID_StdComponentCategoriesMgr,
None,
pythoncom.CLSCTX_INPROC,
pythoncom.IID_ICatInformation,
)
enum = util.Enumerator(catinf.EnumCategories())
ret = []
try:
for catid, lcid, desc in enum:
ret.append(HLICategory((catid, lcid, desc)))
except pythoncom.com_error:
# Registered categories occasionally seem to give spurious errors.
pass # Use what we already have.
return ret
class HLICategory(HLICOM):
"An actual Registered Category"
def GetText(self):
desc = self.myobject[2]
if not desc:
desc = "(unnamed category)"
return desc
def GetSubList(self):
win32ui.DoWaitCursor(1)
catid, lcid, desc = self.myobject
catinf = pythoncom.CoCreateInstance(
pythoncom.CLSID_StdComponentCategoriesMgr,
None,
pythoncom.CLSCTX_INPROC,
pythoncom.IID_ICatInformation,
)
ret = []
for clsid in util.Enumerator(catinf.EnumClassesOfCategories((catid,), ())):
ret.append(HLICLSID(clsid))
win32ui.DoWaitCursor(0)
return ret
class HLIHelpFile(HLICOM):
def CalculateIsExpandable(self):
return 0
def GetText(self):
import os
fname, ctx = self.myobject
base = os.path.split(fname)[1]
return "Help reference in %s" % (base)
def TakeDefaultAction(self):
fname, ctx = self.myobject
if ctx:
cmd = win32con.HELP_CONTEXT
else:
cmd = win32con.HELP_FINDER
win32api.WinHelp(win32ui.GetMainFrame().GetSafeHwnd(), fname, cmd, ctx)
def GetBitmapColumn(self):
return 6
class HLIRegisteredTypeLibrary(HLICOM):
def GetSubList(self):
import os
clsidstr, versionStr = self.myobject
collected = []
helpPath = ""
key = win32api.RegOpenKey(
win32con.HKEY_CLASSES_ROOT, "TypeLib\\%s\\%s" % (clsidstr, versionStr)
)
win32ui.DoWaitCursor(1)
try:
num = 0
while 1:
try:
subKey = win32api.RegEnumKey(key, num)
except win32api.error:
break
hSubKey = win32api.RegOpenKey(key, subKey)
try:
value, typ = win32api.RegQueryValueEx(hSubKey, None)
if typ == win32con.REG_EXPAND_SZ:
value = win32api.ExpandEnvironmentStrings(value)
except win32api.error:
value = ""
if subKey == "HELPDIR":
helpPath = value
elif subKey == "Flags":
flags = value
else:
try:
lcid = int(subKey)
lcidkey = win32api.RegOpenKey(key, subKey)
# Enumerate the platforms
lcidnum = 0
while 1:
try:
platform = win32api.RegEnumKey(lcidkey, lcidnum)
except win32api.error:
break
try:
hplatform = win32api.RegOpenKey(lcidkey, platform)
fname, typ = win32api.RegQueryValueEx(hplatform, None)
if typ == win32con.REG_EXPAND_SZ:
fname = win32api.ExpandEnvironmentStrings(fname)
except win32api.error:
fname = ""
collected.append((lcid, platform, fname))
lcidnum = lcidnum + 1
win32api.RegCloseKey(lcidkey)
except ValueError:
pass
num = num + 1
finally:
win32ui.DoWaitCursor(0)
win32api.RegCloseKey(key)
# Now, loop over my collected objects, adding a TypeLib and a HelpFile
ret = []
# if helpPath: ret.append(browser.MakeHLI(helpPath, "Help Path"))
ret.append(HLICLSID(clsidstr))
for lcid, platform, fname in collected:
extraDescs = []
if platform != "win32":
extraDescs.append(platform)
if lcid:
extraDescs.append("locale=%s" % lcid)
extraDesc = ""
if extraDescs:
extraDesc = " (%s)" % ", ".join(extraDescs)
ret.append(HLITypeLib(fname, "Type Library" + extraDesc))
ret.sort()
return ret
class HLITypeLibEntry(HLICOM):
def GetText(self):
tlb, index = self.myobject
name, doc, ctx, helpFile = tlb.GetDocumentation(index)
try:
typedesc = HLITypeKinds[tlb.GetTypeInfoType(index)][1]
except KeyError:
typedesc = "Unknown!"
return name + " - " + typedesc
def GetSubList(self):
tlb, index = self.myobject
name, doc, ctx, helpFile = tlb.GetDocumentation(index)
ret = []
if doc:
ret.append(browser.HLIDocString(doc, "Doc"))
if helpFile:
ret.append(HLIHelpFile((helpFile, ctx)))
return ret
class HLICoClass(HLITypeLibEntry):
def GetSubList(self):
ret = HLITypeLibEntry.GetSubList(self)
tlb, index = self.myobject
typeinfo = tlb.GetTypeInfo(index)
attr = typeinfo.GetTypeAttr()
for j in range(attr[8]):
flags = typeinfo.GetImplTypeFlags(j)
refType = typeinfo.GetRefTypeInfo(typeinfo.GetRefTypeOfImplType(j))
refAttr = refType.GetTypeAttr()
ret.append(
browser.MakeHLI(refAttr[0], "Name=%s, Flags = %d" % (refAttr[0], flags))
)
return ret
class HLITypeLibMethod(HLITypeLibEntry):
def __init__(self, ob, name=None):
self.entry_type = "Method"
HLITypeLibEntry.__init__(self, ob, name)
def GetSubList(self):
ret = HLITypeLibEntry.GetSubList(self)
tlb, index = self.myobject
typeinfo = tlb.GetTypeInfo(index)
attr = typeinfo.GetTypeAttr()
for i in range(attr[7]):
ret.append(HLITypeLibProperty((typeinfo, i)))
for i in range(attr[6]):
ret.append(HLITypeLibFunction((typeinfo, i)))
return ret
class HLITypeLibEnum(HLITypeLibEntry):
def __init__(self, myitem):
typelib, index = myitem
typeinfo = typelib.GetTypeInfo(index)
self.id = typeinfo.GetVarDesc(index)[0]
name = typeinfo.GetNames(self.id)[0]
HLITypeLibEntry.__init__(self, myitem, name)
def GetText(self):
return self.name + " - Enum/Module"
def GetSubList(self):
ret = []
typelib, index = self.myobject
typeinfo = typelib.GetTypeInfo(index)
attr = typeinfo.GetTypeAttr()
for j in range(attr[7]):
vdesc = typeinfo.GetVarDesc(j)
name = typeinfo.GetNames(vdesc[0])[0]
ret.append(browser.MakeHLI(vdesc[1], name))
return ret
class HLITypeLibProperty(HLICOM):
def __init__(self, myitem):
typeinfo, index = myitem
self.id = typeinfo.GetVarDesc(index)[0]
name = typeinfo.GetNames(self.id)[0]
HLICOM.__init__(self, myitem, name)
def GetText(self):
return self.name + " - Property"
def GetSubList(self):
ret = []
typeinfo, index = self.myobject
names = typeinfo.GetNames(self.id)
if len(names) > 1:
ret.append(browser.MakeHLI(names[1:], "Named Params"))
vd = typeinfo.GetVarDesc(index)
ret.append(browser.MakeHLI(self.id, "Dispatch ID"))
ret.append(browser.MakeHLI(vd[1], "Value"))
ret.append(browser.MakeHLI(vd[2], "Elem Desc"))
ret.append(browser.MakeHLI(vd[3], "Var Flags"))
ret.append(browser.MakeHLI(vd[4], "Var Kind"))
return ret
class HLITypeLibFunction(HLICOM):
funckinds = {
pythoncom.FUNC_VIRTUAL: "Virtual",
pythoncom.FUNC_PUREVIRTUAL: "Pure Virtual",
pythoncom.FUNC_STATIC: "Static",
pythoncom.FUNC_DISPATCH: "Dispatch",
}
invokekinds = {
pythoncom.INVOKE_FUNC: "Function",
pythoncom.INVOKE_PROPERTYGET: "Property Get",
pythoncom.INVOKE_PROPERTYPUT: "Property Put",
pythoncom.INVOKE_PROPERTYPUTREF: "Property Put by reference",
}
funcflags = [
(pythoncom.FUNCFLAG_FRESTRICTED, "Restricted"),
(pythoncom.FUNCFLAG_FSOURCE, "Source"),
(pythoncom.FUNCFLAG_FBINDABLE, "Bindable"),
(pythoncom.FUNCFLAG_FREQUESTEDIT, "Request Edit"),
(pythoncom.FUNCFLAG_FDISPLAYBIND, "Display Bind"),
(pythoncom.FUNCFLAG_FDEFAULTBIND, "Default Bind"),
(pythoncom.FUNCFLAG_FHIDDEN, "Hidden"),
(pythoncom.FUNCFLAG_FUSESGETLASTERROR, "Uses GetLastError"),
]
vartypes = {
pythoncom.VT_EMPTY: "Empty",
pythoncom.VT_NULL: "NULL",
pythoncom.VT_I2: "Integer 2",
pythoncom.VT_I4: "Integer 4",
pythoncom.VT_R4: "Real 4",
pythoncom.VT_R8: "Real 8",
pythoncom.VT_CY: "CY",
pythoncom.VT_DATE: "Date",
pythoncom.VT_BSTR: "String",
pythoncom.VT_DISPATCH: "IDispatch",
pythoncom.VT_ERROR: "Error",
pythoncom.VT_BOOL: "BOOL",
pythoncom.VT_VARIANT: "Variant",
pythoncom.VT_UNKNOWN: "IUnknown",
pythoncom.VT_DECIMAL: "Decimal",
pythoncom.VT_I1: "Integer 1",
pythoncom.VT_UI1: "Unsigned integer 1",
pythoncom.VT_UI2: "Unsigned integer 2",
pythoncom.VT_UI4: "Unsigned integer 4",
pythoncom.VT_I8: "Integer 8",
pythoncom.VT_UI8: "Unsigned integer 8",
pythoncom.VT_INT: "Integer",
pythoncom.VT_UINT: "Unsigned integer",
pythoncom.VT_VOID: "Void",
pythoncom.VT_HRESULT: "HRESULT",
pythoncom.VT_PTR: "Pointer",
pythoncom.VT_SAFEARRAY: "SafeArray",
pythoncom.VT_CARRAY: "C Array",
pythoncom.VT_USERDEFINED: "User Defined",
pythoncom.VT_LPSTR: "Pointer to string",
pythoncom.VT_LPWSTR: "Pointer to Wide String",
pythoncom.VT_FILETIME: "File time",
pythoncom.VT_BLOB: "Blob",
pythoncom.VT_STREAM: "IStream",
pythoncom.VT_STORAGE: "IStorage",
pythoncom.VT_STORED_OBJECT: "Stored object",
pythoncom.VT_STREAMED_OBJECT: "Streamed object",
pythoncom.VT_BLOB_OBJECT: "Blob object",
pythoncom.VT_CF: "CF",
pythoncom.VT_CLSID: "CLSID",
}
type_flags = [
(pythoncom.VT_VECTOR, "Vector"),
(pythoncom.VT_ARRAY, "Array"),
(pythoncom.VT_BYREF, "ByRef"),
(pythoncom.VT_RESERVED, "Reserved"),
]
def __init__(self, myitem):
typeinfo, index = myitem
self.id = typeinfo.GetFuncDesc(index)[0]
name = typeinfo.GetNames(self.id)[0]
HLICOM.__init__(self, myitem, name)
def GetText(self):
return self.name + " - Function"
def MakeReturnTypeName(self, typ):
justtyp = typ & pythoncom.VT_TYPEMASK
try:
typname = self.vartypes[justtyp]
except KeyError:
typname = "?Bad type?"
for flag, desc in self.type_flags:
if flag & typ:
typname = "%s(%s)" % (desc, typname)
return typname
def MakeReturnType(self, returnTypeDesc):
if type(returnTypeDesc) == type(()):
first = returnTypeDesc[0]
result = self.MakeReturnType(first)
if first != pythoncom.VT_USERDEFINED:
result = result + " " + self.MakeReturnType(returnTypeDesc[1])
return result
else:
return self.MakeReturnTypeName(returnTypeDesc)
def GetSubList(self):
ret = []
typeinfo, index = self.myobject
names = typeinfo.GetNames(self.id)
ret.append(browser.MakeHLI(self.id, "Dispatch ID"))
if len(names) > 1:
ret.append(browser.MakeHLI(", ".join(names[1:]), "Named Params"))
fd = typeinfo.GetFuncDesc(index)
if fd[1]:
ret.append(browser.MakeHLI(fd[1], "Possible result values"))
if fd[8]:
typ, flags, default = fd[8]
val = self.MakeReturnType(typ)
if flags:
val = "%s (Flags=%d, default=%s)" % (val, flags, default)
ret.append(browser.MakeHLI(val, "Return Type"))
for argDesc in fd[2]:
typ, flags, default = argDesc
val = self.MakeReturnType(typ)
if flags:
val = "%s (Flags=%d)" % (val, flags)
if default is not None:
val = "%s (Default=%s)" % (val, default)
ret.append(browser.MakeHLI(val, "Argument"))
try:
fkind = self.funckinds[fd[3]]
except KeyError:
fkind = "Unknown"
ret.append(browser.MakeHLI(fkind, "Function Kind"))
try:
ikind = self.invokekinds[fd[4]]
except KeyError:
ikind = "Unknown"
ret.append(browser.MakeHLI(ikind, "Invoke Kind"))
# 5 = call conv
# 5 = offset vtbl
ret.append(browser.MakeHLI(fd[6], "Number Optional Params"))
flagDescs = []
for flag, desc in self.funcflags:
if flag & fd[9]:
flagDescs.append(desc)
if flagDescs:
ret.append(browser.MakeHLI(", ".join(flagDescs), "Function Flags"))
return ret
HLITypeKinds = {
pythoncom.TKIND_ENUM: (HLITypeLibEnum, "Enumeration"),
pythoncom.TKIND_RECORD: (HLITypeLibEntry, "Record"),
pythoncom.TKIND_MODULE: (HLITypeLibEnum, "Module"),
pythoncom.TKIND_INTERFACE: (HLITypeLibMethod, "Interface"),
pythoncom.TKIND_DISPATCH: (HLITypeLibMethod, "Dispatch"),
pythoncom.TKIND_COCLASS: (HLICoClass, "CoClass"),
pythoncom.TKIND_ALIAS: (HLITypeLibEntry, "Alias"),
pythoncom.TKIND_UNION: (HLITypeLibEntry, "Union"),
}
class HLITypeLib(HLICOM):
def GetSubList(self):
ret = []
ret.append(browser.MakeHLI(self.myobject, "Filename"))
try:
tlb = pythoncom.LoadTypeLib(self.myobject)
except pythoncom.com_error:
return [browser.MakeHLI("%s can not be loaded" % self.myobject)]
for i in range(tlb.GetTypeInfoCount()):
try:
ret.append(HLITypeKinds[tlb.GetTypeInfoType(i)][0]((tlb, i)))
except pythoncom.com_error:
ret.append(browser.MakeHLI("The type info can not be loaded!"))
ret.sort()
return ret
class HLIHeadingRegisterdTypeLibs(HLICOM):
"A tree heading for registered type libraries"
def GetText(self):
return "Registered Type Libraries"
def GetSubList(self):
# Explicit lookup in the registry.
ret = []
key = win32api.RegOpenKey(win32con.HKEY_CLASSES_ROOT, "TypeLib")
win32ui.DoWaitCursor(1)
try:
num = 0
while 1:
try:
keyName = win32api.RegEnumKey(key, num)
except win32api.error:
break
# Enumerate all version info
subKey = win32api.RegOpenKey(key, keyName)
name = None
try:
subNum = 0
bestVersion = 0.0
while 1:
try:
versionStr = win32api.RegEnumKey(subKey, subNum)
except win32api.error:
break
try:
versionFlt = float(versionStr)
except ValueError:
versionFlt = 0 # ????
if versionFlt > bestVersion:
bestVersion = versionFlt
name = win32api.RegQueryValue(subKey, versionStr)
subNum = subNum + 1
finally:
win32api.RegCloseKey(subKey)
if name is not None:
ret.append(HLIRegisteredTypeLibrary((keyName, versionStr), name))
num = num + 1
finally:
win32api.RegCloseKey(key)
win32ui.DoWaitCursor(0)
ret.sort()
return ret
def main(modal=True, mdi=False):
from pywin.tools import hierlist
root = HLIRoot("COM Browser")
if mdi and "pywin.framework.app" in sys.modules:
# do it in a MDI window
browser.MakeTemplate()
browser.template.OpenObject(root)
else:
dlg = browser.dynamic_browser(root)
if modal:
dlg.DoModal()
else:
dlg.CreateWindow()
dlg.ShowWindow()
if __name__ == "__main__":
main(modal=win32api.GetConsoleTitle())
ni = pythoncom._GetInterfaceCount()
ng = pythoncom._GetGatewayCount()
if ni or ng:
print("Warning - exiting with %d/%d objects alive" % (ni, ng))

View file

@ -0,0 +1,48 @@
"""Utilities for working with Connections"""
import pythoncom
import win32com.server.util
class SimpleConnection:
"A simple, single connection object"
def __init__(self, coInstance=None, eventInstance=None, eventCLSID=None, debug=0):
self.cp = None
self.cookie = None
self.debug = debug
if not coInstance is None:
self.Connect(coInstance, eventInstance, eventCLSID)
def __del__(self):
try:
self.Disconnect()
except pythoncom.error:
# Ignore disconnection as we are torn down.
pass
def _wrap(self, obj):
useDispatcher = None
if self.debug:
from win32com.server import dispatcher
useDispatcher = dispatcher.DefaultDebugDispatcher
return win32com.server.util.wrap(obj, useDispatcher=useDispatcher)
def Connect(self, coInstance, eventInstance, eventCLSID=None):
try:
oleobj = coInstance._oleobj_
except AttributeError:
oleobj = coInstance
cpc = oleobj.QueryInterface(pythoncom.IID_IConnectionPointContainer)
if eventCLSID is None:
eventCLSID = eventInstance.CLSID
comEventInstance = self._wrap(eventInstance)
self.cp = cpc.FindConnectionPoint(eventCLSID)
self.cookie = self.cp.Advise(comEventInstance)
def Disconnect(self):
if not self.cp is None:
if self.cookie:
self.cp.Unadvise(self.cookie)
self.cookie = None
self.cp = None

View file

@ -0,0 +1,708 @@
"""Support for dynamic COM client support.
Introduction
Dynamic COM client support is the ability to use a COM server without
prior knowledge of the server. This can be used to talk to almost all
COM servers, including much of MS Office.
In general, you should not use this module directly - see below.
Example
>>> import win32com.client
>>> xl = win32com.client.Dispatch("Excel.Application")
# The line above invokes the functionality of this class.
# xl is now an object we can use to talk to Excel.
>>> xl.Visible = 1 # The Excel window becomes visible.
"""
import traceback
import types
import pythoncom # Needed as code we eval() references it.
import win32com.client
import winerror
from pywintypes import IIDType
from . import build
debugging = 0 # General debugging
debugging_attr = 0 # Debugging dynamic attribute lookups.
LCID = 0x0
# These errors generally mean the property or method exists,
# but can't be used in this context - eg, property instead of a method, etc.
# Used to determine if we have a real error or not.
ERRORS_BAD_CONTEXT = [
winerror.DISP_E_MEMBERNOTFOUND,
winerror.DISP_E_BADPARAMCOUNT,
winerror.DISP_E_PARAMNOTOPTIONAL,
winerror.DISP_E_TYPEMISMATCH,
winerror.E_INVALIDARG,
]
ALL_INVOKE_TYPES = [
pythoncom.INVOKE_PROPERTYGET,
pythoncom.INVOKE_PROPERTYPUT,
pythoncom.INVOKE_PROPERTYPUTREF,
pythoncom.INVOKE_FUNC,
]
def debug_print(*args):
if debugging:
for arg in args:
print(arg, end=" ")
print()
def debug_attr_print(*args):
if debugging_attr:
for arg in args:
print(arg, end=" ")
print()
def MakeMethod(func, inst, cls):
return types.MethodType(func, inst)
# get the type objects for IDispatch and IUnknown
PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
PyIUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]
_GoodDispatchTypes = (str, IIDType)
_defaultDispatchItem = build.DispatchItem
def _GetGoodDispatch(IDispatch, clsctx=pythoncom.CLSCTX_SERVER):
# quick return for most common case
if isinstance(IDispatch, PyIDispatchType):
return IDispatch
if isinstance(IDispatch, _GoodDispatchTypes):
try:
IDispatch = pythoncom.connect(IDispatch)
except pythoncom.ole_error:
IDispatch = pythoncom.CoCreateInstance(
IDispatch, None, clsctx, pythoncom.IID_IDispatch
)
else:
# may already be a wrapped class.
IDispatch = getattr(IDispatch, "_oleobj_", IDispatch)
return IDispatch
def _GetGoodDispatchAndUserName(IDispatch, userName, clsctx):
# Get a dispatch object, and a 'user name' (ie, the name as
# displayed to the user in repr() etc.
if userName is None:
if isinstance(IDispatch, str):
userName = IDispatch
## ??? else userName remains None ???
else:
userName = str(userName)
return (_GetGoodDispatch(IDispatch, clsctx), userName)
def _GetDescInvokeType(entry, invoke_type):
# determine the wFlags argument passed as input to IDispatch::Invoke
# Only ever called by __getattr__ and __setattr__ from dynamic objects!
# * `entry` is a MapEntry with whatever typeinfo we have about the property we are getting/setting.
# * `invoke_type` is either INVOKE_PROPERTYGET | INVOKE_PROPERTYSET and really just
# means "called by __getattr__" or "called by __setattr__"
if not entry or not entry.desc:
return invoke_type
if entry.desc.desckind == pythoncom.DESCKIND_VARDESC:
return invoke_type
# So it's a FUNCDESC - just use what it specifies.
return entry.desc.invkind
def Dispatch(
IDispatch,
userName=None,
createClass=None,
typeinfo=None,
UnicodeToString=None,
clsctx=pythoncom.CLSCTX_SERVER,
):
assert UnicodeToString is None, "this is deprecated and will go away"
IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx)
if createClass is None:
createClass = CDispatch
lazydata = None
try:
if typeinfo is None:
typeinfo = IDispatch.GetTypeInfo()
if typeinfo is not None:
try:
# try for a typecomp
typecomp = typeinfo.GetTypeComp()
lazydata = typeinfo, typecomp
except pythoncom.com_error:
pass
except pythoncom.com_error:
typeinfo = None
olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata)
return createClass(IDispatch, olerepr, userName, lazydata=lazydata)
def MakeOleRepr(IDispatch, typeinfo, typecomp):
olerepr = None
if typeinfo is not None:
try:
attr = typeinfo.GetTypeAttr()
# If the type info is a special DUAL interface, magically turn it into
# a DISPATCH typeinfo.
if (
attr[5] == pythoncom.TKIND_INTERFACE
and attr[11] & pythoncom.TYPEFLAG_FDUAL
):
# Get corresponding Disp interface;
# -1 is a special value which does this for us.
href = typeinfo.GetRefTypeOfImplType(-1)
typeinfo = typeinfo.GetRefTypeInfo(href)
attr = typeinfo.GetTypeAttr()
if typecomp is None:
olerepr = build.DispatchItem(typeinfo, attr, None, 0)
else:
olerepr = build.LazyDispatchItem(attr, None)
except pythoncom.ole_error:
pass
if olerepr is None:
olerepr = build.DispatchItem()
return olerepr
def DumbDispatch(
IDispatch,
userName=None,
createClass=None,
UnicodeToString=None,
clsctx=pythoncom.CLSCTX_SERVER,
):
"Dispatch with no type info"
assert UnicodeToString is None, "this is deprecated and will go away"
IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx)
if createClass is None:
createClass = CDispatch
return createClass(IDispatch, build.DispatchItem(), userName)
class CDispatch:
def __init__(
self, IDispatch, olerepr, userName=None, UnicodeToString=None, lazydata=None
):
assert UnicodeToString is None, "this is deprecated and will go away"
if userName is None:
userName = "<unknown>"
self.__dict__["_oleobj_"] = IDispatch
self.__dict__["_username_"] = userName
self.__dict__["_olerepr_"] = olerepr
self.__dict__["_mapCachedItems_"] = {}
self.__dict__["_builtMethods_"] = {}
self.__dict__["_enum_"] = None
self.__dict__["_unicode_to_string_"] = None
self.__dict__["_lazydata_"] = lazydata
def __call__(self, *args):
"Provide 'default dispatch' COM functionality - allow instance to be called"
if self._olerepr_.defaultDispatchName:
invkind, dispid = self._find_dispatch_type_(
self._olerepr_.defaultDispatchName
)
else:
invkind, dispid = (
pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET,
pythoncom.DISPID_VALUE,
)
if invkind is not None:
allArgs = (dispid, LCID, invkind, 1) + args
return self._get_good_object_(
self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None
)
raise TypeError("This dispatch object does not define a default method")
def __bool__(self):
return True # ie "if object:" should always be "true" - without this, __len__ is tried.
# _Possibly_ want to defer to __len__ if available, but Im not sure this is
# desirable???
def __repr__(self):
return "<COMObject %s>" % (self._username_)
def __str__(self):
# __str__ is used when the user does "print object", so we gracefully
# fall back to the __repr__ if the object has no default method.
try:
return str(self.__call__())
except pythoncom.com_error as details:
if details.hresult not in ERRORS_BAD_CONTEXT:
raise
return self.__repr__()
def __dir__(self):
lst = list(self.__dict__.keys()) + dir(self.__class__) + self._dir_ole_()
try:
lst += [p.Name for p in self.Properties_]
except AttributeError:
pass
return list(set(lst))
def _dir_ole_(self):
items_dict = {}
for iTI in range(0, self._oleobj_.GetTypeInfoCount()):
typeInfo = self._oleobj_.GetTypeInfo(iTI)
self._UpdateWithITypeInfo_(items_dict, typeInfo)
return list(items_dict.keys())
def _UpdateWithITypeInfo_(self, items_dict, typeInfo):
typeInfos = [typeInfo]
# suppress IDispatch and IUnknown methods
inspectedIIDs = {pythoncom.IID_IDispatch: None}
while len(typeInfos) > 0:
typeInfo = typeInfos.pop()
typeAttr = typeInfo.GetTypeAttr()
if typeAttr.iid not in inspectedIIDs:
inspectedIIDs[typeAttr.iid] = None
for iFun in range(0, typeAttr.cFuncs):
funDesc = typeInfo.GetFuncDesc(iFun)
funName = typeInfo.GetNames(funDesc.memid)[0]
if funName not in items_dict:
items_dict[funName] = None
# Inspect the type info of all implemented types
# E.g. IShellDispatch5 implements IShellDispatch4 which implements IShellDispatch3 ...
for iImplType in range(0, typeAttr.cImplTypes):
iRefType = typeInfo.GetRefTypeOfImplType(iImplType)
refTypeInfo = typeInfo.GetRefTypeInfo(iRefType)
typeInfos.append(refTypeInfo)
# Delegate comparison to the oleobjs, as they know how to do identity.
def __eq__(self, other):
other = getattr(other, "_oleobj_", other)
return self._oleobj_ == other
def __ne__(self, other):
other = getattr(other, "_oleobj_", other)
return self._oleobj_ != other
def __int__(self):
return int(self.__call__())
def __len__(self):
invkind, dispid = self._find_dispatch_type_("Count")
if invkind:
return self._oleobj_.Invoke(dispid, LCID, invkind, 1)
raise TypeError("This dispatch object does not define a Count method")
def _NewEnum(self):
try:
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
enum = self._oleobj_.InvokeTypes(
pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), ()
)
except pythoncom.com_error:
return None # no enumerator for this object.
from . import util
return util.WrapEnum(enum, None)
def __getitem__(self, index): # syver modified
# Improved __getitem__ courtesy Syver Enstad
# Must check _NewEnum before Item, to ensure b/w compat.
if isinstance(index, int):
if self.__dict__["_enum_"] is None:
self.__dict__["_enum_"] = self._NewEnum()
if self.__dict__["_enum_"] is not None:
return self._get_good_object_(self._enum_.__getitem__(index))
# See if we have an "Item" method/property we can use (goes hand in hand with Count() above!)
invkind, dispid = self._find_dispatch_type_("Item")
if invkind is not None:
return self._get_good_object_(
self._oleobj_.Invoke(dispid, LCID, invkind, 1, index)
)
raise TypeError("This object does not support enumeration")
def __setitem__(self, index, *args):
# XXX - todo - We should support calling Item() here too!
# print "__setitem__ with", index, args
if self._olerepr_.defaultDispatchName:
invkind, dispid = self._find_dispatch_type_(
self._olerepr_.defaultDispatchName
)
else:
invkind, dispid = (
pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF,
pythoncom.DISPID_VALUE,
)
if invkind is not None:
allArgs = (dispid, LCID, invkind, 0, index) + args
return self._get_good_object_(
self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None
)
raise TypeError("This dispatch object does not define a default method")
def _find_dispatch_type_(self, methodName):
if methodName in self._olerepr_.mapFuncs:
item = self._olerepr_.mapFuncs[methodName]
return item.desc[4], item.dispid
if methodName in self._olerepr_.propMapGet:
item = self._olerepr_.propMapGet[methodName]
return item.desc[4], item.dispid
try:
dispid = self._oleobj_.GetIDsOfNames(0, methodName)
except: ### what error?
return None, None
return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid
def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
result = self._oleobj_.InvokeTypes(
*(dispid, LCID, wFlags, retType, argTypes) + args
)
return self._get_good_object_(result, user, resultCLSID)
def _wrap_dispatch_(
self, ob, userName=None, returnCLSID=None, UnicodeToString=None
):
# Given a dispatch object, wrap it in a class
assert UnicodeToString is None, "this is deprecated and will go away"
return Dispatch(ob, userName)
def _get_good_single_object_(self, ob, userName=None, ReturnCLSID=None):
if isinstance(ob, PyIDispatchType):
# make a new instance of (probably this) class.
return self._wrap_dispatch_(ob, userName, ReturnCLSID)
if isinstance(ob, PyIUnknownType):
try:
ob = ob.QueryInterface(pythoncom.IID_IDispatch)
except pythoncom.com_error:
# It is an IUnknown, but not an IDispatch, so just let it through.
return ob
return self._wrap_dispatch_(ob, userName, ReturnCLSID)
return ob
def _get_good_object_(self, ob, userName=None, ReturnCLSID=None):
"""Given an object (usually the retval from a method), make it a good object to return.
Basically checks if it is a COM object, and wraps it up.
Also handles the fact that a retval may be a tuple of retvals"""
if ob is None: # Quick exit!
return None
elif isinstance(ob, tuple):
return tuple(
map(
lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(
o, oun, rc
),
ob,
)
)
else:
return self._get_good_single_object_(ob)
def _make_method_(self, name):
"Make a method object - Assumes in olerepr funcmap"
methodName = build.MakePublicAttributeName(name) # translate keywords etc.
methodCodeList = self._olerepr_.MakeFuncMethod(
self._olerepr_.mapFuncs[name], methodName, 0
)
methodCode = "\n".join(methodCodeList)
try:
# print "Method code for %s is:\n" % self._username_, methodCode
# self._print_details_()
codeObject = compile(methodCode, "<COMObject %s>" % self._username_, "exec")
# Exec the code object
tempNameSpace = {}
# "Dispatch" in the exec'd code is win32com.client.Dispatch, not ours.
globNameSpace = globals().copy()
globNameSpace["Dispatch"] = win32com.client.Dispatch
exec(
codeObject, globNameSpace, tempNameSpace
) # self.__dict__, self.__dict__
name = methodName
# Save the function in map.
fn = self._builtMethods_[name] = tempNameSpace[name]
newMeth = MakeMethod(fn, self, self.__class__)
return newMeth
except:
debug_print("Error building OLE definition for code ", methodCode)
traceback.print_exc()
return None
def _Release_(self):
"""Cleanup object - like a close - to force cleanup when you dont
want to rely on Python's reference counting."""
for childCont in self._mapCachedItems_.values():
childCont._Release_()
self._mapCachedItems_ = {}
if self._oleobj_:
self._oleobj_.Release()
self.__dict__["_oleobj_"] = None
if self._olerepr_:
self.__dict__["_olerepr_"] = None
self._enum_ = None
def _proc_(self, name, *args):
"""Call the named method as a procedure, rather than function.
Mainly used by Word.Basic, which whinges about such things."""
try:
item = self._olerepr_.mapFuncs[name]
dispId = item.dispid
return self._get_good_object_(
self._oleobj_.Invoke(*(dispId, LCID, item.desc[4], 0) + (args))
)
except KeyError:
raise AttributeError(name)
def _print_details_(self):
"Debug routine - dumps what it knows about an object."
print("AxDispatch container", self._username_)
try:
print("Methods:")
for method in self._olerepr_.mapFuncs.keys():
print("\t", method)
print("Props:")
for prop, entry in self._olerepr_.propMap.items():
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
print("Get Props:")
for prop, entry in self._olerepr_.propMapGet.items():
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
print("Put Props:")
for prop, entry in self._olerepr_.propMapPut.items():
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
except:
traceback.print_exc()
def __LazyMap__(self, attr):
try:
if self._LazyAddAttr_(attr):
debug_attr_print(
"%s.__LazyMap__(%s) added something" % (self._username_, attr)
)
return 1
except AttributeError:
return 0
# Using the typecomp, lazily create a new attribute definition.
def _LazyAddAttr_(self, attr):
if self._lazydata_ is None:
return 0
res = 0
typeinfo, typecomp = self._lazydata_
olerepr = self._olerepr_
# We need to explicitly check each invoke type individually - simply
# specifying '0' will bind to "any member", which may not be the one
# we are actually after (ie, we may be after prop_get, but returned
# the info for the prop_put.)
for i in ALL_INVOKE_TYPES:
try:
x, t = typecomp.Bind(attr, i)
# Support 'Get' and 'Set' properties - see
# bug 1587023
if x == 0 and attr[:3] in ("Set", "Get"):
x, t = typecomp.Bind(attr[3:], i)
if x == pythoncom.DESCKIND_FUNCDESC: # it's a FUNCDESC
r = olerepr._AddFunc_(typeinfo, t, 0)
elif x == pythoncom.DESCKIND_VARDESC: # it's a VARDESC
r = olerepr._AddVar_(typeinfo, t, 0)
else: # not found or TYPEDESC/IMPLICITAPP
r = None
if not r is None:
key, map = r[0], r[1]
item = map[key]
if map == olerepr.propMapPut:
olerepr._propMapPutCheck_(key, item)
elif map == olerepr.propMapGet:
olerepr._propMapGetCheck_(key, item)
res = 1
except:
pass
return res
def _FlagAsMethod(self, *methodNames):
"""Flag these attribute names as being methods.
Some objects do not correctly differentiate methods and
properties, leading to problems when calling these methods.
Specifically, trying to say: ob.SomeFunc()
may yield an exception "None object is not callable"
In this case, an attempt to fetch the *property* has worked
and returned None, rather than indicating it is really a method.
Calling: ob._FlagAsMethod("SomeFunc")
should then allow this to work.
"""
for name in methodNames:
details = build.MapEntry(self.__AttrToID__(name), (name,))
self._olerepr_.mapFuncs[name] = details
def __AttrToID__(self, attr):
debug_attr_print(
"Calling GetIDsOfNames for property %s in Dispatch container %s"
% (attr, self._username_)
)
return self._oleobj_.GetIDsOfNames(0, attr)
def __getattr__(self, attr):
if attr == "__iter__":
# We can't handle this as a normal method, as if the attribute
# exists, then it must return an iterable object.
try:
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
enum = self._oleobj_.InvokeTypes(
pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), ()
)
except pythoncom.com_error:
raise AttributeError("This object can not function as an iterator")
# We must return a callable object.
class Factory:
def __init__(self, ob):
self.ob = ob
def __call__(self):
import win32com.client.util
return win32com.client.util.Iterator(self.ob)
return Factory(enum)
if attr.startswith("_") and attr.endswith("_"): # Fast-track.
raise AttributeError(attr)
# If a known method, create new instance and return.
try:
return MakeMethod(self._builtMethods_[attr], self, self.__class__)
except KeyError:
pass
# XXX - Note that we current are case sensitive in the method.
# debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_))
# First check if it is in the method map. Note that an actual method
# must not yet exist, (otherwise we would not be here). This
# means we create the actual method object - which also means
# this code will never be asked for that method name again.
if attr in self._olerepr_.mapFuncs:
return self._make_method_(attr)
# Delegate to property maps/cached items
retEntry = None
if self._olerepr_ and self._oleobj_:
# first check general property map, then specific "put" map.
retEntry = self._olerepr_.propMap.get(attr)
if retEntry is None:
retEntry = self._olerepr_.propMapGet.get(attr)
# Not found so far - See what COM says.
if retEntry is None:
try:
if self.__LazyMap__(attr):
if attr in self._olerepr_.mapFuncs:
return self._make_method_(attr)
retEntry = self._olerepr_.propMap.get(attr)
if retEntry is None:
retEntry = self._olerepr_.propMapGet.get(attr)
if retEntry is None:
retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))
except pythoncom.ole_error:
pass # No prop by that name - retEntry remains None.
if retEntry is not None: # see if in my cache
try:
ret = self._mapCachedItems_[retEntry.dispid]
debug_attr_print("Cached items has attribute!", ret)
return ret
except (KeyError, AttributeError):
debug_attr_print("Attribute %s not in cache" % attr)
# If we are still here, and have a retEntry, get the OLE item
if retEntry is not None:
invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET)
debug_attr_print(
"Getting property Id 0x%x from OLE object" % retEntry.dispid
)
try:
ret = self._oleobj_.Invoke(retEntry.dispid, 0, invoke_type, 1)
except pythoncom.com_error as details:
if details.hresult in ERRORS_BAD_CONTEXT:
# May be a method.
self._olerepr_.mapFuncs[attr] = retEntry
return self._make_method_(attr)
raise
debug_attr_print("OLE returned ", ret)
return self._get_good_object_(ret)
# no where else to look.
raise AttributeError("%s.%s" % (self._username_, attr))
def __setattr__(self, attr, value):
if (
attr in self.__dict__
): # Fast-track - if already in our dict, just make the assignment.
# XXX - should maybe check method map - if someone assigns to a method,
# it could mean something special (not sure what, tho!)
self.__dict__[attr] = value
return
# Allow property assignment.
debug_attr_print(
"SetAttr called for %s.%s=%s on DispatchContainer"
% (self._username_, attr, repr(value))
)
if self._olerepr_:
# Check the "general" property map.
if attr in self._olerepr_.propMap:
entry = self._olerepr_.propMap[attr]
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
return
# Check the specific "put" map.
if attr in self._olerepr_.propMapPut:
entry = self._olerepr_.propMapPut[attr]
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
return
# Try the OLE Object
if self._oleobj_:
if self.__LazyMap__(attr):
# Check the "general" property map.
if attr in self._olerepr_.propMap:
entry = self._olerepr_.propMap[attr]
invoke_type = _GetDescInvokeType(
entry, pythoncom.INVOKE_PROPERTYPUT
)
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
return
# Check the specific "put" map.
if attr in self._olerepr_.propMapPut:
entry = self._olerepr_.propMapPut[attr]
invoke_type = _GetDescInvokeType(
entry, pythoncom.INVOKE_PROPERTYPUT
)
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
return
try:
entry = build.MapEntry(self.__AttrToID__(attr), (attr,))
except pythoncom.com_error:
# No attribute of that name
entry = None
if entry is not None:
try:
invoke_type = _GetDescInvokeType(
entry, pythoncom.INVOKE_PROPERTYPUT
)
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
self._olerepr_.propMap[attr] = entry
debug_attr_print(
"__setattr__ property %s (id=0x%x) in Dispatch container %s"
% (attr, entry.dispid, self._username_)
)
return
except pythoncom.com_error:
pass
raise AttributeError(
"Property '%s.%s' can not be set." % (self._username_, attr)
)

View file

@ -0,0 +1,799 @@
"""Manages the cache of generated Python code.
Description
This file manages the cache of generated Python code. When run from the
command line, it also provides a number of options for managing that cache.
Implementation
Each typelib is generated into a filename of format "{guid}x{lcid}x{major}x{minor}.py"
An external persistant dictionary maps from all known IIDs in all known type libraries
to the type library itself.
Thus, whenever Python code knows the IID of an object, it can find the IID, LCID and version of
the type library which supports it. Given this information, it can find the Python module
with the support.
If necessary, this support can be generated on the fly.
Hacks, to do, etc
Currently just uses a pickled dictionary, but should used some sort of indexed file.
Maybe an OLE2 compound file, or a bsddb file?
"""
import glob
import os
import sys
from importlib import reload
import pythoncom
import pywintypes
import win32com
import win32com.client
from . import CLSIDToClass
bForDemandDefault = 0 # Default value of bForDemand - toggle this to change the world - see also makepy.py
# The global dictionary
clsidToTypelib = {}
# If we have a different version of the typelib generated, this
# maps the "requested version" to the "generated version".
versionRedirectMap = {}
# There is no reason we *must* be readonly in a .zip, but we are now,
# Rather than check for ".zip" or other tricks, PEP302 defines
# a "__loader__" attribute, so we use that.
# (Later, it may become necessary to check if the __loader__ can update files,
# as a .zip loader potentially could - but punt all that until a need arises)
is_readonly = is_zip = hasattr(win32com, "__loader__") and hasattr(
win32com.__loader__, "archive"
)
# A dictionary of ITypeLibrary objects for demand generation explicitly handed to us
# Keyed by usual clsid, lcid, major, minor
demandGeneratedTypeLibraries = {}
import pickle as pickle
def __init__():
# Initialize the module. Called once explicitly at module import below.
try:
_LoadDicts()
except IOError:
Rebuild()
pickleVersion = 1
def _SaveDicts():
if is_readonly:
raise RuntimeError(
"Trying to write to a readonly gencache ('%s')!" % win32com.__gen_path__
)
f = open(os.path.join(GetGeneratePath(), "dicts.dat"), "wb")
try:
p = pickle.Pickler(f)
p.dump(pickleVersion)
p.dump(clsidToTypelib)
finally:
f.close()
def _LoadDicts():
# Load the dictionary from a .zip file if that is where we live.
if is_zip:
import io as io
loader = win32com.__loader__
arc_path = loader.archive
dicts_path = os.path.join(win32com.__gen_path__, "dicts.dat")
if dicts_path.startswith(arc_path):
dicts_path = dicts_path[len(arc_path) + 1 :]
else:
# Hm. See below.
return
try:
data = loader.get_data(dicts_path)
except AttributeError:
# The __loader__ has no get_data method. See below.
return
except IOError:
# Our gencache is in a .zip file (and almost certainly readonly)
# but no dicts file. That actually needn't be fatal for a frozen
# application. Assuming they call "EnsureModule" with the same
# typelib IDs they have been frozen with, that EnsureModule will
# correctly re-build the dicts on the fly. However, objects that
# rely on the gencache but have not done an EnsureModule will
# fail (but their apps are likely to fail running from source
# with a clean gencache anyway, as then they would be getting
# Dynamic objects until the cache is built - so the best answer
# for these apps is to call EnsureModule, rather than freezing
# the dict)
return
f = io.BytesIO(data)
else:
# NOTE: IOError on file open must be caught by caller.
f = open(os.path.join(win32com.__gen_path__, "dicts.dat"), "rb")
try:
p = pickle.Unpickler(f)
version = p.load()
global clsidToTypelib
clsidToTypelib = p.load()
versionRedirectMap.clear()
finally:
f.close()
def GetGeneratedFileName(clsid, lcid, major, minor):
"""Given the clsid, lcid, major and minor for a type lib, return
the file name (no extension) providing this support.
"""
return str(clsid).upper()[1:-1] + "x%sx%sx%s" % (lcid, major, minor)
def SplitGeneratedFileName(fname):
"""Reverse of GetGeneratedFileName()"""
return tuple(fname.split("x", 4))
def GetGeneratePath():
"""Returns the name of the path to generate to.
Checks the directory is OK.
"""
assert not is_readonly, "Why do you want the genpath for a readonly store?"
try:
os.makedirs(win32com.__gen_path__)
# os.mkdir(win32com.__gen_path__)
except os.error:
pass
try:
fname = os.path.join(win32com.__gen_path__, "__init__.py")
os.stat(fname)
except os.error:
f = open(fname, "w")
f.write(
"# Generated file - this directory may be deleted to reset the COM cache...\n"
)
f.write("import win32com\n")
f.write(
"if __path__[:-1] != win32com.__gen_path__: __path__.append(win32com.__gen_path__)\n"
)
f.close()
return win32com.__gen_path__
#
# The helpers for win32com.client.Dispatch and OCX clients.
#
def GetClassForProgID(progid):
"""Get a Python class for a Program ID
Given a Program ID, return a Python class which wraps the COM object
Returns the Python class, or None if no module is available.
Params
progid -- A COM ProgramID or IID (eg, "Word.Application")
"""
clsid = pywintypes.IID(progid) # This auto-converts named to IDs.
return GetClassForCLSID(clsid)
def GetClassForCLSID(clsid):
"""Get a Python class for a CLSID
Given a CLSID, return a Python class which wraps the COM object
Returns the Python class, or None if no module is available.
Params
clsid -- A COM CLSID (or string repr of one)
"""
# first, take a short-cut - we may already have generated support ready-to-roll.
clsid = str(clsid)
if CLSIDToClass.HasClass(clsid):
return CLSIDToClass.GetClass(clsid)
mod = GetModuleForCLSID(clsid)
if mod is None:
return None
try:
return CLSIDToClass.GetClass(clsid)
except KeyError:
return None
def GetModuleForProgID(progid):
"""Get a Python module for a Program ID
Given a Program ID, return a Python module which contains the
class which wraps the COM object.
Returns the Python module, or None if no module is available.
Params
progid -- A COM ProgramID or IID (eg, "Word.Application")
"""
try:
iid = pywintypes.IID(progid)
except pywintypes.com_error:
return None
return GetModuleForCLSID(iid)
def GetModuleForCLSID(clsid):
"""Get a Python module for a CLSID
Given a CLSID, return a Python module which contains the
class which wraps the COM object.
Returns the Python module, or None if no module is available.
Params
progid -- A COM CLSID (ie, not the description)
"""
clsid_str = str(clsid)
try:
typelibCLSID, lcid, major, minor = clsidToTypelib[clsid_str]
except KeyError:
return None
try:
mod = GetModuleForTypelib(typelibCLSID, lcid, major, minor)
except ImportError:
mod = None
if mod is not None:
sub_mod = mod.CLSIDToPackageMap.get(clsid_str)
if sub_mod is None:
sub_mod = mod.VTablesToPackageMap.get(clsid_str)
if sub_mod is not None:
sub_mod_name = mod.__name__ + "." + sub_mod
try:
__import__(sub_mod_name)
except ImportError:
info = typelibCLSID, lcid, major, minor
# Force the generation. If this typelibrary has explicitly been added,
# use it (it may not be registered, causing a lookup by clsid to fail)
if info in demandGeneratedTypeLibraries:
info = demandGeneratedTypeLibraries[info]
from . import makepy
makepy.GenerateChildFromTypeLibSpec(sub_mod, info)
# Generate does an import...
mod = sys.modules[sub_mod_name]
return mod
def GetModuleForTypelib(typelibCLSID, lcid, major, minor):
"""Get a Python module for a type library ID
Given the CLSID of a typelibrary, return an imported Python module,
else None
Params
typelibCLSID -- IID of the type library.
major -- Integer major version.
minor -- Integer minor version
lcid -- Integer LCID for the library.
"""
modName = GetGeneratedFileName(typelibCLSID, lcid, major, minor)
mod = _GetModule(modName)
# If the import worked, it doesn't mean we have actually added this
# module to our cache though - check that here.
if "_in_gencache_" not in mod.__dict__:
AddModuleToCache(typelibCLSID, lcid, major, minor)
assert "_in_gencache_" in mod.__dict__
return mod
def MakeModuleForTypelib(
typelibCLSID,
lcid,
major,
minor,
progressInstance=None,
bForDemand=bForDemandDefault,
bBuildHidden=1,
):
"""Generate support for a type library.
Given the IID, LCID and version information for a type library, generate
and import the necessary support files.
Returns the Python module. No exceptions are caught.
Params
typelibCLSID -- IID of the type library.
major -- Integer major version.
minor -- Integer minor version.
lcid -- Integer LCID for the library.
progressInstance -- Instance to use as progress indicator, or None to
use the GUI progress bar.
"""
from . import makepy
makepy.GenerateFromTypeLibSpec(
(typelibCLSID, lcid, major, minor),
progressInstance=progressInstance,
bForDemand=bForDemand,
bBuildHidden=bBuildHidden,
)
return GetModuleForTypelib(typelibCLSID, lcid, major, minor)
def MakeModuleForTypelibInterface(
typelib_ob, progressInstance=None, bForDemand=bForDemandDefault, bBuildHidden=1
):
"""Generate support for a type library.
Given a PyITypeLib interface generate and import the necessary support files. This is useful
for getting makepy support for a typelibrary that is not registered - the caller can locate
and load the type library itself, rather than relying on COM to find it.
Returns the Python module.
Params
typelib_ob -- The type library itself
progressInstance -- Instance to use as progress indicator, or None to
use the GUI progress bar.
"""
from . import makepy
try:
makepy.GenerateFromTypeLibSpec(
typelib_ob,
progressInstance=progressInstance,
bForDemand=bForDemandDefault,
bBuildHidden=bBuildHidden,
)
except pywintypes.com_error:
return None
tla = typelib_ob.GetLibAttr()
guid = tla[0]
lcid = tla[1]
major = tla[3]
minor = tla[4]
return GetModuleForTypelib(guid, lcid, major, minor)
def EnsureModuleForTypelibInterface(
typelib_ob, progressInstance=None, bForDemand=bForDemandDefault, bBuildHidden=1
):
"""Check we have support for a type library, generating if not.
Given a PyITypeLib interface generate and import the necessary
support files if necessary. This is useful for getting makepy support
for a typelibrary that is not registered - the caller can locate and
load the type library itself, rather than relying on COM to find it.
Returns the Python module.
Params
typelib_ob -- The type library itself
progressInstance -- Instance to use as progress indicator, or None to
use the GUI progress bar.
"""
tla = typelib_ob.GetLibAttr()
guid = tla[0]
lcid = tla[1]
major = tla[3]
minor = tla[4]
# If demand generated, save the typelib interface away for later use
if bForDemand:
demandGeneratedTypeLibraries[(str(guid), lcid, major, minor)] = typelib_ob
try:
return GetModuleForTypelib(guid, lcid, major, minor)
except ImportError:
pass
# Generate it.
return MakeModuleForTypelibInterface(
typelib_ob, progressInstance, bForDemand, bBuildHidden
)
def ForgetAboutTypelibInterface(typelib_ob):
"""Drop any references to a typelib previously added with EnsureModuleForTypelibInterface and forDemand"""
tla = typelib_ob.GetLibAttr()
guid = tla[0]
lcid = tla[1]
major = tla[3]
minor = tla[4]
info = str(guid), lcid, major, minor
try:
del demandGeneratedTypeLibraries[info]
except KeyError:
# Not worth raising an exception - maybe they dont know we only remember for demand generated, etc.
print(
"ForgetAboutTypelibInterface:: Warning - type library with info %s is not being remembered!"
% (info,)
)
# and drop any version redirects to it
for key, val in list(versionRedirectMap.items()):
if val == info:
del versionRedirectMap[key]
def EnsureModule(
typelibCLSID,
lcid,
major,
minor,
progressInstance=None,
bValidateFile=not is_readonly,
bForDemand=bForDemandDefault,
bBuildHidden=1,
):
"""Ensure Python support is loaded for a type library, generating if necessary.
Given the IID, LCID and version information for a type library, check and if
necessary (re)generate, then import the necessary support files. If we regenerate the file, there
is no way to totally snuff out all instances of the old module in Python, and thus we will regenerate the file more than necessary,
unless makepy/genpy is modified accordingly.
Returns the Python module. No exceptions are caught during the generate process.
Params
typelibCLSID -- IID of the type library.
major -- Integer major version.
minor -- Integer minor version
lcid -- Integer LCID for the library.
progressInstance -- Instance to use as progress indicator, or None to
use the GUI progress bar.
bValidateFile -- Whether or not to perform cache validation or not
bForDemand -- Should a complete generation happen now, or on demand?
bBuildHidden -- Should hidden members/attributes etc be generated?
"""
bReloadNeeded = 0
try:
try:
module = GetModuleForTypelib(typelibCLSID, lcid, major, minor)
except ImportError:
# If we get an ImportError
# We may still find a valid cache file under a different MinorVersion #
# (which windows will search out for us)
# print "Loading reg typelib", typelibCLSID, major, minor, lcid
module = None
try:
tlbAttr = pythoncom.LoadRegTypeLib(
typelibCLSID, major, minor, lcid
).GetLibAttr()
# if the above line doesn't throw a pythoncom.com_error, check if
# it is actually a different lib than we requested, and if so, suck it in
if tlbAttr[1] != lcid or tlbAttr[4] != minor:
# print "Trying 2nd minor #", tlbAttr[1], tlbAttr[3], tlbAttr[4]
try:
module = GetModuleForTypelib(
typelibCLSID, tlbAttr[1], tlbAttr[3], tlbAttr[4]
)
except ImportError:
# We don't have a module, but we do have a better minor
# version - remember that.
minor = tlbAttr[4]
# else module remains None
except pythoncom.com_error:
# couldn't load any typelib - mod remains None
pass
if module is not None and bValidateFile:
assert not is_readonly, "Can't validate in a read-only gencache"
try:
typLibPath = pythoncom.QueryPathOfRegTypeLib(
typelibCLSID, major, minor, lcid
)
# windows seems to add an extra \0 (via the underlying BSTR)
# The mainwin toolkit does not add this erroneous \0
if typLibPath[-1] == "\0":
typLibPath = typLibPath[:-1]
suf = getattr(os.path, "supports_unicode_filenames", 0)
if not suf:
# can't pass unicode filenames directly - convert
try:
typLibPath = typLibPath.encode(sys.getfilesystemencoding())
except AttributeError: # no sys.getfilesystemencoding
typLibPath = str(typLibPath)
tlbAttributes = pythoncom.LoadRegTypeLib(
typelibCLSID, major, minor, lcid
).GetLibAttr()
except pythoncom.com_error:
# We have a module, but no type lib - we should still
# run with what we have though - the typelib may not be
# deployed here.
bValidateFile = 0
if module is not None and bValidateFile:
assert not is_readonly, "Can't validate in a read-only gencache"
filePathPrefix = "%s\\%s" % (
GetGeneratePath(),
GetGeneratedFileName(typelibCLSID, lcid, major, minor),
)
filePath = filePathPrefix + ".py"
filePathPyc = filePathPrefix + ".py"
if __debug__:
filePathPyc = filePathPyc + "c"
else:
filePathPyc = filePathPyc + "o"
# Verify that type library is up to date.
# If we have a differing MinorVersion or genpy has bumped versions, update the file
from . import genpy
if (
module.MinorVersion != tlbAttributes[4]
or genpy.makepy_version != module.makepy_version
):
# print "Version skew: %d, %d" % (module.MinorVersion, tlbAttributes[4])
# try to erase the bad file from the cache
try:
os.unlink(filePath)
except os.error:
pass
try:
os.unlink(filePathPyc)
except os.error:
pass
if os.path.isdir(filePathPrefix):
import shutil
shutil.rmtree(filePathPrefix)
minor = tlbAttributes[4]
module = None
bReloadNeeded = 1
else:
minor = module.MinorVersion
filePathPrefix = "%s\\%s" % (
GetGeneratePath(),
GetGeneratedFileName(typelibCLSID, lcid, major, minor),
)
filePath = filePathPrefix + ".py"
filePathPyc = filePathPrefix + ".pyc"
# print "Trying py stat: ", filePath
fModTimeSet = 0
try:
pyModTime = os.stat(filePath)[8]
fModTimeSet = 1
except os.error as e:
# If .py file fails, try .pyc file
# print "Trying pyc stat", filePathPyc
try:
pyModTime = os.stat(filePathPyc)[8]
fModTimeSet = 1
except os.error as e:
pass
# print "Trying stat typelib", pyModTime
# print str(typLibPath)
typLibModTime = os.stat(typLibPath)[8]
if fModTimeSet and (typLibModTime > pyModTime):
bReloadNeeded = 1
module = None
except (ImportError, os.error):
module = None
if module is None:
# We need to build an item. If we are in a read-only cache, we
# can't/don't want to do this - so before giving up, check for
# a different minor version in our cache - according to COM, this is OK
if is_readonly:
key = str(typelibCLSID), lcid, major, minor
# If we have been asked before, get last result.
try:
return versionRedirectMap[key]
except KeyError:
pass
# Find other candidates.
items = []
for desc in GetGeneratedInfos():
if key[0] == desc[0] and key[1] == desc[1] and key[2] == desc[2]:
items.append(desc)
if items:
# Items are all identical, except for last tuple element
# We want the latest minor version we have - so just sort and grab last
items.sort()
new_minor = items[-1][3]
ret = GetModuleForTypelib(typelibCLSID, lcid, major, new_minor)
else:
ret = None
# remember and return
versionRedirectMap[key] = ret
return ret
# print "Rebuilding: ", major, minor
module = MakeModuleForTypelib(
typelibCLSID,
lcid,
major,
minor,
progressInstance,
bForDemand=bForDemand,
bBuildHidden=bBuildHidden,
)
# If we replaced something, reload it
if bReloadNeeded:
module = reload(module)
AddModuleToCache(typelibCLSID, lcid, major, minor)
return module
def EnsureDispatch(
prog_id, bForDemand=1
): # New fn, so we default the new demand feature to on!
"""Given a COM prog_id, return an object that is using makepy support, building if necessary"""
disp = win32com.client.Dispatch(prog_id)
if not disp.__dict__.get("CLSID"): # Eeek - no makepy support - try and build it.
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
mod = EnsureModule(tla[0], tla[1], tla[3], tla[4], bForDemand=bForDemand)
GetModuleForCLSID(disp_clsid)
# Get the class from the module.
from . import CLSIDToClass
disp_class = CLSIDToClass.GetClass(str(disp_clsid))
disp = disp_class(disp._oleobj_)
except pythoncom.com_error:
raise TypeError(
"This COM object can not automate the makepy process - please run makepy manually for this object"
)
return disp
def AddModuleToCache(
typelibclsid, lcid, major, minor, verbose=1, bFlushNow=not is_readonly
):
"""Add a newly generated file to the cache dictionary."""
fname = GetGeneratedFileName(typelibclsid, lcid, major, minor)
mod = _GetModule(fname)
# if mod._in_gencache_ is already true, then we are reloading this
# module - this doesn't mean anything special though!
mod._in_gencache_ = 1
info = str(typelibclsid), lcid, major, minor
dict_modified = False
def SetTypelibForAllClsids(dict):
nonlocal dict_modified
for clsid, cls in dict.items():
if clsidToTypelib.get(clsid) != info:
clsidToTypelib[clsid] = info
dict_modified = True
SetTypelibForAllClsids(mod.CLSIDToClassMap)
SetTypelibForAllClsids(mod.CLSIDToPackageMap)
SetTypelibForAllClsids(mod.VTablesToClassMap)
SetTypelibForAllClsids(mod.VTablesToPackageMap)
# If this lib was previously redirected, drop it
if info in versionRedirectMap:
del versionRedirectMap[info]
if bFlushNow and dict_modified:
_SaveDicts()
def GetGeneratedInfos():
zip_pos = win32com.__gen_path__.find(".zip\\")
if zip_pos >= 0:
import zipfile
zip_file = win32com.__gen_path__[: zip_pos + 4]
zip_path = win32com.__gen_path__[zip_pos + 5 :].replace("\\", "/")
zf = zipfile.ZipFile(zip_file)
infos = {}
for n in zf.namelist():
if not n.startswith(zip_path):
continue
base = n[len(zip_path) + 1 :].split("/")[0]
try:
iid, lcid, major, minor = base.split("x")
lcid = int(lcid)
major = int(major)
minor = int(minor)
iid = pywintypes.IID("{" + iid + "}")
except ValueError:
continue
except pywintypes.com_error:
# invalid IID
continue
infos[(iid, lcid, major, minor)] = 1
zf.close()
return list(infos.keys())
else:
# on the file system
files = glob.glob(win32com.__gen_path__ + "\\*")
ret = []
for file in files:
if not os.path.isdir(file) and not os.path.splitext(file)[1] == ".py":
continue
name = os.path.splitext(os.path.split(file)[1])[0]
try:
iid, lcid, major, minor = name.split("x")
iid = pywintypes.IID("{" + iid + "}")
lcid = int(lcid)
major = int(major)
minor = int(minor)
except ValueError:
continue
except pywintypes.com_error:
# invalid IID
continue
ret.append((iid, lcid, major, minor))
return ret
def _GetModule(fname):
"""Given the name of a module in the gen_py directory, import and return it."""
mod_name = "win32com.gen_py.%s" % fname
mod = __import__(mod_name)
return sys.modules[mod_name]
def Rebuild(verbose=1):
"""Rebuild the cache indexes from the file system."""
clsidToTypelib.clear()
infos = GetGeneratedInfos()
if verbose and len(infos): # Dont bother reporting this when directory is empty!
print("Rebuilding cache of generated files for COM support...")
for info in infos:
iid, lcid, major, minor = info
if verbose:
print("Checking", GetGeneratedFileName(*info))
try:
AddModuleToCache(iid, lcid, major, minor, verbose, 0)
except:
print(
"Could not add module %s - %s: %s"
% (info, sys.exc_info()[0], sys.exc_info()[1])
)
if verbose and len(infos): # Dont bother reporting this when directory is empty!
print("Done.")
_SaveDicts()
def _Dump():
print("Cache is in directory", win32com.__gen_path__)
# Build a unique dir
d = {}
for clsid, (typelibCLSID, lcid, major, minor) in clsidToTypelib.items():
d[typelibCLSID, lcid, major, minor] = None
for typelibCLSID, lcid, major, minor in d.keys():
mod = GetModuleForTypelib(typelibCLSID, lcid, major, minor)
print("%s - %s" % (mod.__doc__, typelibCLSID))
# Boot up
__init__()
def usage():
usageString = """\
Usage: gencache [-q] [-d] [-r]
-q - Quiet
-d - Dump the cache (typelibrary description and filename).
-r - Rebuild the cache dictionary from the existing .py files
"""
print(usageString)
sys.exit(1)
if __name__ == "__main__":
import getopt
try:
opts, args = getopt.getopt(sys.argv[1:], "qrd")
except getopt.error as message:
print(message)
usage()
# we only have options - complain about real args, or none at all!
if len(sys.argv) == 1 or args:
print(usage())
verbose = 1
for opt, val in opts:
if opt == "-d": # Dump
_Dump()
if opt == "-r":
Rebuild(verbose)
if opt == "-q":
verbose = 0

1411
lib/win32com/client/genpy.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,459 @@
# Originally written by Curt Hagenlocher, and various bits
# and pieces by Mark Hammond (and now Greg Stein has had
# a go too :-)
# Note that the main worker code has been moved to genpy.py
# As this is normally run from the command line, it reparses the code each time.
# Now this is nothing more than the command line handler and public interface.
# XXX - TO DO
# XXX - Greg and Mark have some ideas for a revamp - just no
# time - if you want to help, contact us for details.
# Main idea is to drop the classes exported and move to a more
# traditional data driven model.
"""Generate a .py file from an OLE TypeLibrary file.
This module is concerned only with the actual writing of
a .py file. It draws on the @build@ module, which builds
the knowledge of a COM interface.
"""
usageHelp = """ \
Usage:
makepy.py [-i] [-v|q] [-h] [-u] [-o output_file] [-d] [typelib, ...]
-i -- Show information for the specified typelib.
-v -- Verbose output.
-q -- Quiet output.
-h -- Do not generate hidden methods.
-u -- Python 1.5 and earlier: Do NOT convert all Unicode objects to
strings.
Python 1.6 and later: Convert all Unicode objects to strings.
-o -- Create output in a specified output file. If the path leading
to the file does not exist, any missing directories will be
created.
NOTE: -o cannot be used with -d. This will generate an error.
-d -- Generate the base code now and the class code on demand.
Recommended for large type libraries.
typelib -- A TLB, DLL, OCX or anything containing COM type information.
If a typelib is not specified, a window containing a textbox
will open from which you can select a registered type
library.
Examples:
makepy.py -d
Presents a list of registered type libraries from which you can make
a selection.
makepy.py -d "Microsoft Excel 8.0 Object Library"
Generate support for the type library with the specified description
(in this case, the MS Excel object model).
"""
import importlib
import os
import sys
import pythoncom
from win32com.client import Dispatch, gencache, genpy, selecttlb
bForDemandDefault = 0 # Default value of bForDemand - toggle this to change the world - see also gencache.py
error = "makepy.error"
def usage():
sys.stderr.write(usageHelp)
sys.exit(2)
def ShowInfo(spec):
if not spec:
tlbSpec = selecttlb.SelectTlb(excludeFlags=selecttlb.FLAG_HIDDEN)
if tlbSpec is None:
return
try:
tlb = pythoncom.LoadRegTypeLib(
tlbSpec.clsid, tlbSpec.major, tlbSpec.minor, tlbSpec.lcid
)
except pythoncom.com_error: # May be badly registered.
sys.stderr.write(
"Warning - could not load registered typelib '%s'\n" % (tlbSpec.clsid)
)
tlb = None
infos = [(tlb, tlbSpec)]
else:
infos = GetTypeLibsForSpec(spec)
for tlb, tlbSpec in infos:
desc = tlbSpec.desc
if desc is None:
if tlb is None:
desc = "<Could not load typelib %s>" % (tlbSpec.dll)
else:
desc = tlb.GetDocumentation(-1)[0]
print(desc)
print(
" %s, lcid=%s, major=%s, minor=%s"
% (tlbSpec.clsid, tlbSpec.lcid, tlbSpec.major, tlbSpec.minor)
)
print(" >>> # Use these commands in Python code to auto generate .py support")
print(" >>> from win32com.client import gencache")
print(
" >>> gencache.EnsureModule('%s', %s, %s, %s)"
% (tlbSpec.clsid, tlbSpec.lcid, tlbSpec.major, tlbSpec.minor)
)
class SimpleProgress(genpy.GeneratorProgress):
"""A simple progress class prints its output to stderr"""
def __init__(self, verboseLevel):
self.verboseLevel = verboseLevel
def Close(self):
pass
def Finished(self):
if self.verboseLevel > 1:
sys.stderr.write("Generation complete..\n")
def SetDescription(self, desc, maxticks=None):
if self.verboseLevel:
sys.stderr.write(desc + "\n")
def Tick(self, desc=None):
pass
def VerboseProgress(self, desc, verboseLevel=2):
if self.verboseLevel >= verboseLevel:
sys.stderr.write(desc + "\n")
def LogBeginGenerate(self, filename):
self.VerboseProgress("Generating to %s" % filename, 1)
def LogWarning(self, desc):
self.VerboseProgress("WARNING: " + desc, 1)
class GUIProgress(SimpleProgress):
def __init__(self, verboseLevel):
# Import some modules we need to we can trap failure now.
import pywin # nopycln: import
import win32ui
SimpleProgress.__init__(self, verboseLevel)
self.dialog = None
def Close(self):
if self.dialog is not None:
self.dialog.Close()
self.dialog = None
def Starting(self, tlb_desc):
SimpleProgress.Starting(self, tlb_desc)
if self.dialog is None:
from pywin.dialogs import status
self.dialog = status.ThreadedStatusProgressDialog(tlb_desc)
else:
self.dialog.SetTitle(tlb_desc)
def SetDescription(self, desc, maxticks=None):
self.dialog.SetText(desc)
if maxticks:
self.dialog.SetMaxTicks(maxticks)
def Tick(self, desc=None):
self.dialog.Tick()
if desc is not None:
self.dialog.SetText(desc)
def GetTypeLibsForSpec(arg):
"""Given an argument on the command line (either a file name, library
description, or ProgID of an object) return a list of actual typelibs
to use."""
typelibs = []
try:
try:
tlb = pythoncom.LoadTypeLib(arg)
spec = selecttlb.TypelibSpec(None, 0, 0, 0)
spec.FromTypelib(tlb, arg)
typelibs.append((tlb, spec))
except pythoncom.com_error:
# See if it is a description
tlbs = selecttlb.FindTlbsWithDescription(arg)
if len(tlbs) == 0:
# Maybe it is the name of a COM object?
try:
ob = Dispatch(arg)
# and if so, it must support typelib info
tlb, index = ob._oleobj_.GetTypeInfo().GetContainingTypeLib()
spec = selecttlb.TypelibSpec(None, 0, 0, 0)
spec.FromTypelib(tlb)
tlbs.append(spec)
except pythoncom.com_error:
pass
if len(tlbs) == 0:
print("Could not locate a type library matching '%s'" % (arg))
for spec in tlbs:
# Version numbers not always reliable if enumerated from registry.
# (as some libs use hex, other's dont. Both examples from MS, of course.)
if spec.dll is None:
tlb = pythoncom.LoadRegTypeLib(
spec.clsid, spec.major, spec.minor, spec.lcid
)
else:
tlb = pythoncom.LoadTypeLib(spec.dll)
# We have a typelib, but it may not be exactly what we specified
# (due to automatic version matching of COM). So we query what we really have!
attr = tlb.GetLibAttr()
spec.major = attr[3]
spec.minor = attr[4]
spec.lcid = attr[1]
typelibs.append((tlb, spec))
return typelibs
except pythoncom.com_error:
t, v, tb = sys.exc_info()
sys.stderr.write("Unable to load type library from '%s' - %s\n" % (arg, v))
tb = None # Storing tb in a local is a cycle!
sys.exit(1)
def GenerateFromTypeLibSpec(
typelibInfo,
file=None,
verboseLevel=None,
progressInstance=None,
bUnicodeToString=None,
bForDemand=bForDemandDefault,
bBuildHidden=1,
):
assert bUnicodeToString is None, "this is deprecated and will go away"
if verboseLevel is None:
verboseLevel = 0 # By default, we use no gui and no verbose level!
if bForDemand and file is not None:
raise RuntimeError(
"You can only perform a demand-build when the output goes to the gen_py directory"
)
if isinstance(typelibInfo, tuple):
# Tuple
typelibCLSID, lcid, major, minor = typelibInfo
tlb = pythoncom.LoadRegTypeLib(typelibCLSID, major, minor, lcid)
spec = selecttlb.TypelibSpec(typelibCLSID, lcid, major, minor)
spec.FromTypelib(tlb, str(typelibCLSID))
typelibs = [(tlb, spec)]
elif isinstance(typelibInfo, selecttlb.TypelibSpec):
if typelibInfo.dll is None:
# Version numbers not always reliable if enumerated from registry.
tlb = pythoncom.LoadRegTypeLib(
typelibInfo.clsid,
typelibInfo.major,
typelibInfo.minor,
typelibInfo.lcid,
)
else:
tlb = pythoncom.LoadTypeLib(typelibInfo.dll)
typelibs = [(tlb, typelibInfo)]
elif hasattr(typelibInfo, "GetLibAttr"):
# A real typelib object!
# Could also use isinstance(typelibInfo, PyITypeLib) instead, but PyITypeLib is not directly exposed by pythoncom.
# pythoncom.TypeIIDs[pythoncom.IID_ITypeLib] seems to work
tla = typelibInfo.GetLibAttr()
guid = tla[0]
lcid = tla[1]
major = tla[3]
minor = tla[4]
spec = selecttlb.TypelibSpec(guid, lcid, major, minor)
typelibs = [(typelibInfo, spec)]
else:
typelibs = GetTypeLibsForSpec(typelibInfo)
if progressInstance is None:
progressInstance = SimpleProgress(verboseLevel)
progress = progressInstance
bToGenDir = file is None
for typelib, info in typelibs:
gen = genpy.Generator(typelib, info.dll, progress, bBuildHidden=bBuildHidden)
if file is None:
this_name = gencache.GetGeneratedFileName(
info.clsid, info.lcid, info.major, info.minor
)
full_name = os.path.join(gencache.GetGeneratePath(), this_name)
if bForDemand:
try:
os.unlink(full_name + ".py")
except os.error:
pass
try:
os.unlink(full_name + ".pyc")
except os.error:
pass
try:
os.unlink(full_name + ".pyo")
except os.error:
pass
if not os.path.isdir(full_name):
os.mkdir(full_name)
outputName = os.path.join(full_name, "__init__.py")
else:
outputName = full_name + ".py"
fileUse = gen.open_writer(outputName)
progress.LogBeginGenerate(outputName)
else:
fileUse = file
worked = False
try:
gen.generate(fileUse, bForDemand)
worked = True
finally:
if file is None:
gen.finish_writer(outputName, fileUse, worked)
importlib.invalidate_caches()
if bToGenDir:
progress.SetDescription("Importing module")
gencache.AddModuleToCache(info.clsid, info.lcid, info.major, info.minor)
progress.Close()
def GenerateChildFromTypeLibSpec(
child, typelibInfo, verboseLevel=None, progressInstance=None, bUnicodeToString=None
):
assert bUnicodeToString is None, "this is deprecated and will go away"
if verboseLevel is None:
verboseLevel = (
0 # By default, we use no gui, and no verbose level for the children.
)
if type(typelibInfo) == type(()):
typelibCLSID, lcid, major, minor = typelibInfo
tlb = pythoncom.LoadRegTypeLib(typelibCLSID, major, minor, lcid)
else:
tlb = typelibInfo
tla = typelibInfo.GetLibAttr()
typelibCLSID = tla[0]
lcid = tla[1]
major = tla[3]
minor = tla[4]
spec = selecttlb.TypelibSpec(typelibCLSID, lcid, major, minor)
spec.FromTypelib(tlb, str(typelibCLSID))
typelibs = [(tlb, spec)]
if progressInstance is None:
progressInstance = SimpleProgress(verboseLevel)
progress = progressInstance
for typelib, info in typelibs:
dir_name = gencache.GetGeneratedFileName(
info.clsid, info.lcid, info.major, info.minor
)
dir_path_name = os.path.join(gencache.GetGeneratePath(), dir_name)
progress.LogBeginGenerate(dir_path_name)
gen = genpy.Generator(typelib, info.dll, progress)
gen.generate_child(child, dir_path_name)
progress.SetDescription("Importing module")
importlib.invalidate_caches()
__import__("win32com.gen_py." + dir_name + "." + child)
progress.Close()
def main():
import getopt
hiddenSpec = 1
outputName = None
verboseLevel = 1
doit = 1
bForDemand = bForDemandDefault
try:
opts, args = getopt.getopt(sys.argv[1:], "vo:huiqd")
for o, v in opts:
if o == "-h":
hiddenSpec = 0
elif o == "-o":
outputName = v
elif o == "-v":
verboseLevel = verboseLevel + 1
elif o == "-q":
verboseLevel = verboseLevel - 1
elif o == "-i":
if len(args) == 0:
ShowInfo(None)
else:
for arg in args:
ShowInfo(arg)
doit = 0
elif o == "-d":
bForDemand = not bForDemand
except (getopt.error, error) as msg:
sys.stderr.write(str(msg) + "\n")
usage()
if bForDemand and outputName is not None:
sys.stderr.write("Can not use -d and -o together\n")
usage()
if not doit:
return 0
if len(args) == 0:
rc = selecttlb.SelectTlb()
if rc is None:
sys.exit(1)
args = [rc]
if outputName is not None:
path = os.path.dirname(outputName)
if path != "" and not os.path.exists(path):
os.makedirs(path)
if sys.version_info > (3, 0):
f = open(outputName, "wt", encoding="mbcs")
else:
import codecs # not available in py3k.
f = codecs.open(outputName, "w", "mbcs")
else:
f = None
for arg in args:
GenerateFromTypeLibSpec(
arg,
f,
verboseLevel=verboseLevel,
bForDemand=bForDemand,
bBuildHidden=hiddenSpec,
)
if f:
f.close()
if __name__ == "__main__":
rc = main()
if rc:
sys.exit(rc)
sys.exit(0)

View file

@ -0,0 +1,183 @@
"""Utilities for selecting and enumerating the Type Libraries installed on the system
"""
import pythoncom
import win32api
import win32con
class TypelibSpec:
def __init__(self, clsid, lcid, major, minor, flags=0):
self.clsid = str(clsid)
self.lcid = int(lcid)
# We avoid assuming 'major' or 'minor' are integers - when
# read from the registry there is some confusion about if
# they are base 10 or base 16 (they *should* be base 16, but
# how they are written is beyond our control.)
self.major = major
self.minor = minor
self.dll = None
self.desc = None
self.ver_desc = None
self.flags = flags
# For the SelectList
def __getitem__(self, item):
if item == 0:
return self.ver_desc
raise IndexError("Cant index me!")
def __lt__(self, other): # rich-cmp/py3k-friendly version
me = (
(self.ver_desc or "").lower(),
(self.desc or "").lower(),
self.major,
self.minor,
)
them = (
(other.ver_desc or "").lower(),
(other.desc or "").lower(),
other.major,
other.minor,
)
return me < them
def __eq__(self, other): # rich-cmp/py3k-friendly version
return (
(self.ver_desc or "").lower() == (other.ver_desc or "").lower()
and (self.desc or "").lower() == (other.desc or "").lower()
and self.major == other.major
and self.minor == other.minor
)
def Resolve(self):
if self.dll is None:
return 0
tlb = pythoncom.LoadTypeLib(self.dll)
self.FromTypelib(tlb, None)
return 1
def FromTypelib(self, typelib, dllName=None):
la = typelib.GetLibAttr()
self.clsid = str(la[0])
self.lcid = la[1]
self.major = la[3]
self.minor = la[4]
if dllName:
self.dll = dllName
def EnumKeys(root):
index = 0
ret = []
while 1:
try:
item = win32api.RegEnumKey(root, index)
except win32api.error:
break
try:
# Note this doesn't handle REG_EXPAND_SZ, but the implementation
# here doesn't need to - that is handled as the data is read.
val = win32api.RegQueryValue(root, item)
except win32api.error:
val = "" # code using this assumes a string.
ret.append((item, val))
index = index + 1
return ret
FLAG_RESTRICTED = 1
FLAG_CONTROL = 2
FLAG_HIDDEN = 4
def EnumTlbs(excludeFlags=0):
"""Return a list of TypelibSpec objects, one for each registered library."""
key = win32api.RegOpenKey(win32con.HKEY_CLASSES_ROOT, "Typelib")
iids = EnumKeys(key)
results = []
for iid, crap in iids:
try:
key2 = win32api.RegOpenKey(key, str(iid))
except win32api.error:
# A few good reasons for this, including "access denied".
continue
for version, tlbdesc in EnumKeys(key2):
major_minor = version.split(".", 1)
if len(major_minor) < 2:
major_minor.append("0")
# For some reason, this code used to assume the values were hex.
# This seems to not be true - particularly for CDO 1.21
# *sigh* - it appears there are no rules here at all, so when we need
# to know the info, we must load the tlb by filename and request it.
# The Resolve() method on the TypelibSpec does this.
# For this reason, keep the version numbers as strings - that
# way we can't be wrong! Let code that really needs an int to work
# out what to do. FWIW, http://support.microsoft.com/kb/816970 is
# pretty clear that they *should* be hex.
major = major_minor[0]
minor = major_minor[1]
key3 = win32api.RegOpenKey(key2, str(version))
try:
# The "FLAGS" are at this point
flags = int(win32api.RegQueryValue(key3, "FLAGS"))
except (win32api.error, ValueError):
flags = 0
if flags & excludeFlags == 0:
for lcid, crap in EnumKeys(key3):
try:
lcid = int(lcid)
except ValueError: # not an LCID entry
continue
# Check for both "{lcid}\win32" and "{lcid}\win64" keys.
try:
key4 = win32api.RegOpenKey(key3, "%s\\win32" % (lcid,))
except win32api.error:
try:
key4 = win32api.RegOpenKey(key3, "%s\\win64" % (lcid,))
except win32api.error:
continue
try:
dll, typ = win32api.RegQueryValueEx(key4, None)
if typ == win32con.REG_EXPAND_SZ:
dll = win32api.ExpandEnvironmentStrings(dll)
except win32api.error:
dll = None
spec = TypelibSpec(iid, lcid, major, minor, flags)
spec.dll = dll
spec.desc = tlbdesc
spec.ver_desc = tlbdesc + " (" + version + ")"
results.append(spec)
return results
def FindTlbsWithDescription(desc):
"""Find all installed type libraries with the specified description"""
ret = []
items = EnumTlbs()
for item in items:
if item.desc == desc:
ret.append(item)
return ret
def SelectTlb(title="Select Library", excludeFlags=0):
"""Display a list of all the type libraries, and select one. Returns None if cancelled"""
import pywin.dialogs.list
items = EnumTlbs(excludeFlags)
# fixup versions - we assume hex (see __init__ above)
for i in items:
i.major = int(i.major, 16)
i.minor = int(i.minor, 16)
items.sort()
rc = pywin.dialogs.list.SelectFromLists(title, items, ["Type Library"])
if rc is None:
return None
return items[rc]
# Test code.
if __name__ == "__main__":
print(SelectTlb().__dict__)

View file

@ -0,0 +1,279 @@
import commctrl
import pythoncom
import win32api
import win32con
import win32ui
from pywin.mfc import dialog
class TLBrowserException(Exception):
"TypeLib browser internal error"
error = TLBrowserException
FRAMEDLG_STD = win32con.WS_CAPTION | win32con.WS_SYSMENU
SS_STD = win32con.WS_CHILD | win32con.WS_VISIBLE
BS_STD = SS_STD | win32con.WS_TABSTOP
ES_STD = BS_STD | win32con.WS_BORDER
LBS_STD = (
ES_STD | win32con.LBS_NOTIFY | win32con.LBS_NOINTEGRALHEIGHT | win32con.WS_VSCROLL
)
CBS_STD = ES_STD | win32con.CBS_NOINTEGRALHEIGHT | win32con.WS_VSCROLL
typekindmap = {
pythoncom.TKIND_ENUM: "Enumeration",
pythoncom.TKIND_RECORD: "Record",
pythoncom.TKIND_MODULE: "Module",
pythoncom.TKIND_INTERFACE: "Interface",
pythoncom.TKIND_DISPATCH: "Dispatch",
pythoncom.TKIND_COCLASS: "CoClass",
pythoncom.TKIND_ALIAS: "Alias",
pythoncom.TKIND_UNION: "Union",
}
TypeBrowseDialog_Parent = dialog.Dialog
class TypeBrowseDialog(TypeBrowseDialog_Parent):
"Browse a type library"
IDC_TYPELIST = 1000
IDC_MEMBERLIST = 1001
IDC_PARAMLIST = 1002
IDC_LISTVIEW = 1003
def __init__(self, typefile=None):
TypeBrowseDialog_Parent.__init__(self, self.GetTemplate())
try:
if typefile:
self.tlb = pythoncom.LoadTypeLib(typefile)
else:
self.tlb = None
except pythoncom.ole_error:
self.MessageBox("The file does not contain type information")
self.tlb = None
self.HookCommand(self.CmdTypeListbox, self.IDC_TYPELIST)
self.HookCommand(self.CmdMemberListbox, self.IDC_MEMBERLIST)
def OnAttachedObjectDeath(self):
self.tlb = None
self.typeinfo = None
self.attr = None
return TypeBrowseDialog_Parent.OnAttachedObjectDeath(self)
def _SetupMenu(self):
menu = win32ui.CreateMenu()
flags = win32con.MF_STRING | win32con.MF_ENABLED
menu.AppendMenu(flags, win32ui.ID_FILE_OPEN, "&Open...")
menu.AppendMenu(flags, win32con.IDCANCEL, "&Close")
mainMenu = win32ui.CreateMenu()
mainMenu.AppendMenu(flags | win32con.MF_POPUP, menu.GetHandle(), "&File")
self.SetMenu(mainMenu)
self.HookCommand(self.OnFileOpen, win32ui.ID_FILE_OPEN)
def OnFileOpen(self, id, code):
openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST
fspec = "Type Libraries (*.tlb, *.olb)|*.tlb;*.olb|OCX Files (*.ocx)|*.ocx|DLL's (*.dll)|*.dll|All Files (*.*)|*.*||"
dlg = win32ui.CreateFileDialog(1, None, None, openFlags, fspec)
if dlg.DoModal() == win32con.IDOK:
try:
self.tlb = pythoncom.LoadTypeLib(dlg.GetPathName())
except pythoncom.ole_error:
self.MessageBox("The file does not contain type information")
self.tlb = None
self._SetupTLB()
def OnInitDialog(self):
self._SetupMenu()
self.typelb = self.GetDlgItem(self.IDC_TYPELIST)
self.memberlb = self.GetDlgItem(self.IDC_MEMBERLIST)
self.paramlb = self.GetDlgItem(self.IDC_PARAMLIST)
self.listview = self.GetDlgItem(self.IDC_LISTVIEW)
# Setup the listview columns
itemDetails = (commctrl.LVCFMT_LEFT, 100, "Item", 0)
self.listview.InsertColumn(0, itemDetails)
itemDetails = (commctrl.LVCFMT_LEFT, 1024, "Details", 0)
self.listview.InsertColumn(1, itemDetails)
if self.tlb is None:
self.OnFileOpen(None, None)
else:
self._SetupTLB()
return TypeBrowseDialog_Parent.OnInitDialog(self)
def _SetupTLB(self):
self.typelb.ResetContent()
self.memberlb.ResetContent()
self.paramlb.ResetContent()
self.typeinfo = None
self.attr = None
if self.tlb is None:
return
n = self.tlb.GetTypeInfoCount()
for i in range(n):
self.typelb.AddString(self.tlb.GetDocumentation(i)[0])
def _SetListviewTextItems(self, items):
self.listview.DeleteAllItems()
index = -1
for item in items:
index = self.listview.InsertItem(index + 1, item[0])
data = item[1]
if data is None:
data = ""
self.listview.SetItemText(index, 1, data)
def SetupAllInfoTypes(self):
infos = self._GetMainInfoTypes() + self._GetMethodInfoTypes()
self._SetListviewTextItems(infos)
def _GetMainInfoTypes(self):
pos = self.typelb.GetCurSel()
if pos < 0:
return []
docinfo = self.tlb.GetDocumentation(pos)
infos = [("GUID", str(self.attr[0]))]
infos.append(("Help File", docinfo[3]))
infos.append(("Help Context", str(docinfo[2])))
try:
infos.append(("Type Kind", typekindmap[self.tlb.GetTypeInfoType(pos)]))
except:
pass
info = self.tlb.GetTypeInfo(pos)
attr = info.GetTypeAttr()
infos.append(("Attributes", str(attr)))
for j in range(attr[8]):
flags = info.GetImplTypeFlags(j)
refInfo = info.GetRefTypeInfo(info.GetRefTypeOfImplType(j))
doc = refInfo.GetDocumentation(-1)
attr = refInfo.GetTypeAttr()
typeKind = attr[5]
typeFlags = attr[11]
desc = doc[0]
desc = desc + ", Flags=0x%x, typeKind=0x%x, typeFlags=0x%x" % (
flags,
typeKind,
typeFlags,
)
if flags & pythoncom.IMPLTYPEFLAG_FSOURCE:
desc = desc + "(Source)"
infos.append(("Implements", desc))
return infos
def _GetMethodInfoTypes(self):
pos = self.memberlb.GetCurSel()
if pos < 0:
return []
realPos, isMethod = self._GetRealMemberPos(pos)
ret = []
if isMethod:
funcDesc = self.typeinfo.GetFuncDesc(realPos)
id = funcDesc[0]
ret.append(("Func Desc", str(funcDesc)))
else:
id = self.typeinfo.GetVarDesc(realPos)[0]
docinfo = self.typeinfo.GetDocumentation(id)
ret.append(("Help String", docinfo[1]))
ret.append(("Help Context", str(docinfo[2])))
return ret
def CmdTypeListbox(self, id, code):
if code == win32con.LBN_SELCHANGE:
pos = self.typelb.GetCurSel()
if pos >= 0:
self.memberlb.ResetContent()
self.typeinfo = self.tlb.GetTypeInfo(pos)
self.attr = self.typeinfo.GetTypeAttr()
for i in range(self.attr[7]):
id = self.typeinfo.GetVarDesc(i)[0]
self.memberlb.AddString(self.typeinfo.GetNames(id)[0])
for i in range(self.attr[6]):
id = self.typeinfo.GetFuncDesc(i)[0]
self.memberlb.AddString(self.typeinfo.GetNames(id)[0])
self.SetupAllInfoTypes()
return 1
def _GetRealMemberPos(self, pos):
pos = self.memberlb.GetCurSel()
if pos >= self.attr[7]:
return pos - self.attr[7], 1
elif pos >= 0:
return pos, 0
else:
raise error("The position is not valid")
def CmdMemberListbox(self, id, code):
if code == win32con.LBN_SELCHANGE:
self.paramlb.ResetContent()
pos = self.memberlb.GetCurSel()
realPos, isMethod = self._GetRealMemberPos(pos)
if isMethod:
id = self.typeinfo.GetFuncDesc(realPos)[0]
names = self.typeinfo.GetNames(id)
for i in range(len(names)):
if i > 0:
self.paramlb.AddString(names[i])
self.SetupAllInfoTypes()
return 1
def GetTemplate(self):
"Return the template used to create this dialog"
w = 272 # Dialog width
h = 192 # Dialog height
style = (
FRAMEDLG_STD
| win32con.WS_VISIBLE
| win32con.DS_SETFONT
| win32con.WS_MINIMIZEBOX
)
template = [
["Type Library Browser", (0, 0, w, h), style, None, (8, "Helv")],
]
template.append([130, "&Type", -1, (10, 10, 62, 9), SS_STD | win32con.SS_LEFT])
template.append([131, None, self.IDC_TYPELIST, (10, 20, 80, 80), LBS_STD])
template.append(
[130, "&Members", -1, (100, 10, 62, 9), SS_STD | win32con.SS_LEFT]
)
template.append([131, None, self.IDC_MEMBERLIST, (100, 20, 80, 80), LBS_STD])
template.append(
[130, "&Parameters", -1, (190, 10, 62, 9), SS_STD | win32con.SS_LEFT]
)
template.append([131, None, self.IDC_PARAMLIST, (190, 20, 75, 80), LBS_STD])
lvStyle = (
SS_STD
| commctrl.LVS_REPORT
| commctrl.LVS_AUTOARRANGE
| commctrl.LVS_ALIGNLEFT
| win32con.WS_BORDER
| win32con.WS_TABSTOP
)
template.append(
["SysListView32", "", self.IDC_LISTVIEW, (10, 110, 255, 65), lvStyle]
)
return template
if __name__ == "__main__":
import sys
fname = None
try:
fname = sys.argv[1]
except:
pass
dlg = TypeBrowseDialog(fname)
if win32api.GetConsoleTitle(): # empty string w/o console
dlg.DoModal()
else:
dlg.CreateWindow(win32ui.GetMainFrame())

102
lib/win32com/client/util.py Normal file
View file

@ -0,0 +1,102 @@
"""General client side utilities.
This module contains utility functions, used primarily by advanced COM
programmers, or other COM modules.
"""
import pythoncom
from win32com.client import Dispatch, _get_good_object_
PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
def WrapEnum(ob, resultCLSID=None):
"""Wrap an object in a VARIANT enumerator.
All VT_DISPATCHs returned by the enumerator are converted to wrapper objects
(which may be either a class instance, or a dynamic.Dispatch type object).
"""
if type(ob) != pythoncom.TypeIIDs[pythoncom.IID_IEnumVARIANT]:
ob = ob.QueryInterface(pythoncom.IID_IEnumVARIANT)
return EnumVARIANT(ob, resultCLSID)
class Enumerator:
"""A class that provides indexed access into an Enumerator
By wrapping a PyIEnum* object in this class, you can perform
natural looping and indexing into the Enumerator.
Looping is very efficient, but it should be noted that although random
access is supported, the underlying object is still an enumerator, so
this will force many reset-and-seek operations to find the requested index.
"""
def __init__(self, enum):
self._oleobj_ = enum # a PyIEnumVARIANT
self.index = -1
def __getitem__(self, index):
return self.__GetIndex(index)
def __call__(self, index):
return self.__GetIndex(index)
def __GetIndex(self, index):
if type(index) != type(0):
raise TypeError("Only integer indexes are supported for enumerators")
# NOTE
# In this context, self.index is users purely as a flag to say
# "am I still in sequence". The user may call Next() or Reset() if they
# so choose, in which case self.index will not be correct (although we
# still want to stay in sequence)
if index != self.index + 1:
# Index requested out of sequence.
self._oleobj_.Reset()
if index:
self._oleobj_.Skip(
index
) # if asked for item 1, must skip 1, Python always zero based.
self.index = index
result = self._oleobj_.Next(1)
if len(result):
return self._make_retval_(result[0])
raise IndexError("list index out of range")
def Next(self, count=1):
ret = self._oleobj_.Next(count)
realRets = []
for r in ret:
realRets.append(self._make_retval_(r))
return tuple(realRets) # Convert back to tuple.
def Reset(self):
return self._oleobj_.Reset()
def Clone(self):
return self.__class__(self._oleobj_.Clone(), self.resultCLSID)
def _make_retval_(self, result):
return result
class EnumVARIANT(Enumerator):
def __init__(self, enum, resultCLSID=None):
self.resultCLSID = resultCLSID
Enumerator.__init__(self, enum)
def _make_retval_(self, result):
return _get_good_object_(result, resultCLSID=self.resultCLSID)
class Iterator:
def __init__(self, enum, resultCLSID=None):
self.resultCLSID = resultCLSID
self._iter_ = iter(enum.QueryInterface(pythoncom.IID_IEnumVARIANT))
def __iter__(self):
return self
def __next__(self):
return _get_good_object_(next(self._iter_), resultCLSID=self.resultCLSID)

View file

View file

@ -0,0 +1,100 @@
# Implements _both_ a connectable client, and a connectable server.
#
# Note that we cheat just a little - the Server in this demo is not created
# via Normal COM - this means we can avoid registering the server.
# However, the server _is_ accessed as a COM object - just the creation
# is cheated on - so this is still working as a fully-fledged server.
import pythoncom
import win32com.server.connect
import win32com.server.util
from pywin32_testutil import str2bytes
from win32com.server.exception import Exception
# This is the IID of the Events interface both Client and Server support.
IID_IConnectDemoEvents = pythoncom.MakeIID("{A4988850-49C3-11d0-AE5D-52342E000000}")
# The server which implements
# Create a connectable class, that has a single public method
# 'DoIt', which echos to a single sink 'DoneIt'
class ConnectableServer(win32com.server.connect.ConnectableServer):
_public_methods_ = [
"DoIt"
] + win32com.server.connect.ConnectableServer._public_methods_
_connect_interfaces_ = [IID_IConnectDemoEvents]
# The single public method that the client can call on us
# (ie, as a normal COM server, this exposes just this single method.
def DoIt(self, arg):
# Simply broadcast a notification.
self._BroadcastNotify(self.NotifyDoneIt, (arg,))
def NotifyDoneIt(self, interface, arg):
interface.Invoke(1000, 0, pythoncom.DISPATCH_METHOD, 1, arg)
# Here is the client side of the connection world.
# Define a COM object which implements the methods defined by the
# IConnectDemoEvents interface.
class ConnectableClient:
# This is another cheat - I _know_ the server defines the "DoneIt" event
# as DISPID==1000 - I also know from the implementation details of COM
# that the first method in _public_methods_ gets 1000.
# Normally some explicit DISPID->Method mapping is required.
_public_methods_ = ["OnDoneIt"]
def __init__(self):
self.last_event_arg = None
# A client must implement QI, and respond to a query for the Event interface.
# In addition, it must provide a COM object (which server.util.wrap) does.
def _query_interface_(self, iid):
import win32com.server.util
# Note that this seems like a necessary hack. I am responding to IID_IConnectDemoEvents
# but only creating an IDispatch gateway object.
if iid == IID_IConnectDemoEvents:
return win32com.server.util.wrap(self)
# And here is our event method which gets called.
def OnDoneIt(self, arg):
self.last_event_arg = arg
def CheckEvent(server, client, val, verbose):
client.last_event_arg = None
server.DoIt(val)
if client.last_event_arg != val:
raise RuntimeError("Sent %r, but got back %r" % (val, client.last_event_arg))
if verbose:
print("Sent and received %r" % val)
# A simple test script for all this.
# In the real world, it is likely that the code controlling the server
# will be in the same class as that getting the notifications.
def test(verbose=0):
import win32com.client.connect
import win32com.client.dynamic
import win32com.server.policy
server = win32com.client.dynamic.Dispatch(
win32com.server.util.wrap(ConnectableServer())
)
connection = win32com.client.connect.SimpleConnection()
client = ConnectableClient()
connection.Connect(server, client, IID_IConnectDemoEvents)
CheckEvent(server, client, "Hello", verbose)
CheckEvent(server, client, str2bytes("Here is a null>\x00<"), verbose)
CheckEvent(server, client, "Here is a null>\x00<", verbose)
val = "test-\xe0\xf2" # 2 extended characters.
CheckEvent(server, client, val, verbose)
if verbose:
print("Everything seemed to work!")
# Aggressive memory leak checking (ie, do nothing!) :-) All should cleanup OK???
if __name__ == "__main__":
test(1)

View file

@ -0,0 +1,74 @@
import pythoncom
import win32con
formats = """CF_TEXT CF_BITMAP CF_METAFILEPICT CF_SYLK CF_DIF CF_TIFF
CF_OEMTEXT CF_DIB CF_PALETTE CF_PENDATA CF_RIFF CF_WAVE
CF_UNICODETEXT CF_ENHMETAFILE CF_HDROP CF_LOCALE CF_MAX
CF_OWNERDISPLAY CF_DSPTEXT CF_DSPBITMAP CF_DSPMETAFILEPICT
CF_DSPENHMETAFILE""".split()
format_name_map = {}
for f in formats:
val = getattr(win32con, f)
format_name_map[val] = f
tymeds = [attr for attr in pythoncom.__dict__.keys() if attr.startswith("TYMED_")]
def DumpClipboard():
do = pythoncom.OleGetClipboard()
print("Dumping all clipboard formats...")
for fe in do.EnumFormatEtc():
fmt, td, aspect, index, tymed = fe
tymeds_this = [
getattr(pythoncom, t) for t in tymeds if tymed & getattr(pythoncom, t)
]
print("Clipboard format", format_name_map.get(fmt, str(fmt)))
for t_this in tymeds_this:
# As we are enumerating there should be no need to call
# QueryGetData, but we do anyway!
fetc_query = fmt, td, aspect, index, t_this
try:
do.QueryGetData(fetc_query)
except pythoncom.com_error:
print("Eeek - QGD indicated failure for tymed", t_this)
# now actually get it.
try:
medium = do.GetData(fetc_query)
except pythoncom.com_error as exc:
print("Failed to get the clipboard data:", exc)
continue
if medium.tymed == pythoncom.TYMED_GDI:
data = "GDI handle %d" % medium.data
elif medium.tymed == pythoncom.TYMED_MFPICT:
data = "METAFILE handle %d" % medium.data
elif medium.tymed == pythoncom.TYMED_ENHMF:
data = "ENHMETAFILE handle %d" % medium.data
elif medium.tymed == pythoncom.TYMED_HGLOBAL:
data = "%d bytes via HGLOBAL" % len(medium.data)
elif medium.tymed == pythoncom.TYMED_FILE:
data = "filename '%s'" % data
elif medium.tymed == pythoncom.TYMED_ISTREAM:
stream = medium.data
stream.Seek(0, 0)
bytes = 0
while 1:
chunk = stream.Read(4096)
if not chunk:
break
bytes += len(chunk)
data = "%d bytes via IStream" % bytes
elif medium.tymed == pythoncom.TYMED_ISTORAGE:
data = "a IStorage"
else:
data = "*** unknown tymed!"
print(" -> got", data)
do = None
if __name__ == "__main__":
DumpClipboard()
if pythoncom._GetInterfaceCount() + pythoncom._GetGatewayCount():
print(
"XXX - Leaving with %d/%d COM objects alive"
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
)

View file

@ -0,0 +1,98 @@
# A sample originally provided by Richard Bell, and modified by Mark Hammond.
# This sample demonstrates how to use COM events in an aparment-threaded
# world. In this world, COM itself ensures that all calls to and events
# from an object happen on the same thread that created the object, even
# if they originated from different threads. For this cross-thread
# marshalling to work, this main thread *must* run a "message-loop" (ie,
# a loop fetching and dispatching Windows messages). Without such message
# processing, dead-locks can occur.
# See also eventsFreeThreaded.py for how to do this in a free-threaded
# world where these marshalling considerations do not exist.
# NOTE: This example uses Internet Explorer, but it should not be considerd
# a "best-practices" for writing against IE events, but for working with
# events in general. For example:
# * The first OnDocumentComplete event is not a reliable indicator that the
# URL has completed loading
# * As we are demonstrating the most efficient way of handling events, when
# running this sample you will see an IE Windows briefly appear, but
# vanish without ever being repainted.
import time
# sys.coinit_flags not set, so pythoncom initializes apartment-threaded.
import pythoncom
import win32api
import win32com.client
import win32event
class ExplorerEvents:
def __init__(self):
self.event = win32event.CreateEvent(None, 0, 0, None)
def OnDocumentComplete(self, pDisp=pythoncom.Empty, URL=pythoncom.Empty):
thread = win32api.GetCurrentThreadId()
print("OnDocumentComplete event processed on thread %d" % thread)
# Set the event our main thread is waiting on.
win32event.SetEvent(self.event)
def OnQuit(self):
thread = win32api.GetCurrentThreadId()
print("OnQuit event processed on thread %d" % thread)
win32event.SetEvent(self.event)
def WaitWhileProcessingMessages(event, timeout=2):
start = time.perf_counter()
while True:
# Wake 4 times a second - we can't just specify the
# full timeout here, as then it would reset for every
# message we process.
rc = win32event.MsgWaitForMultipleObjects(
(event,), 0, 250, win32event.QS_ALLEVENTS
)
if rc == win32event.WAIT_OBJECT_0:
# event signalled - stop now!
return True
if (time.perf_counter() - start) > timeout:
# Timeout expired.
return False
# must be a message.
pythoncom.PumpWaitingMessages()
def TestExplorerEvents():
iexplore = win32com.client.DispatchWithEvents(
"InternetExplorer.Application", ExplorerEvents
)
thread = win32api.GetCurrentThreadId()
print("TestExplorerEvents created IE object on thread %d" % thread)
iexplore.Visible = 1
try:
iexplore.Navigate(win32api.GetFullPathName("..\\readme.html"))
except pythoncom.com_error as details:
print("Warning - could not open the test HTML file", details)
# Wait for the event to be signalled while pumping messages.
if not WaitWhileProcessingMessages(iexplore.event):
print("Document load event FAILED to fire!!!")
iexplore.Quit()
#
# Give IE a chance to shutdown, else it can get upset on fast machines.
# Note, Quit generates events. Although this test does NOT catch them
# it is NECESSARY to pump messages here instead of a sleep so that the Quit
# happens properly!
if not WaitWhileProcessingMessages(iexplore.event):
print("OnQuit event FAILED to fire!!!")
iexplore = None
if __name__ == "__main__":
TestExplorerEvents()

View file

@ -0,0 +1,92 @@
# A sample originally provided by Richard Bell, and modified by Mark Hammond.
# This sample demonstrates how to use COM events in a free-threaded world.
# In this world, there is no need to marshall calls across threads, so
# no message loops are needed at all. This means regular cross-thread
# sychronization can be used. In this sample we just wait on win32 event
# objects.
# See also ieEventsApartmentThreaded.py for how to do this in an
# aparment-threaded world, where thread-marshalling complicates things.
# NOTE: This example uses Internet Explorer, but it should not be considerd
# a "best-practices" for writing against IE events, but for working with
# events in general. For example:
# * The first OnDocumentComplete event is not a reliable indicator that the
# URL has completed loading
# * As we are demonstrating the most efficient way of handling events, when
# running this sample you will see an IE Windows briefly appear, but
# vanish without ever being repainted.
import sys
sys.coinit_flags = 0 # specify free threading
import pythoncom
import win32api
import win32com.client
import win32event
# The print statements indicate that COM has actually started another thread
# and will deliver the events to that thread (ie, the events do not actually
# fire on our main thread.
class ExplorerEvents:
def __init__(self):
# We reuse this event for all events.
self.event = win32event.CreateEvent(None, 0, 0, None)
def OnDocumentComplete(self, pDisp=pythoncom.Empty, URL=pythoncom.Empty):
#
# Caution: Since the main thread and events thread(s) are different
# it may be necessary to serialize access to shared data. Because
# this is a simple test case, that is not required here. Your
# situation may be different. Caveat programmer.
#
thread = win32api.GetCurrentThreadId()
print("OnDocumentComplete event processed on thread %d" % thread)
# Set the event our main thread is waiting on.
win32event.SetEvent(self.event)
def OnQuit(self):
thread = win32api.GetCurrentThreadId()
print("OnQuit event processed on thread %d" % thread)
win32event.SetEvent(self.event)
def TestExplorerEvents():
iexplore = win32com.client.DispatchWithEvents(
"InternetExplorer.Application", ExplorerEvents
)
thread = win32api.GetCurrentThreadId()
print("TestExplorerEvents created IE object on thread %d" % thread)
iexplore.Visible = 1
try:
iexplore.Navigate(win32api.GetFullPathName("..\\readme.html"))
except pythoncom.com_error as details:
print("Warning - could not open the test HTML file", details)
# In this free-threaded example, we can simply wait until an event has
# been set - we will give it 2 seconds before giving up.
rc = win32event.WaitForSingleObject(iexplore.event, 2000)
if rc != win32event.WAIT_OBJECT_0:
print("Document load event FAILED to fire!!!")
iexplore.Quit()
# Now we can do the same thing to wait for exit!
# Although Quit generates events, in this free-threaded world we
# do *not* need to run any message pumps.
rc = win32event.WaitForSingleObject(iexplore.event, 2000)
if rc != win32event.WAIT_OBJECT_0:
print("OnQuit event FAILED to fire!!!")
iexplore = None
print("Finished the IE event sample!")
if __name__ == "__main__":
TestExplorerEvents()

View file

@ -0,0 +1,170 @@
# A demo plugin for Microsoft Excel
#
# This addin simply adds a new button to the main Excel toolbar,
# and displays a message box when clicked. Thus, it demonstrates
# how to plug in to Excel itself, and hook Excel events.
#
#
# To register the addin, simply execute:
# excelAddin.py
# This will install the COM server, and write the necessary
# AddIn key to Excel
#
# To unregister completely:
# excelAddin.py --unregister
#
# To debug, execute:
# excelAddin.py --debug
#
# Then open Pythonwin, and select "Tools->Trace Collector Debugging Tool"
# Restart excel, and you should see some output generated.
#
# NOTE: If the AddIn fails with an error, Excel will re-register
# the addin to not automatically load next time Excel starts. To
# correct this, simply re-register the addin (see above)
#
# Author <ekoome@yahoo.com> Eric Koome
# Copyright (c) 2003 Wavecom Inc. All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL ERIC KOOME OR
# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
import sys
import pythoncom
from win32com import universal
from win32com.client import Dispatch, DispatchWithEvents, constants, gencache
from win32com.server.exception import COMException
# Support for COM objects we use.
gencache.EnsureModule(
"{00020813-0000-0000-C000-000000000046}", 0, 1, 3, bForDemand=True
) # Excel 9
gencache.EnsureModule(
"{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}", 0, 2, 1, bForDemand=True
) # Office 9
# The TLB defining the interfaces we implement
universal.RegisterInterfaces(
"{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}", 0, 1, 0, ["_IDTExtensibility2"]
)
class ButtonEvent:
def OnClick(self, button, cancel):
import win32con # Possible, but not necessary, to use a Pythonwin GUI
import win32ui
win32ui.MessageBox("Hello from Python", "Python Test", win32con.MB_OKCANCEL)
return cancel
class ExcelAddin:
_com_interfaces_ = ["_IDTExtensibility2"]
_public_methods_ = []
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
_reg_clsid_ = "{C5482ECA-F559-45A0-B078-B2036E6F011A}"
_reg_progid_ = "Python.Test.ExcelAddin"
_reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
def __init__(self):
self.appHostApp = None
def OnConnection(self, application, connectMode, addin, custom):
print("OnConnection", application, connectMode, addin, custom)
try:
self.appHostApp = application
cbcMyBar = self.appHostApp.CommandBars.Add(
Name="PythonBar",
Position=constants.msoBarTop,
MenuBar=constants.msoBarTypeNormal,
Temporary=True,
)
btnMyButton = cbcMyBar.Controls.Add(
Type=constants.msoControlButton, Parameter="Greetings"
)
btnMyButton = self.toolbarButton = DispatchWithEvents(
btnMyButton, ButtonEvent
)
btnMyButton.Style = constants.msoButtonCaption
btnMyButton.BeginGroup = True
btnMyButton.Caption = "&Python"
btnMyButton.TooltipText = "Python rules the World"
btnMyButton.Width = "34"
cbcMyBar.Visible = True
except pythoncom.com_error as xxx_todo_changeme:
(hr, msg, exc, arg) = xxx_todo_changeme.args
print("The Excel call failed with code %d: %s" % (hr, msg))
if exc is None:
print("There is no extended error information")
else:
wcode, source, text, helpFile, helpId, scode = exc
print("The source of the error is", source)
print("The error message is", text)
print("More info can be found in %s (id=%d)" % (helpFile, helpId))
def OnDisconnection(self, mode, custom):
print("OnDisconnection")
self.appHostApp.CommandBars("PythonBar").Delete
self.appHostApp = None
def OnAddInsUpdate(self, custom):
print("OnAddInsUpdate", custom)
def OnStartupComplete(self, custom):
print("OnStartupComplete", custom)
def OnBeginShutdown(self, custom):
print("OnBeginShutdown", custom)
def RegisterAddin(klass):
import winreg
key = winreg.CreateKey(
winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Excel\\Addins"
)
subkey = winreg.CreateKey(key, klass._reg_progid_)
winreg.SetValueEx(subkey, "CommandLineSafe", 0, winreg.REG_DWORD, 0)
winreg.SetValueEx(subkey, "LoadBehavior", 0, winreg.REG_DWORD, 3)
winreg.SetValueEx(subkey, "Description", 0, winreg.REG_SZ, "Excel Addin")
winreg.SetValueEx(subkey, "FriendlyName", 0, winreg.REG_SZ, "A Simple Excel Addin")
def UnregisterAddin(klass):
import winreg
try:
winreg.DeleteKey(
winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Office\\Excel\\Addins\\" + klass._reg_progid_,
)
except WindowsError:
pass
if __name__ == "__main__":
import win32com.server.register
win32com.server.register.UseCommandLine(ExcelAddin)
if "--unregister" in sys.argv:
UnregisterAddin(ExcelAddin)
else:
RegisterAddin(ExcelAddin)

View file

@ -0,0 +1,434 @@
"""Excel IRTDServer implementation.
This module is a functional example of how to implement the IRTDServer interface
in python, using the pywin32 extensions. Further details, about this interface
and it can be found at:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnexcl2k2/html/odc_xlrtdfaq.asp
"""
# Copyright (c) 2003-2004 by Chris Nilsson <chris@slort.org>
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Christopher Nilsson (the author) not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
import datetime # For the example classes...
import threading
import pythoncom
import win32com.client
from win32com import universal
from win32com.client import gencache
from win32com.server.exception import COMException
# Typelib info for version 10 - aka Excel XP.
# This is the minimum version of excel that we can work with as this is when
# Microsoft introduced these interfaces.
EXCEL_TLB_GUID = "{00020813-0000-0000-C000-000000000046}"
EXCEL_TLB_LCID = 0
EXCEL_TLB_MAJOR = 1
EXCEL_TLB_MINOR = 4
# Import the excel typelib to make sure we've got early-binding going on.
# The "ByRef" parameters we use later won't work without this.
gencache.EnsureModule(EXCEL_TLB_GUID, EXCEL_TLB_LCID, EXCEL_TLB_MAJOR, EXCEL_TLB_MINOR)
# Tell pywin to import these extra interfaces.
# --
# QUESTION: Why? The interfaces seem to descend from IDispatch, so
# I'd have thought, for example, calling callback.UpdateNotify() (on the
# IRTDUpdateEvent callback excel gives us) would work without molestation.
# But the callback needs to be cast to a "real" IRTDUpdateEvent type. Hmm...
# This is where my small knowledge of the pywin framework / COM gets hazy.
# --
# Again, we feed in the Excel typelib as the source of these interfaces.
universal.RegisterInterfaces(
EXCEL_TLB_GUID,
EXCEL_TLB_LCID,
EXCEL_TLB_MAJOR,
EXCEL_TLB_MINOR,
["IRtdServer", "IRTDUpdateEvent"],
)
class ExcelRTDServer(object):
"""Base RTDServer class.
Provides most of the features needed to implement the IRtdServer interface.
Manages topic adding, removal, and packing up the values for excel.
Shouldn't be instanciated directly.
Instead, descendant classes should override the CreateTopic() method.
Topic objects only need to provide a GetValue() function to play nice here.
The values given need to be atomic (eg. string, int, float... etc).
Also note: nothing has been done within this class to ensure that we get
time to check our topics for updates. I've left that up to the subclass
since the ways, and needs, of refreshing your topics will vary greatly. For
example, the sample implementation uses a timer thread to wake itself up.
Whichever way you choose to do it, your class needs to be able to wake up
occaisionally, since excel will never call your class without being asked to
first.
Excel will communicate with our object in this order:
1. Excel instanciates our object and calls ServerStart, providing us with
an IRTDUpdateEvent callback object.
2. Excel calls ConnectData when it wants to subscribe to a new "topic".
3. When we have new data to provide, we call the UpdateNotify method of the
callback object we were given.
4. Excel calls our RefreshData method, and receives a 2d SafeArray (row-major)
containing the Topic ids in the 1st dim, and the topic values in the
2nd dim.
5. When not needed anymore, Excel will call our DisconnectData to
unsubscribe from a topic.
6. When there are no more topics left, Excel will call our ServerTerminate
method to kill us.
Throughout, at undetermined periods, Excel will call our Heartbeat
method to see if we're still alive. It must return a non-zero value, or
we'll be killed.
NOTE: By default, excel will at most call RefreshData once every 2 seconds.
This is a setting that needs to be changed excel-side. To change this,
you can set the throttle interval like this in the excel VBA object model:
Application.RTD.ThrottleInterval = 1000 ' milliseconds
"""
_com_interfaces_ = ["IRtdServer"]
_public_methods_ = [
"ConnectData",
"DisconnectData",
"Heartbeat",
"RefreshData",
"ServerStart",
"ServerTerminate",
]
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
# _reg_clsid_ = "# subclass must provide this class attribute"
# _reg_desc_ = "# subclass should provide this description"
# _reg_progid_ = "# subclass must provide this class attribute"
ALIVE = 1
NOT_ALIVE = 0
def __init__(self):
"""Constructor"""
super(ExcelRTDServer, self).__init__()
self.IsAlive = self.ALIVE
self.__callback = None
self.topics = {}
def SignalExcel(self):
"""Use the callback we were given to tell excel new data is available."""
if self.__callback is None:
raise COMException(desc="Callback excel provided is Null")
self.__callback.UpdateNotify()
def ConnectData(self, TopicID, Strings, GetNewValues):
"""Creates a new topic out of the Strings excel gives us."""
try:
self.topics[TopicID] = self.CreateTopic(Strings)
except Exception as why:
raise COMException(desc=str(why))
GetNewValues = True
result = self.topics[TopicID]
if result is None:
result = "# %s: Waiting for update" % self.__class__.__name__
else:
result = result.GetValue()
# fire out internal event...
self.OnConnectData(TopicID)
# GetNewValues as per interface is ByRef, so we need to pass it back too.
return result, GetNewValues
def DisconnectData(self, TopicID):
"""Deletes the given topic."""
self.OnDisconnectData(TopicID)
if TopicID in self.topics:
self.topics[TopicID] = None
del self.topics[TopicID]
def Heartbeat(self):
"""Called by excel to see if we're still here."""
return self.IsAlive
def RefreshData(self, TopicCount):
"""Packs up the topic values. Called by excel when it's ready for an update.
Needs to:
* Return the current number of topics, via the "ByRef" TopicCount
* Return a 2d SafeArray of the topic data.
- 1st dim: topic numbers
- 2nd dim: topic values
We could do some caching, instead of repacking everytime...
But this works for demonstration purposes."""
TopicCount = len(self.topics)
self.OnRefreshData()
# Grow the lists, so we don't need a heap of calls to append()
results = [[None] * TopicCount, [None] * TopicCount]
# Excel expects a 2-dimensional array. The first dim contains the
# topic numbers, and the second contains the values for the topics.
# In true VBA style (yuck), we need to pack the array in row-major format,
# which looks like:
# ( (topic_num1, topic_num2, ..., topic_numN), \
# (topic_val1, topic_val2, ..., topic_valN) )
for idx, topicdata in enumerate(self.topics.items()):
topicNum, topic = topicdata
results[0][idx] = topicNum
results[1][idx] = topic.GetValue()
# TopicCount is meant to be passed to us ByRef, so return it as well, as per
# the way pywin32 handles ByRef arguments.
return tuple(results), TopicCount
def ServerStart(self, CallbackObject):
"""Excel has just created us... We take its callback for later, and set up shop."""
self.IsAlive = self.ALIVE
if CallbackObject is None:
raise COMException(desc="Excel did not provide a callback")
# Need to "cast" the raw PyIDispatch object to the IRTDUpdateEvent interface
IRTDUpdateEventKlass = win32com.client.CLSIDToClass.GetClass(
"{A43788C1-D91B-11D3-8F39-00C04F3651B8}"
)
self.__callback = IRTDUpdateEventKlass(CallbackObject)
self.OnServerStart()
return self.IsAlive
def ServerTerminate(self):
"""Called when excel no longer wants us."""
self.IsAlive = self.NOT_ALIVE # On next heartbeat, excel will free us
self.OnServerTerminate()
def CreateTopic(self, TopicStrings=None):
"""Topic factory method. Subclass must override.
Topic objects need to provide:
* GetValue() method which returns an atomic value.
Will raise NotImplemented if not overridden.
"""
raise NotImplemented("Subclass must implement")
# Overridable class events...
def OnConnectData(self, TopicID):
"""Called when a new topic has been created, at excel's request."""
pass
def OnDisconnectData(self, TopicID):
"""Called when a topic is about to be deleted, at excel's request."""
pass
def OnRefreshData(self):
"""Called when excel has requested all current topic data."""
pass
def OnServerStart(self):
"""Called when excel has instanciated us."""
pass
def OnServerTerminate(self):
"""Called when excel is about to destroy us."""
pass
class RTDTopic(object):
"""Base RTD Topic.
Only method required by our RTDServer implementation is GetValue().
The others are more for convenience."""
def __init__(self, TopicStrings):
super(RTDTopic, self).__init__()
self.TopicStrings = TopicStrings
self.__currentValue = None
self.__dirty = False
def Update(self, sender):
"""Called by the RTD Server.
Gives us a chance to check if our topic data needs to be
changed (eg. check a file, quiz a database, etc)."""
raise NotImplemented("subclass must implement")
def Reset(self):
"""Call when this topic isn't considered "dirty" anymore."""
self.__dirty = False
def GetValue(self):
return self.__currentValue
def SetValue(self, value):
self.__dirty = True
self.__currentValue = value
def HasChanged(self):
return self.__dirty
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
######################################
# Example classes
######################################
class TimeServer(ExcelRTDServer):
"""Example Time RTD server.
Sends time updates back to excel.
example of use, in an excel sheet:
=RTD("Python.RTD.TimeServer","","seconds","5")
This will cause a timestamp string to fill the cell, and update its value
every 5 seconds (or as close as possible depending on how busy excel is).
The empty string parameter denotes the com server is running on the local
machine. Otherwise, put in the hostname to look on. For more info
on this, lookup the Excel help for its "RTD" worksheet function.
Obviously, you'd want to wrap this kind of thing in a friendlier VBA
function.
Also, remember that the RTD function accepts a maximum of 28 arguments!
If you want to pass more, you may need to concatenate arguments into one
string, and have your topic parse them appropriately.
"""
# win32com.server setup attributes...
# Never copy the _reg_clsid_ value in your own classes!
_reg_clsid_ = "{EA7F2CF1-11A2-45E4-B2D5-68E240DB8CB1}"
_reg_progid_ = "Python.RTD.TimeServer"
_reg_desc_ = "Python class implementing Excel IRTDServer -- feeds time"
# other class attributes...
INTERVAL = 0.5 # secs. Threaded timer will wake us up at this interval.
def __init__(self):
super(TimeServer, self).__init__()
# Simply timer thread to ensure we get to update our topics, and
# tell excel about any changes. This is a pretty basic and dirty way to
# do this. Ideally, there should be some sort of waitable (eg. either win32
# event, socket data event...) and be kicked off by that event triggering.
# As soon as we set up shop here, we _must_ return control back to excel.
# (ie. we can't block and do our own thing...)
self.ticker = threading.Timer(self.INTERVAL, self.Update)
def OnServerStart(self):
self.ticker.start()
def OnServerTerminate(self):
if not self.ticker.finished.isSet():
self.ticker.cancel() # Cancel our wake-up thread. Excel has killed us.
def Update(self):
# Get our wake-up thread ready...
self.ticker = threading.Timer(self.INTERVAL, self.Update)
try:
# Check if any of our topics have new info to pass on
if len(self.topics):
refresh = False
for topic in self.topics.values():
topic.Update(self)
if topic.HasChanged():
refresh = True
topic.Reset()
if refresh:
self.SignalExcel()
finally:
self.ticker.start() # Make sure we get to run again
def CreateTopic(self, TopicStrings=None):
"""Topic factory. Builds a TimeTopic object out of the given TopicStrings."""
return TimeTopic(TopicStrings)
class TimeTopic(RTDTopic):
"""Example topic for example RTD server.
Will accept some simple commands to alter how long to delay value updates.
Commands:
* seconds, delay_in_seconds
* minutes, delay_in_minutes
* hours, delay_in_hours
"""
def __init__(self, TopicStrings):
super(TimeTopic, self).__init__(TopicStrings)
try:
self.cmd, self.delay = self.TopicStrings
except Exception as E:
# We could simply return a "# ERROR" type string as the
# topic value, but explosions like this should be able to get handled by
# the VBA-side "On Error" stuff.
raise ValueError("Invalid topic strings: %s" % str(TopicStrings))
# self.cmd = str(self.cmd)
self.delay = float(self.delay)
# setup our initial value
self.checkpoint = self.timestamp()
self.SetValue(str(self.checkpoint))
def timestamp(self):
return datetime.datetime.now()
def Update(self, sender):
now = self.timestamp()
delta = now - self.checkpoint
refresh = False
if self.cmd == "seconds":
if delta.seconds >= self.delay:
refresh = True
elif self.cmd == "minutes":
if delta.minutes >= self.delay:
refresh = True
elif self.cmd == "hours":
if delta.hours >= self.delay:
refresh = True
else:
self.SetValue("#Unknown command: " + self.cmd)
if refresh:
self.SetValue(str(now))
self.checkpoint = now
if __name__ == "__main__":
import win32com.server.register
# Register/Unregister TimeServer example
# eg. at the command line: excelrtd.py --register
# Then type in an excel cell something like:
# =RTD("Python.RTD.TimeServer","","seconds","5")
win32com.server.register.UseCommandLine(TimeServer)

View file

@ -0,0 +1,217 @@
# -*- coding: latin-1 -*-
# PyWin32 Internet Explorer Button
#
# written by Leonard Ritter (paniq@gmx.net)
# and Robert Förtsch (info@robert-foertsch.com)
"""
This sample implements a simple IE Button COM server
with access to the IWebBrowser2 interface.
To demonstrate:
* Execute this script to register the server.
* Open Pythonwin's Tools -> Trace Collector Debugging Tool, so you can
see the output of 'print' statements in this demo.
* Open a new IE instance. The toolbar should have a new "scissors" icon,
with tooltip text "IE Button" - this is our new button - click it.
* Switch back to the Pythonwin window - you should see:
IOleCommandTarget::Exec called.
This is the button being clicked. Extending this to do something more
useful is left as an exercise.
Contribtions to this sample to make it a little "friendlier" welcome!
"""
# imports section
import pythoncom
import win32api
import win32com
import win32com.server.register
from win32com import universal
from win32com.client import Dispatch, DispatchWithEvents, constants, gencache, getevents
# This demo uses 'print' - use win32traceutil to see it if we have no
# console.
try:
win32api.GetConsoleTitle()
except win32api.error:
import win32traceutil
import array
from win32com.axcontrol import axcontrol
# ensure we know the ms internet controls typelib so we have access to IWebBrowser2 later on
win32com.client.gencache.EnsureModule("{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1)
#
IObjectWithSite_methods = ["SetSite", "GetSite"]
IOleCommandTarget_methods = ["Exec", "QueryStatus"]
_iebutton_methods_ = IOleCommandTarget_methods + IObjectWithSite_methods
_iebutton_com_interfaces_ = [
axcontrol.IID_IOleCommandTarget,
axcontrol.IID_IObjectWithSite, # IObjectWithSite
]
class Stub:
"""
this class serves as a method stub,
outputting debug info whenever the object
is being called.
"""
def __init__(self, name):
self.name = name
def __call__(self, *args):
print("STUB: ", self.name, args)
class IEButton:
"""
The actual COM server class
"""
_com_interfaces_ = _iebutton_com_interfaces_
_public_methods_ = _iebutton_methods_
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
_button_text_ = "IE Button"
_tool_tip_ = "An example implementation for an IE Button."
_icon_ = ""
_hot_icon_ = ""
def __init__(self):
# put stubs for non-implemented methods
for method in self._public_methods_:
if not hasattr(self, method):
print("providing default stub for %s" % method)
setattr(self, method, Stub(method))
def QueryStatus(self, pguidCmdGroup, prgCmds, cmdtextf):
# 'cmdtextf' is the 'cmdtextf' element from the OLECMDTEXT structure,
# or None if a NULL pointer was passed.
result = []
for id, flags in prgCmds:
flags |= axcontrol.OLECMDF_SUPPORTED | axcontrol.OLECMDF_ENABLED
result.append((id, flags))
if cmdtextf is None:
cmdtext = None # must return None if nothing requested.
# IE never seems to want any text - this code is here for
# demo purposes only
elif cmdtextf == axcontrol.OLECMDTEXTF_NAME:
cmdtext = "IEButton Name"
else:
cmdtext = "IEButton State"
return result, cmdtext
def Exec(self, pguidCmdGroup, nCmdID, nCmdExecOpt, pvaIn):
print(pguidCmdGroup, nCmdID, nCmdExecOpt, pvaIn)
print("IOleCommandTarget::Exec called.")
# self.webbrowser.ShowBrowserBar(GUID_IETOOLBAR, not is_ietoolbar_visible())
def SetSite(self, unknown):
if unknown:
# first get a command target
cmdtarget = unknown.QueryInterface(axcontrol.IID_IOleCommandTarget)
# then travel over to a service provider
serviceprovider = cmdtarget.QueryInterface(pythoncom.IID_IServiceProvider)
# finally ask for the internet explorer application, returned as a dispatch object
self.webbrowser = win32com.client.Dispatch(
serviceprovider.QueryService(
"{0002DF05-0000-0000-C000-000000000046}", pythoncom.IID_IDispatch
)
)
else:
# lose all references
self.webbrowser = None
def GetClassID(self):
return self._reg_clsid_
def register(classobj):
import winreg
subKeyCLSID = (
"SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\%38s"
% classobj._reg_clsid_
)
try:
hKey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, subKeyCLSID)
subKey = winreg.SetValueEx(
hKey, "ButtonText", 0, winreg.REG_SZ, classobj._button_text_
)
winreg.SetValueEx(
hKey, "ClsidExtension", 0, winreg.REG_SZ, classobj._reg_clsid_
) # reg value for calling COM object
winreg.SetValueEx(
hKey, "CLSID", 0, winreg.REG_SZ, "{1FBA04EE-3024-11D2-8F1F-0000F87ABD16}"
) # CLSID for button that sends command to COM object
winreg.SetValueEx(hKey, "Default Visible", 0, winreg.REG_SZ, "Yes")
winreg.SetValueEx(hKey, "ToolTip", 0, winreg.REG_SZ, classobj._tool_tip_)
winreg.SetValueEx(hKey, "Icon", 0, winreg.REG_SZ, classobj._icon_)
winreg.SetValueEx(hKey, "HotIcon", 0, winreg.REG_SZ, classobj._hot_icon_)
except WindowsError:
print("Couldn't set standard toolbar reg keys.")
else:
print("Set standard toolbar reg keys.")
def unregister(classobj):
import winreg
subKeyCLSID = (
"SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\%38s"
% classobj._reg_clsid_
)
try:
hKey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, subKeyCLSID)
subKey = winreg.DeleteValue(hKey, "ButtonText")
winreg.DeleteValue(hKey, "ClsidExtension") # for calling COM object
winreg.DeleteValue(hKey, "CLSID")
winreg.DeleteValue(hKey, "Default Visible")
winreg.DeleteValue(hKey, "ToolTip")
winreg.DeleteValue(hKey, "Icon")
winreg.DeleteValue(hKey, "HotIcon")
winreg.DeleteKey(winreg.HKEY_LOCAL_MACHINE, subKeyCLSID)
except WindowsError:
print("Couldn't delete Standard toolbar regkey.")
else:
print("Deleted Standard toolbar regkey.")
#
# test implementation
#
class PyWin32InternetExplorerButton(IEButton):
_reg_clsid_ = "{104B66A9-9E68-49D1-A3F5-94754BE9E0E6}"
_reg_progid_ = "PyWin32.IEButton"
_reg_desc_ = "Test Button"
_button_text_ = "IE Button"
_tool_tip_ = "An example implementation for an IE Button."
_icon_ = ""
_hot_icon_ = _icon_
def DllRegisterServer():
register(PyWin32InternetExplorerButton)
def DllUnregisterServer():
unregister(PyWin32InternetExplorerButton)
if __name__ == "__main__":
win32com.server.register.UseCommandLine(
PyWin32InternetExplorerButton,
finalize_register=DllRegisterServer,
finalize_unregister=DllUnregisterServer,
)

View file

@ -0,0 +1,376 @@
# -*- coding: latin-1 -*-
# PyWin32 Internet Explorer Toolbar
#
# written by Leonard Ritter (paniq@gmx.net)
# and Robert Förtsch (info@robert-foertsch.com)
"""
This sample implements a simple IE Toolbar COM server
supporting Windows XP styles and access to
the IWebBrowser2 interface.
It also demonstrates how to hijack the parent window
to catch WM_COMMAND messages.
"""
# imports section
import sys
import winreg
import pythoncom
import win32com
from win32com import universal
from win32com.axcontrol import axcontrol
from win32com.client import Dispatch, DispatchWithEvents, constants, gencache, getevents
from win32com.shell import shell
from win32com.shell.shellcon import *
try:
# try to get styles (winxp)
import winxpgui as win32gui
except:
# import default module (win2k and lower)
import win32gui
import array
import struct
import commctrl
import win32con
import win32ui
# ensure we know the ms internet controls typelib so we have access to IWebBrowser2 later on
win32com.client.gencache.EnsureModule("{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1)
#
IDeskBand_methods = ["GetBandInfo"]
IDockingWindow_methods = ["ShowDW", "CloseDW", "ResizeBorderDW"]
IOleWindow_methods = ["GetWindow", "ContextSensitiveHelp"]
IInputObject_methods = ["UIActivateIO", "HasFocusIO", "TranslateAcceleratorIO"]
IObjectWithSite_methods = ["SetSite", "GetSite"]
IPersistStream_methods = ["GetClassID", "IsDirty", "Load", "Save", "GetSizeMax"]
_ietoolbar_methods_ = (
IDeskBand_methods
+ IDockingWindow_methods
+ IOleWindow_methods
+ IInputObject_methods
+ IObjectWithSite_methods
+ IPersistStream_methods
)
_ietoolbar_com_interfaces_ = [
shell.IID_IDeskBand, # IDeskBand
axcontrol.IID_IObjectWithSite, # IObjectWithSite
pythoncom.IID_IPersistStream,
axcontrol.IID_IOleCommandTarget,
]
class WIN32STRUCT:
def __init__(self, **kw):
full_fmt = ""
for name, fmt, default in self._struct_items_:
self.__dict__[name] = None
if fmt == "z":
full_fmt += "pi"
else:
full_fmt += fmt
for name, val in kw.items():
self.__dict__[name] = val
def __setattr__(self, attr, val):
if not attr.startswith("_") and attr not in self.__dict__:
raise AttributeError(attr)
self.__dict__[attr] = val
def toparam(self):
self._buffs = []
full_fmt = ""
vals = []
for name, fmt, default in self._struct_items_:
val = self.__dict__[name]
if fmt == "z":
fmt = "Pi"
if val is None:
vals.append(0)
vals.append(0)
else:
str_buf = array.array("c", val + "\0")
vals.append(str_buf.buffer_info()[0])
vals.append(len(val))
self._buffs.append(str_buf) # keep alive during the call.
else:
if val is None:
val = default
vals.append(val)
full_fmt += fmt
return struct.pack(*(full_fmt,) + tuple(vals))
class TBBUTTON(WIN32STRUCT):
_struct_items_ = [
("iBitmap", "i", 0),
("idCommand", "i", 0),
("fsState", "B", 0),
("fsStyle", "B", 0),
("bReserved", "H", 0),
("dwData", "I", 0),
("iString", "z", None),
]
class Stub:
"""
this class serves as a method stub,
outputting debug info whenever the object
is being called.
"""
def __init__(self, name):
self.name = name
def __call__(self, *args):
print("STUB: ", self.name, args)
class IEToolbarCtrl:
"""
a tiny wrapper for our winapi-based
toolbar control implementation.
"""
def __init__(self, hwndparent):
styles = (
win32con.WS_CHILD
| win32con.WS_VISIBLE
| win32con.WS_CLIPSIBLINGS
| win32con.WS_CLIPCHILDREN
| commctrl.TBSTYLE_LIST
| commctrl.TBSTYLE_FLAT
| commctrl.TBSTYLE_TRANSPARENT
| commctrl.CCS_TOP
| commctrl.CCS_NODIVIDER
| commctrl.CCS_NORESIZE
| commctrl.CCS_NOPARENTALIGN
)
self.hwnd = win32gui.CreateWindow(
"ToolbarWindow32",
None,
styles,
0,
0,
100,
100,
hwndparent,
0,
win32gui.dllhandle,
None,
)
win32gui.SendMessage(self.hwnd, commctrl.TB_BUTTONSTRUCTSIZE, 20, 0)
def ShowWindow(self, mode):
win32gui.ShowWindow(self.hwnd, mode)
def AddButtons(self, *buttons):
tbbuttons = ""
for button in buttons:
tbbuttons += button.toparam()
return win32gui.SendMessage(
self.hwnd, commctrl.TB_ADDBUTTONS, len(buttons), tbbuttons
)
def GetSafeHwnd(self):
return self.hwnd
class IEToolbar:
"""
The actual COM server class
"""
_com_interfaces_ = _ietoolbar_com_interfaces_
_public_methods_ = _ietoolbar_methods_
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
# if you copy and modify this example, be sure to change the clsid below
_reg_clsid_ = "{F21202A2-959A-4149-B1C3-68B9013F3335}"
_reg_progid_ = "PyWin32.IEToolbar"
_reg_desc_ = "PyWin32 IE Toolbar"
def __init__(self):
# put stubs for non-implemented methods
for method in self._public_methods_:
if not hasattr(self, method):
print("providing default stub for %s" % method)
setattr(self, method, Stub(method))
def GetWindow(self):
return self.toolbar.GetSafeHwnd()
def Load(self, stream):
# called when the toolbar is loaded
pass
def Save(self, pStream, fClearDirty):
# called when the toolbar shall save its information
pass
def CloseDW(self, dwReserved):
del self.toolbar
def ShowDW(self, bShow):
if bShow:
self.toolbar.ShowWindow(win32con.SW_SHOW)
else:
self.toolbar.ShowWindow(win32con.SW_HIDE)
def on_first_button(self):
print("first!")
self.webbrowser.Navigate2("http://starship.python.net/crew/mhammond/")
def on_second_button(self):
print("second!")
def on_third_button(self):
print("third!")
def toolbar_command_handler(self, args):
hwnd, message, wparam, lparam, time, point = args
if lparam == self.toolbar.GetSafeHwnd():
self._command_map[wparam]()
def SetSite(self, unknown):
if unknown:
# retrieve the parent window interface for this site
olewindow = unknown.QueryInterface(pythoncom.IID_IOleWindow)
# ask the window for its handle
hwndparent = olewindow.GetWindow()
# first get a command target
cmdtarget = unknown.QueryInterface(axcontrol.IID_IOleCommandTarget)
# then travel over to a service provider
serviceprovider = cmdtarget.QueryInterface(pythoncom.IID_IServiceProvider)
# finally ask for the internet explorer application, returned as a dispatch object
self.webbrowser = win32com.client.Dispatch(
serviceprovider.QueryService(
"{0002DF05-0000-0000-C000-000000000046}", pythoncom.IID_IDispatch
)
)
# now create and set up the toolbar
self.toolbar = IEToolbarCtrl(hwndparent)
buttons = [
("Visit PyWin32 Homepage", self.on_first_button),
("Another Button", self.on_second_button),
("Yet Another Button", self.on_third_button),
]
self._command_map = {}
# wrap our parent window so we can hook message handlers
window = win32ui.CreateWindowFromHandle(hwndparent)
# add the buttons
for i in range(len(buttons)):
button = TBBUTTON()
name, func = buttons[i]
id = 0x4444 + i
button.iBitmap = -2
button.idCommand = id
button.fsState = commctrl.TBSTATE_ENABLED
button.fsStyle = commctrl.TBSTYLE_BUTTON
button.iString = name
self._command_map[0x4444 + i] = func
self.toolbar.AddButtons(button)
window.HookMessage(self.toolbar_command_handler, win32con.WM_COMMAND)
else:
# lose all references
self.webbrowser = None
def GetClassID(self):
return self._reg_clsid_
def GetBandInfo(self, dwBandId, dwViewMode, dwMask):
ptMinSize = (0, 24)
ptMaxSize = (2000, 24)
ptIntegral = (0, 0)
ptActual = (2000, 24)
wszTitle = "PyWin32 IE Toolbar"
dwModeFlags = DBIMF_VARIABLEHEIGHT
crBkgnd = 0
return (
ptMinSize,
ptMaxSize,
ptIntegral,
ptActual,
wszTitle,
dwModeFlags,
crBkgnd,
)
# used for HKLM install
def DllInstall(bInstall, cmdLine):
comclass = IEToolbar
# register plugin
def DllRegisterServer():
comclass = IEToolbar
# register toolbar with IE
try:
print("Trying to register Toolbar.\n")
hkey = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar"
)
subKey = winreg.SetValueEx(
hkey, comclass._reg_clsid_, 0, winreg.REG_BINARY, "\0"
)
except WindowsError:
print(
"Couldn't set registry value.\nhkey: %d\tCLSID: %s\n"
% (hkey, comclass._reg_clsid_)
)
else:
print(
"Set registry value.\nhkey: %d\tCLSID: %s\n" % (hkey, comclass._reg_clsid_)
)
# TODO: implement reg settings for standard toolbar button
# unregister plugin
def DllUnregisterServer():
comclass = IEToolbar
# unregister toolbar from internet explorer
try:
print("Trying to unregister Toolbar.\n")
hkey = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar"
)
winreg.DeleteValue(hkey, comclass._reg_clsid_)
except WindowsError:
print(
"Couldn't delete registry value.\nhkey: %d\tCLSID: %s\n"
% (hkey, comclass._reg_clsid_)
)
else:
print("Deleting reg key succeeded.\n")
# entry point
if __name__ == "__main__":
import win32com.server.register
win32com.server.register.UseCommandLine(IEToolbar)
# parse actual command line option
if "--unregister" in sys.argv:
DllUnregisterServer()
else:
DllRegisterServer()
else:
# import trace utility for remote debugging
import win32traceutil

View file

@ -0,0 +1,138 @@
# A demo plugin for Microsoft Outlook (NOT Outlook Express)
#
# This addin simply adds a new button to the main Outlook toolbar,
# and displays a message box when clicked. Thus, it demonstrates
# how to plug in to Outlook itself, and hook outlook events.
#
# Additionally, each time a new message arrives in the Inbox, a message
# is printed with the subject of the message.
#
# To register the addin, simply execute:
# outlookAddin.py
# This will install the COM server, and write the necessary
# AddIn key to Outlook
#
# To unregister completely:
# outlookAddin.py --unregister
#
# To debug, execute:
# outlookAddin.py --debug
#
# Then open Pythonwin, and select "Tools->Trace Collector Debugging Tool"
# Restart Outlook, and you should see some output generated.
#
# NOTE: If the AddIn fails with an error, Outlook will re-register
# the addin to not automatically load next time Outlook starts. To
# correct this, simply re-register the addin (see above)
import sys
import pythoncom
from win32com import universal
from win32com.client import DispatchWithEvents, constants, gencache
from win32com.server.exception import COMException
# Support for COM objects we use.
gencache.EnsureModule(
"{00062FFF-0000-0000-C000-000000000046}", 0, 9, 0, bForDemand=True
) # Outlook 9
gencache.EnsureModule(
"{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}", 0, 2, 1, bForDemand=True
) # Office 9
# The TLB defining the interfaces we implement
universal.RegisterInterfaces(
"{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}", 0, 1, 0, ["_IDTExtensibility2"]
)
class ButtonEvent:
def OnClick(self, button, cancel):
import win32ui # Possible, but not necessary, to use a Pythonwin GUI
win32ui.MessageBox("Hello from Python")
return cancel
class FolderEvent:
def OnItemAdd(self, item):
try:
print("An item was added to the inbox with subject:", item.Subject)
except AttributeError:
print(
"An item was added to the inbox, but it has no subject! - ", repr(item)
)
class OutlookAddin:
_com_interfaces_ = ["_IDTExtensibility2"]
_public_methods_ = []
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
_reg_clsid_ = "{0F47D9F3-598B-4d24-B7E3-92AC15ED27E2}"
_reg_progid_ = "Python.Test.OutlookAddin"
_reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
def OnConnection(self, application, connectMode, addin, custom):
print("OnConnection", application, connectMode, addin, custom)
# ActiveExplorer may be none when started without a UI (eg, WinCE synchronisation)
activeExplorer = application.ActiveExplorer()
if activeExplorer is not None:
bars = activeExplorer.CommandBars
toolbar = bars.Item("Standard")
item = toolbar.Controls.Add(Type=constants.msoControlButton, Temporary=True)
# Hook events for the item
item = self.toolbarButton = DispatchWithEvents(item, ButtonEvent)
item.Caption = "Python"
item.TooltipText = "Click for Python"
item.Enabled = True
# And now, for the sake of demonstration, setup a hook for all new messages
inbox = application.Session.GetDefaultFolder(constants.olFolderInbox)
self.inboxItems = DispatchWithEvents(inbox.Items, FolderEvent)
def OnDisconnection(self, mode, custom):
print("OnDisconnection")
def OnAddInsUpdate(self, custom):
print("OnAddInsUpdate", custom)
def OnStartupComplete(self, custom):
print("OnStartupComplete", custom)
def OnBeginShutdown(self, custom):
print("OnBeginShutdown", custom)
def RegisterAddin(klass):
import winreg
key = winreg.CreateKey(
winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Outlook\\Addins"
)
subkey = winreg.CreateKey(key, klass._reg_progid_)
winreg.SetValueEx(subkey, "CommandLineSafe", 0, winreg.REG_DWORD, 0)
winreg.SetValueEx(subkey, "LoadBehavior", 0, winreg.REG_DWORD, 3)
winreg.SetValueEx(subkey, "Description", 0, winreg.REG_SZ, klass._reg_progid_)
winreg.SetValueEx(subkey, "FriendlyName", 0, winreg.REG_SZ, klass._reg_progid_)
def UnregisterAddin(klass):
import winreg
try:
winreg.DeleteKey(
winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Office\\Outlook\\Addins\\" + klass._reg_progid_,
)
except WindowsError:
pass
if __name__ == "__main__":
import win32com.server.register
win32com.server.register.UseCommandLine(OutlookAddin)
if "--unregister" in sys.argv:
UnregisterAddin(OutlookAddin)
else:
RegisterAddin(OutlookAddin)

View file

@ -0,0 +1,77 @@
import pythoncom
from win32com.server import exception, util
VT_EMPTY = pythoncom.VT_EMPTY
class Bag:
_public_methods_ = ["Read", "Write"]
_com_interfaces_ = [pythoncom.IID_IPropertyBag]
def __init__(self):
self.data = {}
def Read(self, propName, varType, errorLog):
print("read: name=", propName, "type=", varType)
if propName not in self.data:
if errorLog:
hr = 0x80070057
exc = pythoncom.com_error(0, "Bag.Read", "no such item", None, 0, hr)
errorLog.AddError(propName, exc)
raise exception.Exception(scode=hr)
return self.data[propName]
def Write(self, propName, value):
print("write: name=", propName, "value=", value)
self.data[propName] = value
class Target:
_public_methods_ = ["GetClassID", "InitNew", "Load", "Save"]
_com_interfaces_ = [pythoncom.IID_IPersist, pythoncom.IID_IPersistPropertyBag]
def GetClassID(self):
raise exception.Exception(scode=0x80004005) # E_FAIL
def InitNew(self):
pass
def Load(self, bag, log):
print(bag.Read("prop1", VT_EMPTY, log))
print(bag.Read("prop2", VT_EMPTY, log))
try:
print(bag.Read("prop3", VT_EMPTY, log))
except exception.Exception:
pass
def Save(self, bag, clearDirty, saveAllProps):
bag.Write("prop1", "prop1.hello")
bag.Write("prop2", "prop2.there")
class Log:
_public_methods_ = ["AddError"]
_com_interfaces_ = [pythoncom.IID_IErrorLog]
def AddError(self, propName, excepInfo):
print("error: propName=", propName, "error=", excepInfo)
def test():
bag = Bag()
target = Target()
log = Log()
target.Save(bag, 1, 1)
target.Load(bag, log)
comBag = util.wrap(bag, pythoncom.IID_IPropertyBag)
comTarget = util.wrap(target, pythoncom.IID_IPersistPropertyBag)
comLog = util.wrap(log, pythoncom.IID_IErrorLog)
comTarget.Save(comBag, 1, 1)
comTarget.Load(comBag, comLog)
if __name__ == "__main__":
test()

View file

@ -0,0 +1,766 @@
/* PythonCOM.h
Main header for Python COM support.
This file is involved mainly with client side COM support for
Python.
Most COM work put together by Greg Stein and Mark Hammond, with a
few others starting to come out of the closet.
--------------------------------------------------------------------
Thread State Rules
------------------
These rules apply to PythonCOM in general, and not just to
the client side.
The rules are quite simple, but it is critical they be followed.
In general, errors here will be picked up quite quickly, as Python
will raise a Fatal Error. However, the Release() issue in particular
may keep a number of problems well hidden.
Interfaces:
-----------
Before making ANY call out to COM, you MUST release the Python lock.
This is true to ANY call whatsoever, including the COM call in question,
but also any calls to "->Release();"
This is normally achieved with the calls
PY_INTERFACE_PRECALL and PY_INTERFACE_POSTCALL, which release
and acquire the Python lock.
Gateways:
---------
Before doing anything related to Python, gateways MUST acquire the
Python lock, and must release it before returning.
This is normally achieved with PY_GATEWAY_METHOD at the top of a
gateway method. This macro resolves to a class, which automatically does
the right thing.
Release:
--------
As mentioned above for Interfaces, EVERY call to Release() must be done
with the Python lock released. This is expanded here.
This is very important, but an error may not be noticed. The problem will
only be seen when the Release() is on a Python object and the Release() is the
final one for the object. In this case, the Python object will attempt to
acquire the Python lock before destroying itself, and Python will raise a
fatal error.
In many many cases, you will not notice this error, but someday, someone will
implement the other side in Python, and suddenly FatalErrors will start
appearing. Make sure you get this right.
Eg, this code is correct:
PY_INTERFACE_PRECALL;
pSomeObj->SomeFunction(pSomeOtherObject);
pSomeOtherObject->Release();
PY_INTERFACE_POSTCALL;
However, this code is WRONG, but will RARELY FAIL.
PY_INTERFACE_PRECALL;
pSomeObj->SomeFunction(pSomeOtherObject);
PY_INTERFACE_POSTCALL;
pSomeOtherObject->Release();
--------------------------------------------------------------------
*/
#ifndef __PYTHONCOM_H__
#define __PYTHONCOM_H__
// #define _DEBUG_LIFETIMES // Trace COM object lifetimes.
#ifdef FREEZE_PYTHONCOM
/* The pythoncom module is being included in a frozen .EXE/.DLL */
#define PYCOM_EXPORT
#else
#ifdef BUILD_PYTHONCOM
/* We are building pythoncomxx.dll */
#define PYCOM_EXPORT __declspec(dllexport)
#else
/* This module uses pythoncomxx.dll */
#define PYCOM_EXPORT __declspec(dllimport)
#ifndef _DEBUG
#pragma comment(lib, "pythoncom.lib")
#else
#pragma comment(lib, "pythoncom_d.lib")
#endif
#endif
#endif
#ifdef MS_WINCE
// List of interfaces not supported by CE.
#define NO_PYCOM_IDISPATCHEX
#define NO_PYCOM_IPROVIDECLASSINFO
#define NO_PYCOM_IENUMGUID
#define NO_PYCOM_IENUMCATEGORYINFO
#define NO_PYCOM_ICATINFORMATION
#define NO_PYCOM_ICATREGISTER
#define NO_PYCOM_ISERVICEPROVIDER
#define NO_PYCOM_IPROPERTYSTORAGE
#define NO_PYCOM_IPROPERTYSETSTORAGE
#define NO_PYCOM_ENUMSTATPROPSTG
#include "ocidl.h"
#include "oleauto.h"
#endif // MS_WINCE
#ifdef __MINGW32__
// Special Mingw32 considerations.
#define NO_PYCOM_ENUMSTATPROPSTG
#define __try try
#define __except catch
#include <olectl.h>
#endif // __MINGW32__
#include <PyWinTypes.h> // Standard Win32 Types
#ifndef NO_PYCOM_IDISPATCHEX
#include <dispex.h> // New header for IDispatchEx interface.
#endif // NO_PYCOM_IDISPATCHEX
#if defined(MAINWIN)
// Mainwin seems to have 1/2 the VT_RECORD infrastructure in place
#if !defined(VT_RECORD)
#define VT_RECORD 36
#define V_RECORDINFO(X) ((X)->brecVal.pRecInfo)
#define V_RECORD(X) ((X)->brecVal.pvRecord)
#else
#pragma message( \
"MAINWIN appears to have grown correct VT_RECORD " \
"support. Please update PythonCOM.h accordingly")
#endif // VT_RECORD
#endif // MAINWIN
class PyIUnknown;
// To make life interesting/complicated, I use C++ classes for
// all Python objects. The main advantage is that I can derive
// a PyIDispatch object from a PyIUnknown, etc. This provides a
// clean C++ interface, and "automatically" provides all base
// Python methods to "derived" Python types.
//
// Main disadvantage is that any extension DLLs will need to include
// these headers, and link with this .lib
//
// Base class for (most of) the type objects.
class PYCOM_EXPORT PyComTypeObject : public PyTypeObject {
public:
PyComTypeObject(const char *name, PyComTypeObject *pBaseType, Py_ssize_t typeSize, struct PyMethodDef *methodList,
PyIUnknown *(*thector)(IUnknown *));
~PyComTypeObject();
// is the given object an interface type object? (e.g. PyIUnknown)
static BOOL is_interface_type(PyObject *ob);
public:
PyIUnknown *(*ctor)(IUnknown *);
};
// A type used for interfaces that can automatically provide enumerators
// (ie, they themselves aren't enumerable, but do have a suitable default
// method that returns a PyIEnum object
class PYCOM_EXPORT PyComEnumProviderTypeObject : public PyComTypeObject {
public:
PyComEnumProviderTypeObject(const char *name, PyComTypeObject *pBaseType, Py_ssize_t typeSize,
struct PyMethodDef *methodList, PyIUnknown *(*thector)(IUnknown *),
const char *enum_method_name);
static PyObject *iter(PyObject *self);
const char *enum_method_name;
};
// A type used for PyIEnum interfaces
class PYCOM_EXPORT PyComEnumTypeObject : public PyComTypeObject {
public:
static PyObject *iter(PyObject *self);
static PyObject *iternext(PyObject *self);
PyComEnumTypeObject(const char *name, PyComTypeObject *pBaseType, Py_ssize_t typeSize, struct PyMethodDef *methodList,
PyIUnknown *(*thector)(IUnknown *));
};
// Very very base class - not COM specific - Should exist in the
// Python core somewhere, IMO.
class PYCOM_EXPORT PyIBase : public PyObject {
public:
// virtuals for Python support
virtual PyObject *getattr(char *name);
virtual int setattr(char *name, PyObject *v);
virtual PyObject *repr();
virtual int compare(PyObject *other)
{
if (this == other)
return 0;
if (this < other)
return -1;
return 1;
}
// These iter are a little special, in that returning NULL means
// use the implementation in the type
virtual PyObject *iter() { return NULL; }
virtual PyObject *iternext() { return NULL; }
protected:
PyIBase();
virtual ~PyIBase();
public:
static BOOL is_object(PyObject *, PyComTypeObject *which);
BOOL is_object(PyComTypeObject *which);
static void dealloc(PyObject *ob);
static PyObject *repr(PyObject *ob);
static PyObject *getattro(PyObject *self, PyObject *name);
static int setattro(PyObject *op, PyObject *obname, PyObject *v);
static int cmp(PyObject *ob1, PyObject *ob2);
static PyObject *richcmp(PyObject *ob1, PyObject *ob2, int op);
};
/* Special Type objects */
extern PYCOM_EXPORT PyTypeObject PyOleEmptyType; // equivalent to VT_EMPTY
extern PYCOM_EXPORT PyTypeObject PyOleMissingType; // special Python handling.
extern PYCOM_EXPORT PyTypeObject PyOleArgNotFoundType; // special VT_ERROR value
extern PYCOM_EXPORT PyTypeObject PyOleNothingType; // special VT_ERROR value
// ALL of these set an appropriate Python error on bad return.
// Given a Python object that is a registered COM type, return a given
// interface pointer on its underlying object, with a new reference added.
PYCOM_EXPORT BOOL PyCom_InterfaceFromPyObject(PyObject *ob, REFIID iid, LPVOID *ppv, BOOL bNoneOK = TRUE);
// As above, but allows instance with "_oleobj_" attribute.
PYCOM_EXPORT BOOL PyCom_InterfaceFromPyInstanceOrObject(PyObject *ob, REFIID iid, LPVOID *ppv, BOOL bNoneOK = TRUE);
// Release an arbitary COM pointer.
// NOTE: the PRECALL/POSTCALL stuff is probably not strictly necessary
// since the PyGILSTATE stuff has been in place (and even then, it only
// mattered when it was the last Release() on a Python implemented object)
#define PYCOM_RELEASE(pUnk) \
{ \
if (pUnk) { \
PY_INTERFACE_PRECALL; \
(pUnk)->Release(); \
PY_INTERFACE_POSTCALL; \
} \
}
// Given an IUnknown and an Interface ID, create and return an object
// of the appropriate type. eg IID_Unknown->PyIUnknown,
// IID_IDispatch->PyIDispatch, etc.
// Uses a map that external extension DLLs can populate with their IID/type.
// Under the principal of least surprise, this will return Py_None is punk is NULL.
// Otherwise, a valid PyI*, but with NULL m_obj (and therefore totally useless)
// object would be created.
// BOOL bAddRef indicates if a COM reference count should be added to the IUnknown.
// This depends purely on the context in which it is called. If the IUnknown is obtained
// from a function that creates a new ref (eg, CoCreateInstance()) then you should use
// FALSE. If you receive the pointer as (eg) a param to a gateway function, then
// you normally need to pass TRUE, as this is truly a new reference.
// *** ALWAYS take the time to get this right. ***
PYCOM_EXPORT PyObject *PyCom_PyObjectFromIUnknown(IUnknown *punk, REFIID riid, BOOL bAddRef = FALSE);
// VARIANT <-> PyObject conversion utilities.
PYCOM_EXPORT BOOL PyCom_VariantFromPyObject(PyObject *obj, VARIANT *var);
PYCOM_EXPORT PyObject *PyCom_PyObjectFromVariant(const VARIANT *var);
// PROPVARIANT
PYCOM_EXPORT PyObject *PyObject_FromPROPVARIANT(PROPVARIANT *pVar);
PYCOM_EXPORT PyObject *PyObject_FromPROPVARIANTs(PROPVARIANT *pVars, ULONG cVars);
PYCOM_EXPORT BOOL PyObject_AsPROPVARIANT(PyObject *ob, PROPVARIANT *pVar);
// Other conversion helpers...
PYCOM_EXPORT PyObject *PyCom_PyObjectFromSTATSTG(STATSTG *pStat);
PYCOM_EXPORT BOOL PyCom_PyObjectAsSTATSTG(PyObject *ob, STATSTG *pStat, DWORD flags = 0);
PYCOM_EXPORT BOOL PyCom_SAFEARRAYFromPyObject(PyObject *obj, SAFEARRAY **ppSA, VARENUM vt = VT_VARIANT);
PYCOM_EXPORT PyObject *PyCom_PyObjectFromSAFEARRAY(SAFEARRAY *psa, VARENUM vt = VT_VARIANT);
#ifndef NO_PYCOM_STGOPTIONS
PYCOM_EXPORT BOOL PyCom_PyObjectAsSTGOPTIONS(PyObject *obstgoptions, STGOPTIONS **ppstgoptions, TmpWCHAR *tmpw_shelve);
#endif
PYCOM_EXPORT PyObject *PyCom_PyObjectFromSTATPROPSETSTG(STATPROPSETSTG *pStat);
PYCOM_EXPORT BOOL PyCom_PyObjectAsSTATPROPSETSTG(PyObject *, STATPROPSETSTG *);
// Currency support.
PYCOM_EXPORT PyObject *PyObject_FromCurrency(CURRENCY &cy);
PYCOM_EXPORT BOOL PyObject_AsCurrency(PyObject *ob, CURRENCY *pcy);
// OLEMENUGROUPWIDTHS are used by axcontrol, shell, etc
PYCOM_EXPORT BOOL PyObject_AsOLEMENUGROUPWIDTHS(PyObject *oblpMenuWidths, OLEMENUGROUPWIDTHS *pWidths);
PYCOM_EXPORT PyObject *PyObject_FromOLEMENUGROUPWIDTHS(const OLEMENUGROUPWIDTHS *pWidths);
/* Functions for Initializing COM, and also letting the core know about it!
*/
PYCOM_EXPORT HRESULT PyCom_CoInitializeEx(LPVOID reserved, DWORD dwInit);
PYCOM_EXPORT HRESULT PyCom_CoInitialize(LPVOID reserved);
PYCOM_EXPORT void PyCom_CoUninitialize();
///////////////////////////////////////////////////////////////////
// Error related functions
// Client related functions - generally called by interfaces before
// they return NULL back to Python to indicate the error.
// All these functions return NULL so interfaces can generally
// just "return PyCom_BuildPyException(hr, punk, IID_IWhatever)"
// Uses the HRESULT, and IErrorInfo interfaces if available to
// create and set a pythoncom.com_error.
PYCOM_EXPORT PyObject *PyCom_BuildPyException(HRESULT hr, IUnknown *pUnk = NULL, REFIID iid = IID_NULL);
// Uses the HRESULT and an EXCEPINFO structure to create and
// set a pythoncom.com_error.
PYCOM_EXPORT PyObject *PyCom_BuildPyExceptionFromEXCEPINFO(HRESULT hr, EXCEPINFO *pexcepInfo, UINT nArgErr = (UINT)-1);
// Sets a pythoncom.internal_error - no one should ever see these!
PYCOM_EXPORT PyObject *PyCom_BuildInternalPyException(char *msg);
// Log an error to a Python logger object if one can be found, or
// to stderr if no log available.
// If logProvider is not NULL, we will call a "_GetLogger_()" method on it.
// If logProvider is NULL, we attempt to fetch "win32com.logger".
// If they do not exist, return None, or raise an error fetching them
// (or even writing to them once fetched), the message still goes to stderr.
// NOTE: By default, win32com does *not* provide a logger, so default is that
// all errors are written to stdout.
// This will *not* write a record if a COM Server error is current.
PYCOM_EXPORT void PyCom_LoggerNonServerException(PyObject *logProvider, const WCHAR *fmt, ...);
// Write an error record, including exception. This will write an error
// record even if a COM server error is current.
PYCOM_EXPORT void PyCom_LoggerException(PyObject *logProvider, const WCHAR *fmt, ...);
// Write a warning record - in general this does *not* mean a call failed, but
// still is something in the programmers control that they should change.
// XXX - if an exception is pending when this is called, the traceback will
// also be written. This is undesirable and will be changed should this
// start being a problem.
PYCOM_EXPORT void PyCom_LoggerWarning(PyObject *logProvider, const WCHAR *fmt, ...);
// Server related error functions
// These are supplied so that any Python errors we detect can be
// converted into COM error information. The HRESULT returned should
// be returned by the COM function, and these functions also set the
// IErrorInfo interfaces, so the caller can extract more detailed
// information about the Python exception.
// Set a COM exception, logging the exception if not an explicitly raised 'server' exception
PYCOM_EXPORT HRESULT PyCom_SetAndLogCOMErrorFromPyException(const char *methodName, REFIID riid /* = IID_NULL */);
PYCOM_EXPORT HRESULT PyCom_SetAndLogCOMErrorFromPyExceptionEx(PyObject *provider, const char *methodName,
REFIID riid /* = IID_NULL */);
// Used in gateways to SetErrorInfo() with a simple HRESULT, then return it.
// The description is generally only useful for debugging purposes,
// and if you are debugging via a server that supports IErrorInfo (like Python :-)
// NOTE: this function is usuable from outside the Python context
PYCOM_EXPORT HRESULT PyCom_SetCOMErrorFromSimple(HRESULT hr, REFIID riid = IID_NULL, const WCHAR *description = NULL);
// Used in gateways to check if an IEnum*'s Next() or Clone() method worked.
PYCOM_EXPORT HRESULT PyCom_CheckIEnumNextResult(HRESULT hr, REFIID riid);
// Used in gateways when an enumerator expected a sequence but didn't get it.
PYCOM_EXPORT HRESULT PyCom_HandleIEnumNoSequence(REFIID riid);
// Used in gateways to SetErrorInfo() the current Python exception, and
// (assuming not a server error explicitly raised) also logs an error
// to stdout/win32com.logger.
// NOTE: this function assumes GIL held
PYCOM_EXPORT HRESULT PyCom_SetCOMErrorFromPyException(REFIID riid = IID_NULL);
// A couple of EXCEPINFO helpers - could be private to IDispatch
// if it wasnt for the AXScript support (and ITypeInfo if we get around to that :-)
// These functions do not set any error states to either Python or
// COM - they simply convert to/from PyObjects and EXCEPINFOs
// Use the current Python exception to fill an EXCEPINFO structure.
PYCOM_EXPORT void PyCom_ExcepInfoFromPyException(EXCEPINFO *pExcepInfo);
// Fill in an EXCEPINFO structure from a Python instance or tuple object.
// (ie, similar to the above, except the Python exception object is specified,
// rather than using the "current"
PYCOM_EXPORT BOOL PyCom_ExcepInfoFromPyObject(PyObject *obExcepInfo, EXCEPINFO *pexcepInfo, HRESULT *phresult = NULL);
// Create a Python object holding the exception information. The exception
// information is *not* freed by this function. Python exceptions are
// raised and NULL is returned if an error occurs.
PYCOM_EXPORT PyObject *PyCom_PyObjectFromExcepInfo(const EXCEPINFO *pexcepInfo);
///////////////////////////////////////////////////////////////////
//
// External C++ helpers - these helpers are for other DLLs which
// may need similar functionality, but dont want to duplicate all
// This helper is for an application that has an IDispatch, and COM arguments
// and wants to call a Python function. It is assumed the caller can map the IDispatch
// to a Python object, so the Python handler is passed.
// Args:
// handler : A Python callable object.
// dispparms : the COM arguments.
// pVarResult : The variant for the return value of the Python call.
// pexcepinfo : Exception info the helper may fill out.
// puArgErr : Argument error the helper may fill out on exception
// addnArgs : Any additional arguments to the Python function. May be NULL.
// If addnArgs is NULL, then it is assumed the Python call should be native -
// ie, the COM args are packed as normal Python args to the call.
// If addnArgs is NOT NULL, it is assumed the Python function itself is
// a helper. This Python function will be called with 2 arguments - both
// tuples - first one is the COM args, second is the addn args.
PYCOM_EXPORT BOOL PyCom_MakeOlePythonCall(PyObject *handler, DISPPARAMS FAR *params, VARIANT FAR *pVarResult,
EXCEPINFO FAR *pexcepinfo, UINT FAR *puArgErr, PyObject *addnlArgs);
/////////////////////////////////////////////////////////////////////////////
// Various special purpose singletons
class PYCOM_EXPORT PyOleEmpty : public PyObject {
public:
PyOleEmpty();
};
class PYCOM_EXPORT PyOleMissing : public PyObject {
public:
PyOleMissing();
};
class PYCOM_EXPORT PyOleArgNotFound : public PyObject {
public:
PyOleArgNotFound();
};
class PYCOM_EXPORT PyOleNothing : public PyObject {
public:
PyOleNothing();
};
// We need to dynamically create C++ Python objects
// These helpers allow each type object to create it.
#define MAKE_PYCOM_CTOR(classname) \
static PyIUnknown *PyObConstruct(IUnknown *pInitObj) { return new classname(pInitObj); }
#define MAKE_PYCOM_CTOR_ERRORINFO(classname, iid) \
static PyIUnknown *PyObConstruct(IUnknown *pInitObj) { return new classname(pInitObj); } \
static PyObject *SetPythonCOMError(PyObject *self, HRESULT hr) \
{ \
return PyCom_BuildPyException(hr, GetI(self), iid); \
}
#define GET_PYCOM_CTOR(classname) classname::PyObConstruct
// Macros that interfaces should use. PY_INTERFACE_METHOD at the top of the method
// The other 2 wrap directly around the underlying method call.
#define PY_INTERFACE_METHOD
// Identical to Py_BEGIN_ALLOW_THREADS except no { !!!
#define PY_INTERFACE_PRECALL PyThreadState *_save = PyEval_SaveThread();
#define PY_INTERFACE_POSTCALL PyEval_RestoreThread(_save);
/////////////////////////////////////////////////////////////////////////////
// class PyIUnknown
class PYCOM_EXPORT PyIUnknown : public PyIBase {
public:
MAKE_PYCOM_CTOR(PyIUnknown);
virtual PyObject *repr();
virtual int compare(PyObject *other);
static IUnknown *GetI(PyObject *self);
IUnknown *m_obj;
static char *szErrMsgObjectReleased;
static void SafeRelease(PyIUnknown *ob);
static PyComTypeObject type;
// The Python methods
static PyObject *QueryInterface(PyObject *self, PyObject *args);
static PyObject *SafeRelease(PyObject *self, PyObject *args);
protected:
PyIUnknown(IUnknown *punk);
~PyIUnknown();
};
/////////////////////////////////////////////////////////////////////////////
// class PyIDispatch
class PYCOM_EXPORT PyIDispatch : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyIDispatch);
static IDispatch *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
static PyObject *Invoke(PyObject *self, PyObject *args);
static PyObject *InvokeTypes(PyObject *self, PyObject *args);
static PyObject *GetIDsOfNames(PyObject *self, PyObject *args);
static PyObject *GetTypeInfo(PyObject *self, PyObject *args);
static PyObject *GetTypeInfoCount(PyObject *self, PyObject *args);
protected:
PyIDispatch(IUnknown *pdisp);
~PyIDispatch();
};
#ifndef NO_PYCOM_IDISPATCHEX
/////////////////////////////////////////////////////////////////////////////
// class PyIDispatchEx
class PYCOM_EXPORT PyIDispatchEx : public PyIDispatch {
public:
MAKE_PYCOM_CTOR_ERRORINFO(PyIDispatchEx, IID_IDispatchEx);
static IDispatchEx *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
static PyObject *GetDispID(PyObject *self, PyObject *args);
static PyObject *InvokeEx(PyObject *self, PyObject *args);
static PyObject *DeleteMemberByName(PyObject *self, PyObject *args);
static PyObject *DeleteMemberByDispID(PyObject *self, PyObject *args);
static PyObject *GetMemberProperties(PyObject *self, PyObject *args);
static PyObject *GetMemberName(PyObject *self, PyObject *args);
static PyObject *GetNextDispID(PyObject *self, PyObject *args);
protected:
PyIDispatchEx(IUnknown *pdisp);
~PyIDispatchEx();
};
#endif // NO_PYCOM_IDISPATCHEX
/////////////////////////////////////////////////////////////////////////////
// class PyIClassFactory
class PYCOM_EXPORT PyIClassFactory : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyIClassFactory);
static IClassFactory *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
static PyObject *CreateInstance(PyObject *self, PyObject *args);
static PyObject *LockServer(PyObject *self, PyObject *args);
protected:
PyIClassFactory(IUnknown *pdisp);
~PyIClassFactory();
};
#ifndef NO_PYCOM_IPROVIDECLASSINFO
/////////////////////////////////////////////////////////////////////////////
// class PyIProvideTypeInfo
class PYCOM_EXPORT PyIProvideClassInfo : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyIProvideClassInfo);
static IProvideClassInfo *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
static PyObject *GetClassInfo(PyObject *self, PyObject *args);
protected:
PyIProvideClassInfo(IUnknown *pdisp);
~PyIProvideClassInfo();
};
class PYCOM_EXPORT PyIProvideClassInfo2 : public PyIProvideClassInfo {
public:
MAKE_PYCOM_CTOR(PyIProvideClassInfo2);
static IProvideClassInfo2 *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
static PyObject *GetGUID(PyObject *self, PyObject *args);
protected:
PyIProvideClassInfo2(IUnknown *pdisp);
~PyIProvideClassInfo2();
};
#endif // NO_PYCOM_IPROVIDECLASSINFO
/////////////////////////////////////////////////////////////////////////////
// class PyITypeInfo
class PYCOM_EXPORT PyITypeInfo : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyITypeInfo);
static PyComTypeObject type;
static ITypeInfo *GetI(PyObject *self);
PyObject *GetContainingTypeLib();
PyObject *GetDocumentation(MEMBERID);
PyObject *GetRefTypeInfo(HREFTYPE href);
PyObject *GetRefTypeOfImplType(int index);
PyObject *GetFuncDesc(int pos);
PyObject *GetIDsOfNames(OLECHAR FAR *FAR *, int);
PyObject *GetNames(MEMBERID);
PyObject *GetTypeAttr();
PyObject *GetVarDesc(int pos);
PyObject *GetImplTypeFlags(int index);
PyObject *GetTypeComp();
protected:
PyITypeInfo(IUnknown *);
~PyITypeInfo();
};
/////////////////////////////////////////////////////////////////////////////
// class PyITypeComp
class PYCOM_EXPORT PyITypeComp : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyITypeComp);
static PyComTypeObject type;
static ITypeComp *GetI(PyObject *self);
PyObject *Bind(OLECHAR *szName, unsigned short wflags);
PyObject *BindType(OLECHAR *szName);
protected:
PyITypeComp(IUnknown *);
~PyITypeComp();
};
/////////////////////////////////////////////////////////////////////////////
// class CPyTypeLib
class PYCOM_EXPORT PyITypeLib : public PyIUnknown {
public:
MAKE_PYCOM_CTOR(PyITypeLib);
static PyComTypeObject type;
static ITypeLib *GetI(PyObject *self);
PyObject *GetLibAttr();
PyObject *GetDocumentation(int pos);
PyObject *GetTypeInfo(int pos);
PyObject *GetTypeInfoCount();
PyObject *GetTypeInfoOfGuid(REFGUID guid);
PyObject *GetTypeInfoType(int pos);
PyObject *GetTypeComp();
protected:
PyITypeLib(IUnknown *);
~PyITypeLib();
};
/////////////////////////////////////////////////////////////////////////////
// class PyIConnectionPoint
class PYCOM_EXPORT PyIConnectionPoint : public PyIUnknown {
public:
MAKE_PYCOM_CTOR_ERRORINFO(PyIConnectionPoint, IID_IConnectionPoint);
static PyComTypeObject type;
static IConnectionPoint *GetI(PyObject *self);
static PyObject *GetConnectionInterface(PyObject *self, PyObject *args);
static PyObject *GetConnectionPointContainer(PyObject *self, PyObject *args);
static PyObject *Advise(PyObject *self, PyObject *args);
static PyObject *Unadvise(PyObject *self, PyObject *args);
static PyObject *EnumConnections(PyObject *self, PyObject *args);
protected:
PyIConnectionPoint(IUnknown *);
~PyIConnectionPoint();
};
class PYCOM_EXPORT PyIConnectionPointContainer : public PyIUnknown {
public:
MAKE_PYCOM_CTOR_ERRORINFO(PyIConnectionPointContainer, IID_IConnectionPointContainer);
static PyComTypeObject type;
static IConnectionPointContainer *GetI(PyObject *self);
static PyObject *EnumConnectionPoints(PyObject *self, PyObject *args);
static PyObject *FindConnectionPoint(PyObject *self, PyObject *args);
protected:
PyIConnectionPointContainer(IUnknown *);
~PyIConnectionPointContainer();
};
/////////////////////////////////////////////////////////////////////////////
// class PythonOleArgHelper
//
// A PythonOleArgHelper is used primarily to help out Python helpers
// which need to convert from a Python object when the specific OLE
// type is known - eg, when a TypeInfo is available.
//
// The type of conversion determines who owns what buffers etc. I wish BYREF didnt exist :-)
typedef enum {
// We dont know what sort of conversion it is yet.
POAH_CONVERT_UNKNOWN,
// A PyObject is given, we convert to a VARIANT, make the COM call, then BYREFs back to a PyObject
// ie, this is typically a "normal" COM call, where Python initiates the call
POAH_CONVERT_FROM_PYOBJECT,
// A VARIANT is given, we convert to a PyObject, make the Python call, then BYREFs back to a VARIANT.
// ie, this is typically handling a COM event, where COM itself initiates the call.
POAH_CONVERT_FROM_VARIANT,
} POAH_CONVERT_DIRECTION;
class PYCOM_EXPORT PythonOleArgHelper {
public:
PythonOleArgHelper();
~PythonOleArgHelper();
BOOL ParseTypeInformation(PyObject *reqdObjectTuple);
// Using this call with reqdObject != NULL will check the existing
// VT_ of the variant. If not VT_EMPTY, then the result will be coerced to
// that type. This contrasts with PyCom_PyObjectToVariant which just
// uses the Python type to determine the variant type.
BOOL MakeObjToVariant(PyObject *obj, VARIANT *var, PyObject *reqdObjectTuple = NULL);
PyObject *MakeVariantToObj(VARIANT *var);
VARTYPE m_reqdType;
BOOL m_bParsedTypeInfo;
BOOL m_bIsOut;
POAH_CONVERT_DIRECTION m_convertDirection;
PyObject *m_pyVariant; // if non-null, a win32com.client.VARIANT
union {
void *m_pValueHolder;
short m_sBuf;
long m_lBuf;
LONGLONG m_llBuf;
VARIANT_BOOL m_boolBuf;
double m_dBuf;
float m_fBuf;
IDispatch *m_dispBuf;
IUnknown *m_unkBuf;
SAFEARRAY *m_arrayBuf;
VARIANT *m_varBuf;
DATE m_dateBuf;
CY m_cyBuf;
};
};
/////////////////////////////////////////////////////////////////////////////
// global functions and variables
PYCOM_EXPORT BOOL MakePythonArgumentTuples(PyObject **pArgs, PythonOleArgHelper **ppHelpers, PyObject **pNamedArgs,
PythonOleArgHelper **ppNamedHelpers, DISPPARAMS FAR *params);
// Convert a Python object to a BSTR - allow embedded NULLs, None, etc.
PYCOM_EXPORT BOOL PyCom_BstrFromPyObject(PyObject *stringObject, BSTR *pResult, BOOL bNoneOK = FALSE);
// MakeBstrToObj - convert a BSTR into a Python string.
//
// ONLY USE THIS FOR TRUE BSTR's - Use the fn below for OLECHAR *'s.
// NOTE - does not use standard macros, so NULLs get through!
PYCOM_EXPORT PyObject *MakeBstrToObj(const BSTR bstr);
// Size info is available (eg, a fn returns a string and also fills in a size variable)
PYCOM_EXPORT PyObject *MakeOLECHARToObj(const OLECHAR *str, int numChars);
// No size info avail.
PYCOM_EXPORT PyObject *MakeOLECHARToObj(const OLECHAR *str);
PYCOM_EXPORT void PyCom_LogF(const WCHAR *fmt, ...);
// Generic conversion from python sequence to VT_VECTOR array
// Resulting array must be freed with CoTaskMemFree
template <typename arraytype>
BOOL SeqToVector(PyObject *ob, arraytype **pA, ULONG *pcount, BOOL (*converter)(PyObject *, arraytype *))
{
TmpPyObject seq = PyWinSequence_Tuple(ob, pcount);
if (seq == NULL)
return FALSE;
*pA = (arraytype *)CoTaskMemAlloc(*pcount * sizeof(arraytype));
if (*pA == NULL) {
PyErr_NoMemory();
return FALSE;
}
for (ULONG i = 0; i < *pcount; i++) {
PyObject *item = PyTuple_GET_ITEM((PyObject *)seq, i);
if (!(*converter)(item, &(*pA)[i]))
return FALSE;
}
return TRUE;
}
#endif // __PYTHONCOM_H__

View file

@ -0,0 +1,84 @@
// Support for PythonCOM and its extensions to register the interfaces,
// gateways and IIDs it supports.
//
// The module can simply declare an array of type PyCom_InterfaceSupportInfo, then
// use the macros to populate it.
//
// See Register.cpp and AXScript.cpp for examples on its use.
#ifndef __PYTHONCOMREGISTER_H__
#define __PYTHONCOMREGISTER_H__
#include "PythonCOMServer.h" // Need defns in this file...
typedef struct {
const GUID *pGUID; // The supported IID - required
const char *interfaceName; // Name of the interface - required
const char *iidName; // Name of the IID that goes into the dict. - required
PyTypeObject *pTypeOb; // the type object for client PyI* side - NULL for server only support.
pfnPyGatewayConstructor ctor; // Gateway (PyG*) interface constructor - NULL for client only support
} PyCom_InterfaceSupportInfo;
#define PYCOM_INTERFACE_IID_ONLY(ifc) \
{ \
&IID_I##ifc, "I" #ifc, "IID_I" #ifc, NULL, NULL \
}
#define PYCOM_INTERFACE_CLSID_ONLY(ifc) \
{ \
&CLSID_##ifc, "CLSID_" #ifc, "CLSID_" #ifc, NULL, NULL \
}
#define PYCOM_INTERFACE_CATID_ONLY(ifc) \
{ \
&CATID_##ifc, "CATID_" #ifc, "CATID_" #ifc, NULL, NULL \
}
#define PYCOM_INTERFACE_CLIENT_ONLY(ifc) \
{ \
&IID_I##ifc, "I" #ifc, "IID_I" #ifc, &PyI##ifc::type, NULL \
}
#define PYCOM_INTERFACE_SERVER_ONLY(ifc) \
{ \
&IID_I##ifc, "I" #ifc, "IID_I" #ifc, NULL, GET_PYGATEWAY_CTOR(PyG##ifc) \
}
#define PYCOM_INTERFACE_FULL(ifc) \
{ \
&IID_I##ifc, "I" #ifc, "IID_I" #ifc, &PyI##ifc::type, GET_PYGATEWAY_CTOR(PyG##ifc) \
}
// Versions that use __uuidof() to get the IID, which seems to avoid the need
// to link with a lib holding the IIDs. Note that almost all extensions
// build with __uuidof() being the default; the build failed at 'shell' - so
// we could consider making this the default and making the 'explicit' version
// above the special case.
#define PYCOM_INTERFACE_IID_ONLY_UUIDOF(ifc) \
{ \
&__uuidof(I##ifc), "I" #ifc, "IID_I" #ifc, NULL, NULL \
}
#define PYCOM_INTERFACE_CLIENT_ONLY_UUIDOF(ifc) \
{ \
&__uuidof(I##ifc), "I" #ifc, "IID_I" #ifc, &PyI##ifc::type, NULL \
}
#define PYCOM_INTERFACE_SERVER_ONLY_UUIDOF(ifc) \
{ \
&__uuidof(I##ifc), "I" #ifc, "IID_I" #ifc, NULL, GET_PYGATEWAY_CTOR(PyG##ifc) \
}
#define PYCOM_INTERFACE_FULL_UUIDOF(ifc) \
{ \
&__uuidof(I##ifc), "I" #ifc, "IID_I" #ifc, &PyI##ifc::type, GET_PYGATEWAY_CTOR(PyG##ifc) \
}
// Prototypes for the register functions
// Register a PythonCOM extension module
PYCOM_EXPORT int PyCom_RegisterExtensionSupport(PyObject *dict, const PyCom_InterfaceSupportInfo *pInterfaces,
int numEntries);
// THESE SHOULD NO LONGER BE USED. Instead, use the functions above passing an
// array of PyCom_InterfaceSupportInfo objects.
PYCOM_EXPORT int PyCom_RegisterClientType(PyTypeObject *typeOb, const GUID *guid);
HRESULT PYCOM_EXPORT PyCom_RegisterGatewayObject(REFIID iid, pfnPyGatewayConstructor ctor, const char *interfaceName);
PYCOM_EXPORT int PyCom_IsGatewayRegistered(REFIID iid);
#endif /* __PYTHONCOMREGISTER_H__ */

View file

@ -0,0 +1,176 @@
#ifndef __PYTHONCOMSERVER_H__
#define __PYTHONCOMSERVER_H__
// PythonCOMServer.h :Server side COM support
#include <Python.h>
#define DLLAcquireGlobalLock PyWin_AcquireGlobalLock
#define DLLReleaseGlobalLock PyWin_ReleaseGlobalLock
void PYCOM_EXPORT PyCom_DLLAddRef(void);
void PYCOM_EXPORT PyCom_DLLReleaseRef(void);
// Use this macro at the start of all gateway methods.
#define PY_GATEWAY_METHOD CEnterLeavePython _celp
class PyGatewayBase;
// Gateway constructors.
// Each gateway must be able to be created from a "gateway constructor". This
// is simply a function that takes a Python instance as as argument, and returns
// a gateway object of the correct type. The MAKE_PYGATEWAY_CTOR is a helper that
// will embed such a constructor in the class - however, this is not necessary -
// _any_ function of the correct signature can be used.
typedef HRESULT (*pfnPyGatewayConstructor)(PyObject *PythonInstance, PyGatewayBase *, void **ppResult, REFIID iid);
HRESULT PyCom_MakeRegisteredGatewayObject(REFIID iid, PyObject *instance, PyGatewayBase *base, void **ppv);
// A version of the above which support classes being derived from
// other than IUnknown
#define PYGATEWAY_MAKE_SUPPORT2(classname, IInterface, theIID, gatewaybaseclass) \
public: \
static HRESULT PyGatewayConstruct(PyObject *pPyInstance, PyGatewayBase *unkBase, void **ppResult, \
REFIID iid) \
{ \
if (ppResult == NULL) \
return E_INVALIDARG; \
classname *newob = new classname(pPyInstance); \
newob->m_pBaseObject = unkBase; \
if (unkBase) \
unkBase->AddRef(); \
*ppResult = newob->ThisAsIID(iid); \
return *ppResult ? S_OK : E_OUTOFMEMORY; \
} \
\
protected: \
virtual IID GetIID(void) { return theIID; } \
virtual void *ThisAsIID(IID iid) \
{ \
if (this == NULL) \
return NULL; \
if (iid == theIID) \
return (IInterface *)this; \
else \
return gatewaybaseclass::ThisAsIID(iid); \
} \
STDMETHOD_(ULONG, AddRef)(void) { return gatewaybaseclass::AddRef(); } \
STDMETHOD_(ULONG, Release)(void) { return gatewaybaseclass::Release(); } \
STDMETHOD(QueryInterface)(REFIID iid, void **obj) { return gatewaybaseclass::QueryInterface(iid, obj); };
// This is the "old" version to use, or use it if you derive
// directly from PyGatewayBase
#define PYGATEWAY_MAKE_SUPPORT(classname, IInterface, theIID) \
PYGATEWAY_MAKE_SUPPORT2(classname, IInterface, theIID, PyGatewayBase)
#define GET_PYGATEWAY_CTOR(classname) classname::PyGatewayConstruct
#ifdef _MSC_VER
// Disable an OK warning...
#pragma warning(disable : 4275)
// warning C4275: non dll-interface struct 'IDispatch' used as base for dll-interface class 'PyGatewayBase'
#endif // _MSC_VER
// Helper interface for fetching a Python object from a gateway
extern const GUID IID_IInternalUnwrapPythonObject;
interface IInternalUnwrapPythonObject : public IUnknown
{
public:
STDMETHOD(Unwrap)(PyObject * *ppPyObject) = 0;
};
/////////////////////////////////////////////////////////////////////////////
// PyGatewayBase
//
// Base class for all gateways.
//
class PYCOM_EXPORT PyGatewayBase :
#ifndef NO_PYCOM_IDISPATCHEX
public IDispatchEx, // IDispatch comes along for the ride!
#else
public IDispatch, // No IDispatchEx - must explicitely use IDispatch
#endif
public ISupportErrorInfo,
public IInternalUnwrapPythonObject {
protected:
PyGatewayBase(PyObject *instance);
virtual ~PyGatewayBase();
// Invoke the Python method (via the policy object)
STDMETHOD(InvokeViaPolicy)(const char *szMethodName, PyObject **ppResult = NULL, const char *szFormat = NULL, ...);
public:
// IUnknown
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
STDMETHOD(QueryInterface)(REFIID iid, void **obj);
// IDispatch
STDMETHOD(GetTypeInfoCount)(UINT FAR *pctInfo);
STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo FAR *FAR *pptInfo);
STDMETHOD(GetIDsOfNames)(REFIID refiid, OLECHAR FAR *FAR *rgszNames, UINT cNames, LCID lcid, DISPID FAR *rgdispid);
STDMETHOD(Invoke)
(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *params, VARIANT FAR *pVarResult,
EXCEPINFO FAR *pexcepinfo, UINT FAR *puArgErr);
// IDispatchEx
#ifndef NO_PYCOM_IDISPATCHEX
STDMETHOD(GetDispID)(BSTR bstrName, DWORD grfdex, DISPID *pid);
STDMETHOD(InvokeEx)
(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
STDMETHOD(DeleteMemberByName)(BSTR bstr, DWORD grfdex);
STDMETHOD(DeleteMemberByDispID)(DISPID id);
STDMETHOD(GetMemberProperties)(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex);
STDMETHOD(GetMemberName)(DISPID id, BSTR *pbstrName);
STDMETHOD(GetNextDispID)(DWORD grfdex, DISPID id, DISPID *pid);
STDMETHOD(GetNameSpaceParent)(IUnknown **ppunk);
#endif // NO_PYCOM_IDISPATCHEX
// ISupportErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
// IInternalUnwrapPythonObject
STDMETHOD(Unwrap)(PyObject **ppPyObject);
// Basically just PYGATEWAY_MAKE_SUPPORT(PyGatewayBase, IDispatch, IID_IDispatch);
// but with special handling as its the base class.
static HRESULT PyGatewayConstruct(PyObject *pPyInstance, PyGatewayBase *gatewayBase, void **ppResult,
REFIID iid)
{
if (ppResult == NULL)
return E_INVALIDARG;
PyGatewayBase *obNew = new PyGatewayBase(pPyInstance);
obNew->m_pBaseObject = gatewayBase;
if (gatewayBase)
gatewayBase->AddRef();
*ppResult = (IDispatch *)obNew;
return *ppResult ? S_OK : E_OUTOFMEMORY;
}
// Currently this is used only for ISupportErrorInfo,
// so hopefully this will never be called in this base class.
// (however, this is not a rule, so we wont assert or anything!)
virtual IID GetIID(void) { return IID_IUnknown; }
virtual void *ThisAsIID(IID iid);
// End of PYGATEWAY_MAKE_SUPPORT
PyObject *m_pPyObject;
PyGatewayBase *m_pBaseObject;
private:
LONG m_cRef;
};
#ifdef _MSC_VER
#pragma warning(default : 4275)
#endif // _MSC_VER
// B/W compat hack for gateways.
#define PyCom_HandlePythonFailureToCOM() \
PyCom_SetAndLogCOMErrorFromPyExceptionEx(this->m_pPyObject, "<unknown>", GetIID())
// F/W compat hack for gateways! Must be careful about updating
// PyGatewayBase vtable, so a slightly older pythoncomXX.dll will work
// with slightly later extensions. So use a #define.
#define MAKE_PYCOM_GATEWAY_FAILURE_CODE(method_name) \
PyCom_SetAndLogCOMErrorFromPyExceptionEx(this->m_pPyObject, method_name, GetIID())
#endif /* __PYTHONCOMSERVER_H__ */

View file

@ -0,0 +1 @@
# indicates a python package.

View file

@ -0,0 +1,560 @@
"""Utility functions for writing out gateway C++ files
This module will generate a C++/Python binding for a specific COM
interface.
At this stage, no command line interface exists. You must start Python,
import this module, change to the directory where the generated code should
be written, and run the public function.
This module is capable of generating both 'Interfaces' (ie, Python
client side support for the interface) and 'Gateways' (ie, Python
server side support for the interface). Many COM interfaces are useful
both as Client and Server. Other interfaces, however, really only make
sense to implement one side or the other. For example, it would be pointless
for Python to implement Server side for 'IRunningObjectTable', unless we were
implementing core COM for an operating system in Python (hey - now there's an idea!)
Most COM interface code is totally boiler-plate - it consists of
converting arguments, dispatching the call to Python, and processing
any result values.
This module automates the generation of such code. It has the ability to
parse a .H file generated by the MIDL tool (ie, almost all COM .h files)
and build almost totally complete C++ code.
The module understands some of the well known data types, and how to
convert them. There are only a couple of places where hand-editing is
necessary, as detailed below:
unsupported types -- If a type is not known, the generator will
pretty much ignore it, but write a comment to the generated code. You
may want to add custom support for this type. In some cases, C++ compile errors
will result. These are intentional - generating code to remove these errors would
imply a false sense of security that the generator has done the right thing.
other return policies -- By default, Python never sees the return SCODE from
a COM function. The interface usually returns None if OK, else a COM exception
if "FAILED(scode)" is TRUE. You may need to change this if:
* EXCEPINFO is passed to the COM function. This is not detected and handled
* For some reason Python should always see the result SCODE, even if it
did fail or succeed. For example, some functions return a BOOLEAN result
in the SCODE, meaning Python should always see it.
* FAILED(scode) for the interface still has valid data to return (by default,
the code generated does not process the return values, and raise an exception
to Python/COM
"""
import re
from . import makegwparse
def make_framework_support(
header_file_name, interface_name, bMakeInterface=1, bMakeGateway=1
):
"""Generate C++ code for a Python Interface and Gateway
header_file_name -- The full path to the .H file which defines the interface.
interface_name -- The name of the interface to search for, and to generate.
bMakeInterface = 1 -- Should interface (ie, client) support be generated.
bMakeGatewayInterface = 1 -- Should gateway (ie, server) support be generated.
This method will write a .cpp and .h file into the current directory,
(using the name of the interface to build the file name.
"""
fin = open(header_file_name)
try:
interface = makegwparse.parse_interface_info(interface_name, fin)
finally:
fin.close()
if bMakeInterface and bMakeGateway:
desc = "Interface and Gateway"
elif bMakeInterface and not bMakeGateway:
desc = "Interface"
else:
desc = "Gateway"
if interface.name[:5] == "IEnum": # IEnum - use my really simple template-based one
import win32com.makegw.makegwenum
ifc_cpp_writer = win32com.makegw.makegwenum._write_enumifc_cpp
gw_cpp_writer = win32com.makegw.makegwenum._write_enumgw_cpp
else: # Use my harder working ones.
ifc_cpp_writer = _write_ifc_cpp
gw_cpp_writer = _write_gw_cpp
fout = open("Py%s.cpp" % interface.name, "w")
try:
fout.write(
"""\
// This file implements the %s %s for Python.
// Generated by makegw.py
#include "shell_pch.h"
"""
% (interface.name, desc)
)
# if bMakeGateway:
# fout.write('#include "PythonCOMServer.h"\n')
# if interface.base not in ["IUnknown", "IDispatch"]:
# fout.write('#include "Py%s.h"\n' % interface.base)
fout.write(
'#include "Py%s.h"\n\n// @doc - This file contains autoduck documentation\n'
% interface.name
)
if bMakeInterface:
ifc_cpp_writer(fout, interface)
if bMakeGateway:
gw_cpp_writer(fout, interface)
finally:
fout.close()
fout = open("Py%s.h" % interface.name, "w")
try:
fout.write(
"""\
// This file declares the %s %s for Python.
// Generated by makegw.py
"""
% (interface.name, desc)
)
if bMakeInterface:
_write_ifc_h(fout, interface)
if bMakeGateway:
_write_gw_h(fout, interface)
finally:
fout.close()
###########################################################################
#
# INTERNAL FUNCTIONS
#
#
def _write_ifc_h(f, interface):
f.write(
"""\
// ---------------------------------------------------
//
// Interface Declaration
class Py%s : public Py%s
{
public:
MAKE_PYCOM_CTOR(Py%s);
static %s *GetI(PyObject *self);
static PyComTypeObject type;
// The Python methods
"""
% (interface.name, interface.base, interface.name, interface.name)
)
for method in interface.methods:
f.write(
"\tstatic PyObject *%s(PyObject *self, PyObject *args);\n" % method.name
)
f.write(
"""\
protected:
Py%s(IUnknown *pdisp);
~Py%s();
};
"""
% (interface.name, interface.name)
)
def _write_ifc_cpp(f, interface):
name = interface.name
f.write(
"""\
// ---------------------------------------------------
//
// Interface Implementation
Py%(name)s::Py%(name)s(IUnknown *pdisp):
Py%(base)s(pdisp)
{
ob_type = &type;
}
Py%(name)s::~Py%(name)s()
{
}
/* static */ %(name)s *Py%(name)s::GetI(PyObject *self)
{
return (%(name)s *)Py%(base)s::GetI(self);
}
"""
% (interface.__dict__)
)
ptr = re.sub("[a-z]", "", interface.name)
strdict = {"interfacename": interface.name, "ptr": ptr}
for method in interface.methods:
strdict["method"] = method.name
f.write(
"""\
// @pymethod |Py%(interfacename)s|%(method)s|Description of %(method)s.
PyObject *Py%(interfacename)s::%(method)s(PyObject *self, PyObject *args)
{
%(interfacename)s *p%(ptr)s = GetI(self);
if ( p%(ptr)s == NULL )
return NULL;
"""
% strdict
)
argsParseTuple = (
argsCOM
) = (
formatChars
) = codePost = codePobjects = codeCobjects = cleanup = cleanup_gil = ""
needConversion = 0
# if method.name=="Stat": import win32dbg;win32dbg.brk()
for arg in method.args:
try:
argCvt = makegwparse.make_arg_converter(arg)
if arg.HasAttribute("in"):
val = argCvt.GetFormatChar()
if val:
f.write("\t" + argCvt.GetAutoduckString() + "\n")
formatChars = formatChars + val
argsParseTuple = (
argsParseTuple + ", " + argCvt.GetParseTupleArg()
)
codePobjects = (
codePobjects + argCvt.DeclareParseArgTupleInputConverter()
)
codePost = codePost + argCvt.GetParsePostCode()
needConversion = needConversion or argCvt.NeedUSES_CONVERSION()
cleanup = cleanup + argCvt.GetInterfaceArgCleanup()
cleanup_gil = cleanup_gil + argCvt.GetInterfaceArgCleanupGIL()
comArgName, comArgDeclString = argCvt.GetInterfaceCppObjectInfo()
if comArgDeclString: # If we should declare a variable
codeCobjects = codeCobjects + "\t%s;\n" % (comArgDeclString)
argsCOM = argsCOM + ", " + comArgName
except makegwparse.error_not_supported as why:
f.write(
'// *** The input argument %s of type "%s" was not processed ***\n// Please check the conversion function is appropriate and exists!\n'
% (arg.name, arg.raw_type)
)
f.write(
"\t%s %s;\n\tPyObject *ob%s;\n" % (arg.type, arg.name, arg.name)
)
f.write(
"\t// @pyparm <o Py%s>|%s||Description for %s\n"
% (arg.type, arg.name, arg.name)
)
codePost = (
codePost
+ "\tif (bPythonIsHappy && !PyObject_As%s( ob%s, &%s )) bPythonIsHappy = FALSE;\n"
% (arg.type, arg.name, arg.name)
)
formatChars = formatChars + "O"
argsParseTuple = argsParseTuple + ", &ob%s" % (arg.name)
argsCOM = argsCOM + ", " + arg.name
cleanup = cleanup + "\tPyObject_Free%s(%s);\n" % (arg.type, arg.name)
if needConversion:
f.write("\tUSES_CONVERSION;\n")
f.write(codePobjects)
f.write(codeCobjects)
f.write(
'\tif ( !PyArg_ParseTuple(args, "%s:%s"%s) )\n\t\treturn NULL;\n'
% (formatChars, method.name, argsParseTuple)
)
if codePost:
f.write("\tBOOL bPythonIsHappy = TRUE;\n")
f.write(codePost)
f.write("\tif (!bPythonIsHappy) return NULL;\n")
strdict["argsCOM"] = argsCOM[1:]
strdict["cleanup"] = cleanup
strdict["cleanup_gil"] = cleanup_gil
f.write(
""" HRESULT hr;
PY_INTERFACE_PRECALL;
hr = p%(ptr)s->%(method)s(%(argsCOM)s );
%(cleanup)s
PY_INTERFACE_POSTCALL;
%(cleanup_gil)s
if ( FAILED(hr) )
return PyCom_BuildPyException(hr, p%(ptr)s, IID_%(interfacename)s );
"""
% strdict
)
codePre = codePost = formatChars = codeVarsPass = codeDecl = ""
for arg in method.args:
if not arg.HasAttribute("out"):
continue
try:
argCvt = makegwparse.make_arg_converter(arg)
formatChar = argCvt.GetFormatChar()
if formatChar:
formatChars = formatChars + formatChar
codePre = codePre + argCvt.GetBuildForInterfacePreCode()
codePost = codePost + argCvt.GetBuildForInterfacePostCode()
codeVarsPass = codeVarsPass + ", " + argCvt.GetBuildValueArg()
codeDecl = codeDecl + argCvt.DeclareParseArgTupleInputConverter()
except makegwparse.error_not_supported as why:
f.write(
'// *** The output argument %s of type "%s" was not processed ***\n// %s\n'
% (arg.name, arg.raw_type, why)
)
continue
if formatChars:
f.write(
'%s\n%s\tPyObject *pyretval = Py_BuildValue("%s"%s);\n%s\treturn pyretval;'
% (codeDecl, codePre, formatChars, codeVarsPass, codePost)
)
else:
f.write("\tPy_INCREF(Py_None);\n\treturn Py_None;\n")
f.write("\n}\n\n")
f.write("// @object Py%s|Description of the interface\n" % (name))
f.write("static struct PyMethodDef Py%s_methods[] =\n{\n" % name)
for method in interface.methods:
f.write(
'\t{ "%s", Py%s::%s, 1 }, // @pymeth %s|Description of %s\n'
% (method.name, interface.name, method.name, method.name, method.name)
)
interfacebase = interface.base
f.write(
"""\
{ NULL }
};
PyComTypeObject Py%(name)s::type("Py%(name)s",
&Py%(interfacebase)s::type,
sizeof(Py%(name)s),
Py%(name)s_methods,
GET_PYCOM_CTOR(Py%(name)s));
"""
% locals()
)
def _write_gw_h(f, interface):
if interface.name[0] == "I":
gname = "PyG" + interface.name[1:]
else:
gname = "PyG" + interface.name
name = interface.name
if interface.base == "IUnknown" or interface.base == "IDispatch":
base_name = "PyGatewayBase"
else:
if interface.base[0] == "I":
base_name = "PyG" + interface.base[1:]
else:
base_name = "PyG" + interface.base
f.write(
"""\
// ---------------------------------------------------
//
// Gateway Declaration
class %s : public %s, public %s
{
protected:
%s(PyObject *instance) : %s(instance) { ; }
PYGATEWAY_MAKE_SUPPORT2(%s, %s, IID_%s, %s)
"""
% (gname, base_name, name, gname, base_name, gname, name, name, base_name)
)
if interface.base != "IUnknown":
f.write(
"\t// %s\n\t// *** Manually add %s method decls here\n\n"
% (interface.base, interface.base)
)
else:
f.write("\n\n")
f.write("\t// %s\n" % name)
for method in interface.methods:
f.write("\tSTDMETHOD(%s)(\n" % method.name)
if method.args:
for arg in method.args[:-1]:
f.write("\t\t%s,\n" % (arg.GetRawDeclaration()))
arg = method.args[-1]
f.write("\t\t%s);\n\n" % (arg.GetRawDeclaration()))
else:
f.write("\t\tvoid);\n\n")
f.write("};\n")
f.close()
def _write_gw_cpp(f, interface):
if interface.name[0] == "I":
gname = "PyG" + interface.name[1:]
else:
gname = "PyG" + interface.name
name = interface.name
if interface.base == "IUnknown" or interface.base == "IDispatch":
base_name = "PyGatewayBase"
else:
if interface.base[0] == "I":
base_name = "PyG" + interface.base[1:]
else:
base_name = "PyG" + interface.base
f.write(
"""\
// ---------------------------------------------------
//
// Gateway Implementation
"""
% {"name": name, "gname": gname, "base_name": base_name}
)
for method in interface.methods:
f.write(
"""\
STDMETHODIMP %s::%s(
"""
% (gname, method.name)
)
if method.args:
for arg in method.args[:-1]:
inoutstr = "][".join(arg.inout)
f.write("\t\t/* [%s] */ %s,\n" % (inoutstr, arg.GetRawDeclaration()))
arg = method.args[-1]
inoutstr = "][".join(arg.inout)
f.write("\t\t/* [%s] */ %s)\n" % (inoutstr, arg.GetRawDeclaration()))
else:
f.write("\t\tvoid)\n")
f.write("{\n\tPY_GATEWAY_METHOD;\n")
cout = 0
codePre = codePost = codeVars = ""
argStr = ""
needConversion = 0
formatChars = ""
if method.args:
for arg in method.args:
if arg.HasAttribute("out"):
cout = cout + 1
if arg.indirectionLevel == 2:
f.write("\tif (%s==NULL) return E_POINTER;\n" % arg.name)
if arg.HasAttribute("in"):
try:
argCvt = makegwparse.make_arg_converter(arg)
argCvt.SetGatewayMode()
formatchar = argCvt.GetFormatChar()
needConversion = needConversion or argCvt.NeedUSES_CONVERSION()
if formatchar:
formatChars = formatChars + formatchar
codeVars = (
codeVars + argCvt.DeclareParseArgTupleInputConverter()
)
argStr = argStr + ", " + argCvt.GetBuildValueArg()
codePre = codePre + argCvt.GetBuildForGatewayPreCode()
codePost = codePost + argCvt.GetBuildForGatewayPostCode()
except makegwparse.error_not_supported as why:
f.write(
'// *** The input argument %s of type "%s" was not processed ***\n// - Please ensure this conversion function exists, and is appropriate\n// - %s\n'
% (arg.name, arg.raw_type, why)
)
f.write(
"\tPyObject *ob%s = PyObject_From%s(%s);\n"
% (arg.name, arg.type, arg.name)
)
f.write(
'\tif (ob%s==NULL) return MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'
% (arg.name, method.name)
)
codePost = codePost + "\tPy_DECREF(ob%s);\n" % arg.name
formatChars = formatChars + "O"
argStr = argStr + ", ob%s" % (arg.name)
if needConversion:
f.write("\tUSES_CONVERSION;\n")
f.write(codeVars)
f.write(codePre)
if cout:
f.write("\tPyObject *result;\n")
resStr = "&result"
else:
resStr = "NULL"
if formatChars:
fullArgStr = '%s, "%s"%s' % (resStr, formatChars, argStr)
else:
fullArgStr = resStr
f.write('\tHRESULT hr=InvokeViaPolicy("%s", %s);\n' % (method.name, fullArgStr))
f.write(codePost)
if cout:
f.write("\tif (FAILED(hr)) return hr;\n")
f.write(
"\t// Process the Python results, and convert back to the real params\n"
)
# process the output arguments.
formatChars = codePobjects = codePost = argsParseTuple = ""
needConversion = 0
for arg in method.args:
if not arg.HasAttribute("out"):
continue
try:
argCvt = makegwparse.make_arg_converter(arg)
argCvt.SetGatewayMode()
val = argCvt.GetFormatChar()
if val:
formatChars = formatChars + val
argsParseTuple = (
argsParseTuple + ", " + argCvt.GetParseTupleArg()
)
codePobjects = (
codePobjects + argCvt.DeclareParseArgTupleInputConverter()
)
codePost = codePost + argCvt.GetParsePostCode()
needConversion = needConversion or argCvt.NeedUSES_CONVERSION()
except makegwparse.error_not_supported as why:
f.write(
'// *** The output argument %s of type "%s" was not processed ***\n// %s\n'
% (arg.name, arg.raw_type, why)
)
if formatChars: # If I have any to actually process.
if len(formatChars) == 1:
parseFn = "PyArg_Parse"
else:
parseFn = "PyArg_ParseTuple"
if codePobjects:
f.write(codePobjects)
f.write(
'\tif (!%s(result, "%s" %s))\n\t\treturn MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'
% (parseFn, formatChars, argsParseTuple, method.name)
)
if codePost:
f.write("\tBOOL bPythonIsHappy = TRUE;\n")
f.write(codePost)
f.write(
'\tif (!bPythonIsHappy) hr = MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'
% method.name
)
f.write("\tPy_DECREF(result);\n")
f.write("\treturn hr;\n}\n\n")
def test():
# make_framework_support("d:\\msdev\\include\\objidl.h", "ILockBytes")
make_framework_support("d:\\msdev\\include\\objidl.h", "IStorage")
# make_framework_support("d:\\msdev\\include\\objidl.h", "IEnumSTATSTG")

View file

@ -0,0 +1,331 @@
"""Utility file for generating PyIEnum support.
This is almost a 'template' file. It simplay contains almost full
C++ source code for PyIEnum* support, and the Python code simply
substitutes the appropriate interface name.
This module is notmally not used directly - the @makegw@ module
automatically calls this.
"""
#
# INTERNAL FUNCTIONS
#
#
import string
def is_interface_enum(enumtype):
return not (enumtype[0] in string.uppercase and enumtype[2] in string.uppercase)
def _write_enumifc_cpp(f, interface):
enumtype = interface.name[5:]
if is_interface_enum(enumtype):
# Assume an interface.
enum_interface = "I" + enumtype[:-1]
converter = (
"PyObject *ob = PyCom_PyObjectFromIUnknown(rgVar[i], IID_%(enum_interface)s, FALSE);"
% locals()
)
arraydeclare = (
"%(enum_interface)s **rgVar = new %(enum_interface)s *[celt];" % locals()
)
else:
# Enum of a simple structure
converter = (
"PyObject *ob = PyCom_PyObjectFrom%(enumtype)s(&rgVar[i]);" % locals()
)
arraydeclare = "%(enumtype)s *rgVar = new %(enumtype)s[celt];" % locals()
f.write(
"""
// ---------------------------------------------------
//
// Interface Implementation
PyIEnum%(enumtype)s::PyIEnum%(enumtype)s(IUnknown *pdisp):
PyIUnknown(pdisp)
{
ob_type = &type;
}
PyIEnum%(enumtype)s::~PyIEnum%(enumtype)s()
{
}
/* static */ IEnum%(enumtype)s *PyIEnum%(enumtype)s::GetI(PyObject *self)
{
return (IEnum%(enumtype)s *)PyIUnknown::GetI(self);
}
// @pymethod object|PyIEnum%(enumtype)s|Next|Retrieves a specified number of items in the enumeration sequence.
PyObject *PyIEnum%(enumtype)s::Next(PyObject *self, PyObject *args)
{
long celt = 1;
// @pyparm int|num|1|Number of items to retrieve.
if ( !PyArg_ParseTuple(args, "|l:Next", &celt) )
return NULL;
IEnum%(enumtype)s *pIE%(enumtype)s = GetI(self);
if ( pIE%(enumtype)s == NULL )
return NULL;
%(arraydeclare)s
if ( rgVar == NULL ) {
PyErr_SetString(PyExc_MemoryError, "allocating result %(enumtype)ss");
return NULL;
}
int i;
/* for ( i = celt; i--; )
// *** possibly init each structure element???
*/
ULONG celtFetched = 0;
PY_INTERFACE_PRECALL;
HRESULT hr = pIE%(enumtype)s->Next(celt, rgVar, &celtFetched);
PY_INTERFACE_POSTCALL;
if ( HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS && FAILED(hr) )
{
delete [] rgVar;
return PyCom_BuildPyException(hr,pIE%(enumtype)s, IID_IE%(enumtype)s);
}
PyObject *result = PyTuple_New(celtFetched);
if ( result != NULL )
{
for ( i = celtFetched; i--; )
{
%(converter)s
if ( ob == NULL )
{
Py_DECREF(result);
result = NULL;
break;
}
PyTuple_SET_ITEM(result, i, ob);
}
}
/* for ( i = celtFetched; i--; )
// *** possibly cleanup each structure element???
*/
delete [] rgVar;
return result;
}
// @pymethod |PyIEnum%(enumtype)s|Skip|Skips over the next specified elementes.
PyObject *PyIEnum%(enumtype)s::Skip(PyObject *self, PyObject *args)
{
long celt;
if ( !PyArg_ParseTuple(args, "l:Skip", &celt) )
return NULL;
IEnum%(enumtype)s *pIE%(enumtype)s = GetI(self);
if ( pIE%(enumtype)s == NULL )
return NULL;
PY_INTERFACE_PRECALL;
HRESULT hr = pIE%(enumtype)s->Skip(celt);
PY_INTERFACE_POSTCALL;
if ( FAILED(hr) )
return PyCom_BuildPyException(hr, pIE%(enumtype)s, IID_IE%(enumtype)s);
Py_INCREF(Py_None);
return Py_None;
}
// @pymethod |PyIEnum%(enumtype)s|Reset|Resets the enumeration sequence to the beginning.
PyObject *PyIEnum%(enumtype)s::Reset(PyObject *self, PyObject *args)
{
if ( !PyArg_ParseTuple(args, ":Reset") )
return NULL;
IEnum%(enumtype)s *pIE%(enumtype)s = GetI(self);
if ( pIE%(enumtype)s == NULL )
return NULL;
PY_INTERFACE_PRECALL;
HRESULT hr = pIE%(enumtype)s->Reset();
PY_INTERFACE_POSTCALL;
if ( FAILED(hr) )
return PyCom_BuildPyException(hr, pIE%(enumtype)s, IID_IE%(enumtype)s);
Py_INCREF(Py_None);
return Py_None;
}
// @pymethod <o PyIEnum%(enumtype)s>|PyIEnum%(enumtype)s|Clone|Creates another enumerator that contains the same enumeration state as the current one
PyObject *PyIEnum%(enumtype)s::Clone(PyObject *self, PyObject *args)
{
if ( !PyArg_ParseTuple(args, ":Clone") )
return NULL;
IEnum%(enumtype)s *pIE%(enumtype)s = GetI(self);
if ( pIE%(enumtype)s == NULL )
return NULL;
IEnum%(enumtype)s *pClone;
PY_INTERFACE_PRECALL;
HRESULT hr = pIE%(enumtype)s->Clone(&pClone);
PY_INTERFACE_POSTCALL;
if ( FAILED(hr) )
return PyCom_BuildPyException(hr, pIE%(enumtype)s, IID_IE%(enumtype)s);
return PyCom_PyObjectFromIUnknown(pClone, IID_IEnum%(enumtype)s, FALSE);
}
// @object PyIEnum%(enumtype)s|A Python interface to IEnum%(enumtype)s
static struct PyMethodDef PyIEnum%(enumtype)s_methods[] =
{
{ "Next", PyIEnum%(enumtype)s::Next, 1 }, // @pymeth Next|Retrieves a specified number of items in the enumeration sequence.
{ "Skip", PyIEnum%(enumtype)s::Skip, 1 }, // @pymeth Skip|Skips over the next specified elementes.
{ "Reset", PyIEnum%(enumtype)s::Reset, 1 }, // @pymeth Reset|Resets the enumeration sequence to the beginning.
{ "Clone", PyIEnum%(enumtype)s::Clone, 1 }, // @pymeth Clone|Creates another enumerator that contains the same enumeration state as the current one.
{ NULL }
};
PyComEnumTypeObject PyIEnum%(enumtype)s::type("PyIEnum%(enumtype)s",
&PyIUnknown::type,
sizeof(PyIEnum%(enumtype)s),
PyIEnum%(enumtype)s_methods,
GET_PYCOM_CTOR(PyIEnum%(enumtype)s));
"""
% locals()
)
def _write_enumgw_cpp(f, interface):
enumtype = interface.name[5:]
if is_interface_enum(enumtype):
# Assume an interface.
enum_interface = "I" + enumtype[:-1]
converter = (
"if ( !PyCom_InterfaceFromPyObject(ob, IID_%(enum_interface)s, (void **)&rgVar[i], FALSE) )"
% locals()
)
argdeclare = "%(enum_interface)s __RPC_FAR * __RPC_FAR *rgVar" % locals()
else:
argdeclare = "%(enumtype)s __RPC_FAR *rgVar" % locals()
converter = "if ( !PyCom_PyObjectAs%(enumtype)s(ob, &rgVar[i]) )" % locals()
f.write(
"""
// ---------------------------------------------------
//
// Gateway Implementation
// Std delegation
STDMETHODIMP_(ULONG) PyGEnum%(enumtype)s::AddRef(void) {return PyGatewayBase::AddRef();}
STDMETHODIMP_(ULONG) PyGEnum%(enumtype)s::Release(void) {return PyGatewayBase::Release();}
STDMETHODIMP PyGEnum%(enumtype)s::QueryInterface(REFIID iid, void ** obj) {return PyGatewayBase::QueryInterface(iid, obj);}
STDMETHODIMP PyGEnum%(enumtype)s::GetTypeInfoCount(UINT FAR* pctInfo) {return PyGatewayBase::GetTypeInfoCount(pctInfo);}
STDMETHODIMP PyGEnum%(enumtype)s::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptInfo) {return PyGatewayBase::GetTypeInfo(itinfo, lcid, pptInfo);}
STDMETHODIMP PyGEnum%(enumtype)s::GetIDsOfNames(REFIID refiid, OLECHAR FAR* FAR* rgszNames, UINT cNames, LCID lcid, DISPID FAR* rgdispid) {return PyGatewayBase::GetIDsOfNames( refiid, rgszNames, cNames, lcid, rgdispid);}
STDMETHODIMP PyGEnum%(enumtype)s::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* params, VARIANT FAR* pVarResult, EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr) {return PyGatewayBase::Invoke( dispid, riid, lcid, wFlags, params, pVarResult, pexcepinfo, puArgErr);}
STDMETHODIMP PyGEnum%(enumtype)s::Next(
/* [in] */ ULONG celt,
/* [length_is][size_is][out] */ %(argdeclare)s,
/* [out] */ ULONG __RPC_FAR *pCeltFetched)
{
PY_GATEWAY_METHOD;
PyObject *result;
HRESULT hr = InvokeViaPolicy("Next", &result, "i", celt);
if ( FAILED(hr) )
return hr;
if ( !PySequence_Check(result) )
goto error;
int len;
len = PyObject_Length(result);
if ( len == -1 )
goto error;
if ( len > (int)celt)
len = celt;
if ( pCeltFetched )
*pCeltFetched = len;
int i;
for ( i = 0; i < len; ++i )
{
PyObject *ob = PySequence_GetItem(result, i);
if ( ob == NULL )
goto error;
%(converter)s
{
Py_DECREF(result);
return PyCom_SetCOMErrorFromPyException(IID_IEnum%(enumtype)s);
}
}
Py_DECREF(result);
return len < (int)celt ? S_FALSE : S_OK;
error:
PyErr_Clear(); // just in case
Py_DECREF(result);
return PyCom_HandleIEnumNoSequence(IID_IEnum%(enumtype)s);
}
STDMETHODIMP PyGEnum%(enumtype)s::Skip(
/* [in] */ ULONG celt)
{
PY_GATEWAY_METHOD;
return InvokeViaPolicy("Skip", NULL, "i", celt);
}
STDMETHODIMP PyGEnum%(enumtype)s::Reset(void)
{
PY_GATEWAY_METHOD;
return InvokeViaPolicy("Reset");
}
STDMETHODIMP PyGEnum%(enumtype)s::Clone(
/* [out] */ IEnum%(enumtype)s __RPC_FAR *__RPC_FAR *ppEnum)
{
PY_GATEWAY_METHOD;
PyObject * result;
HRESULT hr = InvokeViaPolicy("Clone", &result);
if ( FAILED(hr) )
return hr;
/*
** Make sure we have the right kind of object: we should have some kind
** of IUnknown subclass wrapped into a PyIUnknown instance.
*/
if ( !PyIBase::is_object(result, &PyIUnknown::type) )
{
/* the wrong kind of object was returned to us */
Py_DECREF(result);
return PyCom_SetCOMErrorFromSimple(E_FAIL, IID_IEnum%(enumtype)s);
}
/*
** Get the IUnknown out of the thing. note that the Python ob maintains
** a reference, so we don't have to explicitly AddRef() here.
*/
IUnknown *punk = ((PyIUnknown *)result)->m_obj;
if ( !punk )
{
/* damn. the object was released. */
Py_DECREF(result);
return PyCom_SetCOMErrorFromSimple(E_FAIL, IID_IEnum%(enumtype)s);
}
/*
** Get the interface we want. note it is returned with a refcount.
** This QI is actually going to instantiate a PyGEnum%(enumtype)s.
*/
hr = punk->QueryInterface(IID_IEnum%(enumtype)s, (LPVOID *)ppEnum);
/* done with the result; this DECREF is also for <punk> */
Py_DECREF(result);
return PyCom_CheckIEnumNextResult(hr, IID_IEnum%(enumtype)s);
}
"""
% locals()
)

File diff suppressed because it is too large Load diff

70
lib/win32com/olectl.py Normal file
View file

@ -0,0 +1,70 @@
"""Constants used by COM Controls
Hand created version of OLECTL.H constants.
"""
import winerror
FACILITY_CONTROL = 0xA
def MAKE_SCODE(sev, fac, code):
return int((int(-sev) << 31) | ((fac) << 16) | ((code)))
def STD_CTL_SCODE(n):
return MAKE_SCODE(winerror.SEVERITY_ERROR, FACILITY_CONTROL, n)
CTL_E_ILLEGALFUNCTIONCALL = STD_CTL_SCODE(5)
CTL_E_OVERFLOW = STD_CTL_SCODE(6)
CTL_E_OUTOFMEMORY = STD_CTL_SCODE(7)
CTL_E_DIVISIONBYZERO = STD_CTL_SCODE(11)
CTL_E_OUTOFSTRINGSPACE = STD_CTL_SCODE(14)
CTL_E_OUTOFSTACKSPACE = STD_CTL_SCODE(28)
CTL_E_BADFILENAMEORNUMBER = STD_CTL_SCODE(52)
CTL_E_FILENOTFOUND = STD_CTL_SCODE(53)
CTL_E_BADFILEMODE = STD_CTL_SCODE(54)
CTL_E_FILEALREADYOPEN = STD_CTL_SCODE(55)
CTL_E_DEVICEIOERROR = STD_CTL_SCODE(57)
CTL_E_FILEALREADYEXISTS = STD_CTL_SCODE(58)
CTL_E_BADRECORDLENGTH = STD_CTL_SCODE(59)
CTL_E_DISKFULL = STD_CTL_SCODE(61)
CTL_E_BADRECORDNUMBER = STD_CTL_SCODE(63)
CTL_E_BADFILENAME = STD_CTL_SCODE(64)
CTL_E_TOOMANYFILES = STD_CTL_SCODE(67)
CTL_E_DEVICEUNAVAILABLE = STD_CTL_SCODE(68)
CTL_E_PERMISSIONDENIED = STD_CTL_SCODE(70)
CTL_E_DISKNOTREADY = STD_CTL_SCODE(71)
CTL_E_PATHFILEACCESSERROR = STD_CTL_SCODE(75)
CTL_E_PATHNOTFOUND = STD_CTL_SCODE(76)
CTL_E_INVALIDPATTERNSTRING = STD_CTL_SCODE(93)
CTL_E_INVALIDUSEOFNULL = STD_CTL_SCODE(94)
CTL_E_INVALIDFILEFORMAT = STD_CTL_SCODE(321)
CTL_E_INVALIDPROPERTYVALUE = STD_CTL_SCODE(380)
CTL_E_INVALIDPROPERTYARRAYINDEX = STD_CTL_SCODE(381)
CTL_E_SETNOTSUPPORTEDATRUNTIME = STD_CTL_SCODE(382)
CTL_E_SETNOTSUPPORTED = STD_CTL_SCODE(383)
CTL_E_NEEDPROPERTYARRAYINDEX = STD_CTL_SCODE(385)
CTL_E_SETNOTPERMITTED = STD_CTL_SCODE(387)
CTL_E_GETNOTSUPPORTEDATRUNTIME = STD_CTL_SCODE(393)
CTL_E_GETNOTSUPPORTED = STD_CTL_SCODE(394)
CTL_E_PROPERTYNOTFOUND = STD_CTL_SCODE(422)
CTL_E_INVALIDCLIPBOARDFORMAT = STD_CTL_SCODE(460)
CTL_E_INVALIDPICTURE = STD_CTL_SCODE(481)
CTL_E_PRINTERERROR = STD_CTL_SCODE(482)
CTL_E_CANTSAVEFILETOTEMP = STD_CTL_SCODE(735)
CTL_E_SEARCHTEXTNOTFOUND = STD_CTL_SCODE(744)
CTL_E_REPLACEMENTSTOOLONG = STD_CTL_SCODE(746)
CONNECT_E_FIRST = MAKE_SCODE(winerror.SEVERITY_ERROR, winerror.FACILITY_ITF, 0x0200)
CONNECT_E_LAST = MAKE_SCODE(winerror.SEVERITY_ERROR, winerror.FACILITY_ITF, 0x020F)
CONNECT_S_FIRST = MAKE_SCODE(winerror.SEVERITY_SUCCESS, winerror.FACILITY_ITF, 0x0200)
CONNECT_S_LAST = MAKE_SCODE(winerror.SEVERITY_SUCCESS, winerror.FACILITY_ITF, 0x020F)
CONNECT_E_NOCONNECTION = CONNECT_E_FIRST + 0
CONNECT_E_ADVISELIMIT = CONNECT_E_FIRST + 1
CONNECT_E_CANNOTCONNECT = CONNECT_E_FIRST + 2
CONNECT_E_OVERRIDDEN = CONNECT_E_FIRST + 3
CLASS_E_NOTLICENSED = winerror.CLASSFACTORY_E_FIRST + 2

87
lib/win32com/readme.html Normal file
View file

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>win32com Readme</title>
</head>
<body>
<p><img width="551" height="99" id="_x0000_i1025"
src="html%5Cimage%5Cpycom_blowing.gif"
alt="Python and COM - Blowing the others away"> </p>
<h1>Python COM Extensions Readme </h1>
<p>This is the readme for win32com. Please check out the <a
href="html/docindex.html">win32com documentation index</a></p>
<p>The <a href="test/.">win32com/test directory</a> contains some interesting
scripts (and a new <a href="test/readme.txt">readme.txt</a>). Although these
are used for testing, they do show a variety of COM techniques.</p>
<h3>VARIANT objects</h3>
<p>win32com.client now has explicit VARIANT objects which can be used in
situations where you need more control over the argument types passed when
calling COM methods. See the <a href="html/variant.html">documentation on
this object</a>
<a name="currency"><h3>Important Currency changes</h3></a>
<p>
In all builds prior to 204, a COM currency value was returned as a tuple of
integers. Working with 2 integers to represent a currency object was a poor
choice, but the alternative was never clear. Now Python ships with the
<a href="http://www.python.org/dev/doc/devel/lib/module-decimal.html">decimal</a>
module, the alternative has arrived!
</p>
<p>
Up until build 212, code could set <code>pythoncom.__future_currency__ = True</code>
to force use of the decimal module, with a warning issued otherwise. In
builds 213 and later, the decimal module is unconditionally used when
pythoncon returns you a currency value.
</p>
<h3>Recent Changes</h3>
<h4>Lots of internal changes on the road to py3k</h4>
<h4>win32com.axcontrol and win2con.internet</h4>
Many more interfaces for hosting AX controls and the interfaces
used by Internet Explorer.
<h4>win32com.shell</h4>
The shell interfaces have undergone a number of enhancements and changes.
A couple of methods have changed signature between the first build with shell support (200) and later builds.
SHGetFileInfo was broken in its result handling, so had to be changed - this
is the only function used by the samples that changed, but others not used by the samples also have changed.
These shell interfaces are now generally stable.
<h4>New win32com.taskscheduler module</h4>
Roger Upole has contributed an interface to the Windows task scheduler. This is actually very neat, and it allows
Python to edit the task list as shown by Windows Control Panel. Property page suppport may even appear later,
now that the win32 library has the new win32rcparser module.
<h4>ActiveX Scripting </h4>
<p>Python only supports "trusted" execution hosts - thus, it will no longer work
as an engine inside IE (Python itself no longer has a restricted execution environment).
Python continues to work fine as an Active Scripting Engine in all other
applications, including Windows Scripting Host, and ASP.
<p>There is also support for Python as an ActiveX Scripting Host.</p>
<p>Active Debugging seems to be fully functional.</p>
<h4>Older stuff</h4>
<ul>
</li>
<li>Unexpected exceptions in Python COM objects will generally now dump
the exception and traceback to stdout. &nbsp;This is useful for debugging
and testing - it means that in some cases there will be no need to register
an object with <span style="font-style: italic;">--debug</span> to see these
tracebacks. &nbsp;Note that COM objects used by server processes (such as
ASP) generally have no valid stdout, so will still need to use <span
style="font-style: italic;">--debug</span> as usual.<br>
</li>
<li>universal gateway support has been improved - we can now work as an
Outlook Addin<br>
</li>
</body>
</html>

View file

@ -0,0 +1 @@
# Empty __init__ file to designate a sub-package.

View file

@ -0,0 +1,84 @@
"""Utilities for Server Side connections.
A collection of helpers for server side connection points.
"""
import pythoncom
import win32com.server.util
import winerror
from win32com import olectl
from .exception import Exception
# Methods implemented by the interfaces.
IConnectionPointContainer_methods = ["EnumConnectionPoints", "FindConnectionPoint"]
IConnectionPoint_methods = [
"EnumConnections",
"Unadvise",
"Advise",
"GetConnectionPointContainer",
"GetConnectionInterface",
]
class ConnectableServer:
_public_methods_ = IConnectionPointContainer_methods + IConnectionPoint_methods
_com_interfaces_ = [
pythoncom.IID_IConnectionPoint,
pythoncom.IID_IConnectionPointContainer,
]
# Clients must set _connect_interfaces_ = [...]
def __init__(self):
self.cookieNo = 0
self.connections = {}
# IConnectionPoint interfaces
def EnumConnections(self):
raise Exception(winerror.E_NOTIMPL)
def GetConnectionInterface(self):
raise Exception(winerror.E_NOTIMPL)
def GetConnectionPointContainer(self):
return win32com.server.util.wrap(self)
def Advise(self, pUnk):
# Creates a connection to the client. Simply allocate a new cookie,
# find the clients interface, and store it in a dictionary.
try:
interface = pUnk.QueryInterface(
self._connect_interfaces_[0], pythoncom.IID_IDispatch
)
except pythoncom.com_error:
raise Exception(scode=olectl.CONNECT_E_NOCONNECTION)
self.cookieNo = self.cookieNo + 1
self.connections[self.cookieNo] = interface
return self.cookieNo
def Unadvise(self, cookie):
# Destroy a connection - simply delete interface from the map.
try:
del self.connections[cookie]
except KeyError:
raise Exception(scode=winerror.E_UNEXPECTED)
# IConnectionPointContainer interfaces
def EnumConnectionPoints(self):
raise Exception(winerror.E_NOTIMPL)
def FindConnectionPoint(self, iid):
# Find a connection we support. Only support the single event interface.
if iid in self._connect_interfaces_:
return win32com.server.util.wrap(self)
def _BroadcastNotify(self, broadcaster, extraArgs):
# Broadcasts a notification to all connections.
# Ignores clients that fail.
for interface in self.connections.values():
try:
broadcaster(*(interface,) + extraArgs)
except pythoncom.com_error as details:
self._OnNotifyFail(interface, details)
def _OnNotifyFail(self, interface, details):
print("Ignoring COM error to connection - %s" % (repr(details)))

View file

@ -0,0 +1,291 @@
"""Dispatcher
Please see policy.py for a discussion on dispatchers and policies
"""
import traceback
from sys import exc_info
import pythoncom
import win32api
import win32com
#
from win32com.server.exception import IsCOMServerException
from win32com.util import IIDToInterfaceName
class DispatcherBase:
"""The base class for all Dispatchers.
This dispatcher supports wrapping all operations in exception handlers,
and all the necessary delegation to the policy.
This base class supports the printing of "unexpected" exceptions. Note, however,
that exactly where the output of print goes may not be useful! A derived class may
provide additional semantics for this.
"""
def __init__(self, policyClass, object):
self.policy = policyClass(object)
# The logger we should dump to. If None, we should send to the
# default location (typically 'print')
self.logger = getattr(win32com, "logger", None)
# Note the "return self._HandleException_()" is purely to stop pychecker
# complaining - _HandleException_ will itself raise an exception for the
# pythoncom framework, so the result will never be seen.
def _CreateInstance_(self, clsid, reqIID):
try:
self.policy._CreateInstance_(clsid, reqIID)
return pythoncom.WrapObject(self, reqIID)
except:
return self._HandleException_()
def _QueryInterface_(self, iid):
try:
return self.policy._QueryInterface_(iid)
except:
return self._HandleException_()
def _Invoke_(self, dispid, lcid, wFlags, args):
try:
return self.policy._Invoke_(dispid, lcid, wFlags, args)
except:
return self._HandleException_()
def _GetIDsOfNames_(self, names, lcid):
try:
return self.policy._GetIDsOfNames_(names, lcid)
except:
return self._HandleException_()
def _GetTypeInfo_(self, index, lcid):
try:
return self.policy._GetTypeInfo_(index, lcid)
except:
return self._HandleException_()
def _GetTypeInfoCount_(self):
try:
return self.policy._GetTypeInfoCount_()
except:
return self._HandleException_()
def _GetDispID_(self, name, fdex):
try:
return self.policy._GetDispID_(name, fdex)
except:
return self._HandleException_()
def _InvokeEx_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
try:
return self.policy._InvokeEx_(
dispid, lcid, wFlags, args, kwargs, serviceProvider
)
except:
return self._HandleException_()
def _DeleteMemberByName_(self, name, fdex):
try:
return self.policy._DeleteMemberByName_(name, fdex)
except:
return self._HandleException_()
def _DeleteMemberByDispID_(self, id):
try:
return self.policy._DeleteMemberByDispID_(id)
except:
return self._HandleException_()
def _GetMemberProperties_(self, id, fdex):
try:
return self.policy._GetMemberProperties_(id, fdex)
except:
return self._HandleException_()
def _GetMemberName_(self, dispid):
try:
return self.policy._GetMemberName_(dispid)
except:
return self._HandleException_()
def _GetNextDispID_(self, fdex, flags):
try:
return self.policy._GetNextDispID_(fdex, flags)
except:
return self._HandleException_()
def _GetNameSpaceParent_(self):
try:
return self.policy._GetNameSpaceParent_()
except:
return self._HandleException_()
def _HandleException_(self):
"""Called whenever an exception is raised.
Default behaviour is to print the exception.
"""
# If not a COM exception, print it for the developer.
if not IsCOMServerException():
if self.logger is not None:
self.logger.exception("pythoncom server error")
else:
traceback.print_exc()
# But still raise it for the framework.
raise
def _trace_(self, *args):
if self.logger is not None:
record = " ".join(map(str, args))
self.logger.debug(record)
else:
for arg in args[:-1]:
print(arg, end=" ")
print(args[-1])
class DispatcherTrace(DispatcherBase):
"""A dispatcher, which causes a 'print' line for each COM function called."""
def _QueryInterface_(self, iid):
rc = DispatcherBase._QueryInterface_(self, iid)
if not rc:
self._trace_(
"in %s._QueryInterface_ with unsupported IID %s (%s)"
% (repr(self.policy._obj_), IIDToInterfaceName(iid), iid)
)
return rc
def _GetIDsOfNames_(self, names, lcid):
self._trace_("in _GetIDsOfNames_ with '%s' and '%d'\n" % (names, lcid))
return DispatcherBase._GetIDsOfNames_(self, names, lcid)
def _GetTypeInfo_(self, index, lcid):
self._trace_("in _GetTypeInfo_ with index=%d, lcid=%d\n" % (index, lcid))
return DispatcherBase._GetTypeInfo_(self, index, lcid)
def _GetTypeInfoCount_(self):
self._trace_("in _GetTypeInfoCount_\n")
return DispatcherBase._GetTypeInfoCount_(self)
def _Invoke_(self, dispid, lcid, wFlags, args):
self._trace_("in _Invoke_ with", dispid, lcid, wFlags, args)
return DispatcherBase._Invoke_(self, dispid, lcid, wFlags, args)
def _GetDispID_(self, name, fdex):
self._trace_("in _GetDispID_ with", name, fdex)
return DispatcherBase._GetDispID_(self, name, fdex)
def _InvokeEx_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
self._trace_(
"in %r._InvokeEx_-%s%r [%x,%s,%r]"
% (self.policy._obj_, dispid, args, wFlags, lcid, serviceProvider)
)
return DispatcherBase._InvokeEx_(
self, dispid, lcid, wFlags, args, kwargs, serviceProvider
)
def _DeleteMemberByName_(self, name, fdex):
self._trace_("in _DeleteMemberByName_ with", name, fdex)
return DispatcherBase._DeleteMemberByName_(self, name, fdex)
def _DeleteMemberByDispID_(self, id):
self._trace_("in _DeleteMemberByDispID_ with", id)
return DispatcherBase._DeleteMemberByDispID_(self, id)
def _GetMemberProperties_(self, id, fdex):
self._trace_("in _GetMemberProperties_ with", id, fdex)
return DispatcherBase._GetMemberProperties_(self, id, fdex)
def _GetMemberName_(self, dispid):
self._trace_("in _GetMemberName_ with", dispid)
return DispatcherBase._GetMemberName_(self, dispid)
def _GetNextDispID_(self, fdex, flags):
self._trace_("in _GetNextDispID_ with", fdex, flags)
return DispatcherBase._GetNextDispID_(self, fdex, flags)
def _GetNameSpaceParent_(self):
self._trace_("in _GetNameSpaceParent_")
return DispatcherBase._GetNameSpaceParent_(self)
class DispatcherWin32trace(DispatcherTrace):
"""A tracing dispatcher that sends its output to the win32trace remote collector."""
def __init__(self, policyClass, object):
DispatcherTrace.__init__(self, policyClass, object)
if self.logger is None:
# If we have no logger, setup our output.
import win32traceutil # Sets up everything.
self._trace_(
"Object with win32trace dispatcher created (object=%s)" % repr(object)
)
class DispatcherOutputDebugString(DispatcherTrace):
"""A tracing dispatcher that sends its output to win32api.OutputDebugString"""
def _trace_(self, *args):
for arg in args[:-1]:
win32api.OutputDebugString(str(arg) + " ")
win32api.OutputDebugString(str(args[-1]) + "\n")
class DispatcherWin32dbg(DispatcherBase):
"""A source-level debugger dispatcher
A dispatcher which invokes the debugger as an object is instantiated, or
when an unexpected exception occurs.
Requires Pythonwin.
"""
def __init__(self, policyClass, ob):
# No one uses this, and it just causes py2exe to drag all of
# pythonwin in.
# import pywin.debugger
pywin.debugger.brk()
print("The DispatcherWin32dbg dispatcher is deprecated!")
print("Please let me know if this is a problem.")
print("Uncomment the relevant lines in dispatcher.py to re-enable")
# DEBUGGER Note - You can either:
# * Hit Run and wait for a (non Exception class) exception to occur!
# * Set a breakpoint and hit run.
# * Step into the object creation (a few steps away!)
DispatcherBase.__init__(self, policyClass, ob)
def _HandleException_(self):
"""Invoke the debugger post mortem capability"""
# Save details away.
typ, val, tb = exc_info()
# import pywin.debugger, pywin.debugger.dbgcon
debug = 0
try:
raise typ(val)
except Exception: # AARG - What is this Exception???
# Use some inside knowledge to borrow a Debugger option which dictates if we
# stop at "expected" exceptions.
debug = pywin.debugger.GetDebugger().get_option(
pywin.debugger.dbgcon.OPT_STOP_EXCEPTIONS
)
except:
debug = 1
if debug:
try:
pywin.debugger.post_mortem(tb, typ, val) # The original exception
except:
traceback.print_exc()
# But still raise it.
del tb
raise
try:
import win32trace
DefaultDebugDispatcher = DispatcherWin32trace
except ImportError: # no win32trace module - just use a print based one.
DefaultDebugDispatcher = DispatcherTrace

View file

@ -0,0 +1,105 @@
"""Exception Handling
Exceptions
To better support COM exceptions, the framework allows for an instance to be
raised. This instance may have a certain number of known attributes, which are
translated into COM exception details.
This means, for example, that Python could raise a COM exception that includes details
on a Help file and location, and a description for the user.
This module provides a class which provides the necessary attributes.
"""
import sys
import pythoncom
# Note that we derive from com_error, which derives from exceptions.Exception
# Also note that we dont support "self.args", as we dont support tuple-unpacking
class COMException(pythoncom.com_error):
"""An Exception object that is understood by the framework.
If the framework is presented with an exception of type class,
it looks for certain known attributes on this class to provide rich
error information to the caller.
It should be noted that the framework supports providing this error
information via COM Exceptions, or via the ISupportErrorInfo interface.
By using this class, you automatically provide rich error information to the
server.
"""
def __init__(
self,
description=None,
scode=None,
source=None,
helpfile=None,
helpContext=None,
desc=None,
hresult=None,
):
"""Initialize an exception
**Params**
description -- A string description for the exception.
scode -- An integer scode to be returned to the server, if necessary.
The pythoncom framework defaults this to be DISP_E_EXCEPTION if not specified otherwise.
source -- A string which identifies the source of the error.
helpfile -- A string which points to a help file which contains details on the error.
helpContext -- An integer context in the help file.
desc -- A short-cut for description.
hresult -- A short-cut for scode.
"""
# convert a WIN32 error into an HRESULT
scode = scode or hresult
if scode and scode != 1: # We dont want S_FALSE mapped!
if scode >= -32768 and scode < 32768:
# this is HRESULT_FROM_WIN32()
scode = -2147024896 | (scode & 0x0000FFFF)
self.scode = scode
self.description = description or desc
if scode == 1 and not self.description:
self.description = "S_FALSE"
elif scode and not self.description:
self.description = pythoncom.GetScodeString(scode)
self.source = source
self.helpfile = helpfile
self.helpcontext = helpContext
# todo - fill in the exception value
pythoncom.com_error.__init__(self, scode, self.description, None, -1)
def __repr__(self):
return "<COM Exception - scode=%s, desc=%s>" % (self.scode, self.description)
# Old name for the COMException class.
# Do NOT use the name Exception, as it is now a built-in
# COMException is the new, official name.
Exception = COMException
def IsCOMException(t=None):
if t is None:
t = sys.exc_info()[0]
try:
return issubclass(t, pythoncom.com_error)
except TypeError: # 1.5 in -X mode?
return t is pythoncon.com_error
def IsCOMServerException(t=None):
if t is None:
t = sys.exc_info()[0]
try:
return issubclass(t, COMException)
except TypeError: # String exception
return 0

View file

@ -0,0 +1,26 @@
# Class factory utilities.
import pythoncom
def RegisterClassFactories(clsids, flags=None, clsctx=None):
"""Given a list of CLSID, create and register class factories.
Returns a list, which should be passed to RevokeClassFactories
"""
if flags is None:
flags = pythoncom.REGCLS_MULTIPLEUSE | pythoncom.REGCLS_SUSPENDED
if clsctx is None:
clsctx = pythoncom.CLSCTX_LOCAL_SERVER
ret = []
for clsid in clsids:
# Some server append '-Embedding' etc
if clsid[0] not in ["-", "/"]:
factory = pythoncom.MakePyFactory(clsid)
regId = pythoncom.CoRegisterClassObject(clsid, factory, clsctx, flags)
ret.append((factory, regId))
return ret
def RevokeClassFactories(infos):
for factory, revokeId in infos:
pythoncom.CoRevokeClassObject(revokeId)

View file

@ -0,0 +1,53 @@
# LocalServer .EXE support for Python.
#
# This is designed to be used as a _script_ file by pythonw.exe
#
# In some cases, you could also use Python.exe, which will create
# a console window useful for debugging.
#
# NOTE: When NOT running in any sort of debugging mode,
# 'print' statements may fail, as sys.stdout is not valid!!!
#
# Usage:
# wpython.exe LocalServer.py clsid [, clsid]
import sys
sys.coinit_flags = 2
import pythoncom
import win32api
from win32com.server import factory
usage = """\
Invalid command line arguments
This program provides LocalServer COM support
for Python COM objects.
It is typically run automatically by COM, passing as arguments
The ProgID or CLSID of the Python Server(s) to be hosted
"""
def serve(clsids):
infos = factory.RegisterClassFactories(clsids)
pythoncom.EnableQuitMessage(win32api.GetCurrentThreadId())
pythoncom.CoResumeClassObjects()
pythoncom.PumpMessages()
factory.RevokeClassFactories(infos)
pythoncom.CoUninitialize()
def main():
if len(sys.argv) == 1:
win32api.MessageBox(0, usage, "Python COM Server")
sys.exit(1)
serve(sys.argv[1:])
if __name__ == "__main__":
main()

View file

@ -0,0 +1,829 @@
"""Policies
Note that Dispatchers are now implemented in "dispatcher.py", but
are still documented here.
Policies
A policy is an object which manages the interaction between a public
Python object, and COM . In simple terms, the policy object is the
object which is actually called by COM, and it invokes the requested
method, fetches/sets the requested property, etc. See the
@win32com.server.policy.CreateInstance@ method for a description of
how a policy is specified or created.
Exactly how a policy determines which underlying object method/property
is obtained is up to the policy. A few policies are provided, but you
can build your own. See each policy class for a description of how it
implements its policy.
There is a policy that allows the object to specify exactly which
methods and properties will be exposed. There is also a policy that
will dynamically expose all Python methods and properties - even those
added after the object has been instantiated.
Dispatchers
A Dispatcher is a level in front of a Policy. A dispatcher is the
thing which actually receives the COM calls, and passes them to the
policy object (which in turn somehow does something with the wrapped
object).
It is important to note that a policy does not need to have a dispatcher.
A dispatcher has the same interface as a policy, and simply steps in its
place, delegating to the real policy. The primary use for a Dispatcher
is to support debugging when necessary, but without imposing overheads
when not (ie, by not using a dispatcher at all).
There are a few dispatchers provided - "tracing" dispatchers which simply
prints calls and args (including a variation which uses
win32api.OutputDebugString), and a "debugger" dispatcher, which can
invoke the debugger when necessary.
Error Handling
It is important to realise that the caller of these interfaces may
not be Python. Therefore, general Python exceptions and tracebacks aren't
much use.
In general, there is an Exception class that should be raised, to allow
the framework to extract rich COM type error information.
The general rule is that the **only** exception returned from Python COM
Server code should be an Exception instance. Any other Python exception
should be considered an implementation bug in the server (if not, it
should be handled, and an appropriate Exception instance raised). Any
other exception is considered "unexpected", and a dispatcher may take
special action (see Dispatchers above)
Occasionally, the implementation will raise the policy.error error.
This usually means there is a problem in the implementation that the
Python programmer should fix.
For example, if policy is asked to wrap an object which it can not
support (because, eg, it does not provide _public_methods_ or _dynamic_)
then policy.error will be raised, indicating it is a Python programmers
problem, rather than a COM error.
"""
__author__ = "Greg Stein and Mark Hammond"
import sys
import types
import pythoncom
import pywintypes
import win32api
import win32con
import winerror
# Import a few important constants to speed lookups.
from pythoncom import (
DISPATCH_METHOD,
DISPATCH_PROPERTYGET,
DISPATCH_PROPERTYPUT,
DISPATCH_PROPERTYPUTREF,
DISPID_COLLECT,
DISPID_CONSTRUCTOR,
DISPID_DESTRUCTOR,
DISPID_EVALUATE,
DISPID_NEWENUM,
DISPID_PROPERTYPUT,
DISPID_STARTENUM,
DISPID_UNKNOWN,
DISPID_VALUE,
)
S_OK = 0
# Few more globals to speed things.
IDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
IUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]
from .exception import COMException
error = __name__ + " error"
regSpec = "CLSID\\%s\\PythonCOM"
regPolicy = "CLSID\\%s\\PythonCOMPolicy"
regDispatcher = "CLSID\\%s\\PythonCOMDispatcher"
regAddnPath = "CLSID\\%s\\PythonCOMPath"
def CreateInstance(clsid, reqIID):
"""Create a new instance of the specified IID
The COM framework **always** calls this function to create a new
instance for the specified CLSID. This function looks up the
registry for the name of a policy, creates the policy, and asks the
policy to create the specified object by calling the _CreateInstance_ method.
Exactly how the policy creates the instance is up to the policy. See the
specific policy documentation for more details.
"""
# First see is sys.path should have something on it.
try:
addnPaths = win32api.RegQueryValue(
win32con.HKEY_CLASSES_ROOT, regAddnPath % clsid
).split(";")
for newPath in addnPaths:
if newPath not in sys.path:
sys.path.insert(0, newPath)
except win32api.error:
pass
try:
policy = win32api.RegQueryValue(win32con.HKEY_CLASSES_ROOT, regPolicy % clsid)
policy = resolve_func(policy)
except win32api.error:
policy = DefaultPolicy
try:
dispatcher = win32api.RegQueryValue(
win32con.HKEY_CLASSES_ROOT, regDispatcher % clsid
)
if dispatcher:
dispatcher = resolve_func(dispatcher)
except win32api.error:
dispatcher = None
if dispatcher:
retObj = dispatcher(policy, None)
else:
retObj = policy(None)
return retObj._CreateInstance_(clsid, reqIID)
class BasicWrapPolicy:
"""The base class of policies.
Normally not used directly (use a child class, instead)
This policy assumes we are wrapping another object
as the COM server. This supports the delegation of the core COM entry points
to either the wrapped object, or to a child class.
This policy supports the following special attributes on the wrapped object
_query_interface_ -- A handler which can respond to the COM 'QueryInterface' call.
_com_interfaces_ -- An optional list of IIDs which the interface will assume are
valid for the object.
_invoke_ -- A handler which can respond to the COM 'Invoke' call. If this attribute
is not provided, then the default policy implementation is used. If this attribute
does exist, it is responsible for providing all required functionality - ie, the
policy _invoke_ method is not invoked at all (and nor are you able to call it!)
_getidsofnames_ -- A handler which can respond to the COM 'GetIDsOfNames' call. If this attribute
is not provided, then the default policy implementation is used. If this attribute
does exist, it is responsible for providing all required functionality - ie, the
policy _getidsofnames_ method is not invoked at all (and nor are you able to call it!)
IDispatchEx functionality:
_invokeex_ -- Very similar to _invoke_, except slightly different arguments are used.
And the result is just the _real_ result (rather than the (hresult, argErr, realResult)
tuple that _invoke_ uses.
This is the new, prefered handler (the default _invoke_ handler simply called _invokeex_)
_getdispid_ -- Very similar to _getidsofnames_, except slightly different arguments are used,
and only 1 property at a time can be fetched (which is all we support in getidsofnames anyway!)
This is the new, prefered handler (the default _invoke_ handler simply called _invokeex_)
_getnextdispid_- uses self._name_to_dispid_ to enumerate the DISPIDs
"""
def __init__(self, object):
"""Initialise the policy object
Params:
object -- The object to wrap. May be None *iff* @BasicWrapPolicy._CreateInstance_@ will be
called immediately after this to setup a brand new object
"""
if object is not None:
self._wrap_(object)
def _CreateInstance_(self, clsid, reqIID):
"""Creates a new instance of a **wrapped** object
This method looks up a "@win32com.server.policy.regSpec@" % clsid entry
in the registry (using @DefaultPolicy@)
"""
try:
classSpec = win32api.RegQueryValue(
win32con.HKEY_CLASSES_ROOT, regSpec % clsid
)
except win32api.error:
raise error(
"The object is not correctly registered - %s key can not be read"
% (regSpec % clsid)
)
myob = call_func(classSpec)
self._wrap_(myob)
try:
return pythoncom.WrapObject(self, reqIID)
except pythoncom.com_error as xxx_todo_changeme:
(hr, desc, exc, arg) = xxx_todo_changeme.args
from win32com.util import IIDToInterfaceName
desc = (
"The object '%r' was created, but does not support the "
"interface '%s'(%s): %s"
% (myob, IIDToInterfaceName(reqIID), reqIID, desc)
)
raise pythoncom.com_error(hr, desc, exc, arg)
def _wrap_(self, object):
"""Wraps up the specified object.
This function keeps a reference to the passed
object, and may interogate it to determine how to respond to COM requests, etc.
"""
# We "clobber" certain of our own methods with ones
# provided by the wrapped object, iff they exist.
self._name_to_dispid_ = {}
ob = self._obj_ = object
if hasattr(ob, "_query_interface_"):
self._query_interface_ = ob._query_interface_
if hasattr(ob, "_invoke_"):
self._invoke_ = ob._invoke_
if hasattr(ob, "_invokeex_"):
self._invokeex_ = ob._invokeex_
if hasattr(ob, "_getidsofnames_"):
self._getidsofnames_ = ob._getidsofnames_
if hasattr(ob, "_getdispid_"):
self._getdispid_ = ob._getdispid_
# Allow for override of certain special attributes.
if hasattr(ob, "_com_interfaces_"):
self._com_interfaces_ = []
# Allow interfaces to be specified by name.
for i in ob._com_interfaces_:
if type(i) != pywintypes.IIDType:
# Prolly a string!
if i[0] != "{":
i = pythoncom.InterfaceNames[i]
else:
i = pythoncom.MakeIID(i)
self._com_interfaces_.append(i)
else:
self._com_interfaces_ = []
# "QueryInterface" handling.
def _QueryInterface_(self, iid):
"""The main COM entry-point for QueryInterface.
This checks the _com_interfaces_ attribute and if the interface is not specified
there, it calls the derived helper _query_interface_
"""
if iid in self._com_interfaces_:
return 1
return self._query_interface_(iid)
def _query_interface_(self, iid):
"""Called if the object does not provide the requested interface in _com_interfaces_,
and does not provide a _query_interface_ handler.
Returns a result to the COM framework indicating the interface is not supported.
"""
return 0
# "Invoke" handling.
def _Invoke_(self, dispid, lcid, wFlags, args):
"""The main COM entry-point for Invoke.
This calls the _invoke_ helper.
"""
# Translate a possible string dispid to real dispid.
if type(dispid) == type(""):
try:
dispid = self._name_to_dispid_[dispid.lower()]
except KeyError:
raise COMException(
scode=winerror.DISP_E_MEMBERNOTFOUND, desc="Member not found"
)
return self._invoke_(dispid, lcid, wFlags, args)
def _invoke_(self, dispid, lcid, wFlags, args):
# Delegates to the _invokeex_ implementation. This allows
# a custom policy to define _invokeex_, and automatically get _invoke_ too.
return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None)
# "GetIDsOfNames" handling.
def _GetIDsOfNames_(self, names, lcid):
"""The main COM entry-point for GetIDsOfNames.
This checks the validity of the arguments, and calls the _getidsofnames_ helper.
"""
if len(names) > 1:
raise COMException(
scode=winerror.DISP_E_INVALID,
desc="Cannot support member argument names",
)
return self._getidsofnames_(names, lcid)
def _getidsofnames_(self, names, lcid):
### note: lcid is being ignored...
return (self._getdispid_(names[0], 0),)
# IDispatchEx support for policies. Most of the IDispathEx functionality
# by default will raise E_NOTIMPL. Thus it is not necessary for derived
# policies to explicitely implement all this functionality just to not implement it!
def _GetDispID_(self, name, fdex):
return self._getdispid_(name, fdex)
def _getdispid_(self, name, fdex):
try:
### TODO - look at the fdex flags!!!
return self._name_to_dispid_[name.lower()]
except KeyError:
raise COMException(scode=winerror.DISP_E_UNKNOWNNAME)
# "InvokeEx" handling.
def _InvokeEx_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
"""The main COM entry-point for InvokeEx.
This calls the _invokeex_ helper.
"""
# Translate a possible string dispid to real dispid.
if type(dispid) == type(""):
try:
dispid = self._name_to_dispid_[dispid.lower()]
except KeyError:
raise COMException(
scode=winerror.DISP_E_MEMBERNOTFOUND, desc="Member not found"
)
return self._invokeex_(dispid, lcid, wFlags, args, kwargs, serviceProvider)
def _invokeex_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
"""A stub for _invokeex_ - should never be called.
Simply raises an exception.
"""
# Base classes should override this method (and not call the base)
raise error("This class does not provide _invokeex_ semantics")
def _DeleteMemberByName_(self, name, fdex):
return self._deletememberbyname_(name, fdex)
def _deletememberbyname_(self, name, fdex):
raise COMException(scode=winerror.E_NOTIMPL)
def _DeleteMemberByDispID_(self, id):
return self._deletememberbydispid(id)
def _deletememberbydispid_(self, id):
raise COMException(scode=winerror.E_NOTIMPL)
def _GetMemberProperties_(self, id, fdex):
return self._getmemberproperties_(id, fdex)
def _getmemberproperties_(self, id, fdex):
raise COMException(scode=winerror.E_NOTIMPL)
def _GetMemberName_(self, dispid):
return self._getmembername_(dispid)
def _getmembername_(self, dispid):
raise COMException(scode=winerror.E_NOTIMPL)
def _GetNextDispID_(self, fdex, dispid):
return self._getnextdispid_(fdex, dispid)
def _getnextdispid_(self, fdex, dispid):
ids = list(self._name_to_dispid_.values())
ids.sort()
if DISPID_STARTENUM in ids:
ids.remove(DISPID_STARTENUM)
if dispid == DISPID_STARTENUM:
return ids[0]
else:
try:
return ids[ids.index(dispid) + 1]
except ValueError: # dispid not in list?
raise COMException(scode=winerror.E_UNEXPECTED)
except IndexError: # No more items
raise COMException(scode=winerror.S_FALSE)
def _GetNameSpaceParent_(self):
return self._getnamespaceparent()
def _getnamespaceparent_(self):
raise COMException(scode=winerror.E_NOTIMPL)
class MappedWrapPolicy(BasicWrapPolicy):
"""Wraps an object using maps to do its magic
This policy wraps up a Python object, using a number of maps
which translate from a Dispatch ID and flags, into an object to call/getattr, etc.
It is the responsibility of derived classes to determine exactly how the
maps are filled (ie, the derived classes determine the map filling policy.
This policy supports the following special attributes on the wrapped object
_dispid_to_func_/_dispid_to_get_/_dispid_to_put_ -- These are dictionaries
(keyed by integer dispid, values are string attribute names) which the COM
implementation uses when it is processing COM requests. Note that the implementation
uses this dictionary for its own purposes - not a copy - which means the contents of
these dictionaries will change as the object is used.
"""
def _wrap_(self, object):
BasicWrapPolicy._wrap_(self, object)
ob = self._obj_
if hasattr(ob, "_dispid_to_func_"):
self._dispid_to_func_ = ob._dispid_to_func_
else:
self._dispid_to_func_ = {}
if hasattr(ob, "_dispid_to_get_"):
self._dispid_to_get_ = ob._dispid_to_get_
else:
self._dispid_to_get_ = {}
if hasattr(ob, "_dispid_to_put_"):
self._dispid_to_put_ = ob._dispid_to_put_
else:
self._dispid_to_put_ = {}
def _getmembername_(self, dispid):
if dispid in self._dispid_to_func_:
return self._dispid_to_func_[dispid]
elif dispid in self._dispid_to_get_:
return self._dispid_to_get_[dispid]
elif dispid in self._dispid_to_put_:
return self._dispid_to_put_[dispid]
else:
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND)
class DesignatedWrapPolicy(MappedWrapPolicy):
"""A policy which uses a mapping to link functions and dispid
A MappedWrappedPolicy which allows the wrapped object to specify, via certain
special named attributes, exactly which methods and properties are exposed.
All a wrapped object need do is provide the special attributes, and the policy
will handle everything else.
Attributes:
_public_methods_ -- Required, unless a typelib GUID is given -- A list
of strings, which must be the names of methods the object
provides. These methods will be exposed and callable
from other COM hosts.
_public_attrs_ A list of strings, which must be the names of attributes on the object.
These attributes will be exposed and readable and possibly writeable from other COM hosts.
_readonly_attrs_ -- A list of strings, which must also appear in _public_attrs. These
attributes will be readable, but not writable, by other COM hosts.
_value_ -- A method that will be called if the COM host requests the "default" method
(ie, calls Invoke with dispid==DISPID_VALUE)
_NewEnum -- A method that will be called if the COM host requests an enumerator on the
object (ie, calls Invoke with dispid==DISPID_NEWENUM.)
It is the responsibility of the method to ensure the returned
object conforms to the required Enum interface.
_typelib_guid_ -- The GUID of the typelibrary with interface definitions we use.
_typelib_version_ -- A tuple of (major, minor) with a default of 1,1
_typelib_lcid_ -- The LCID of the typelib, default = LOCALE_USER_DEFAULT
_Evaluate -- Dunno what this means, except the host has called Invoke with dispid==DISPID_EVALUATE!
See the COM documentation for details.
"""
def _wrap_(self, ob):
# If we have nominated universal interfaces to support, load them now
tlb_guid = getattr(ob, "_typelib_guid_", None)
if tlb_guid is not None:
tlb_major, tlb_minor = getattr(ob, "_typelib_version_", (1, 0))
tlb_lcid = getattr(ob, "_typelib_lcid_", 0)
from win32com import universal
# XXX - what if the user wants to implement interfaces from multiple
# typelibs?
# Filter out all 'normal' IIDs (ie, IID objects and strings starting with {
interfaces = [
i
for i in getattr(ob, "_com_interfaces_", [])
if type(i) != pywintypes.IIDType and not i.startswith("{")
]
universal_data = universal.RegisterInterfaces(
tlb_guid, tlb_lcid, tlb_major, tlb_minor, interfaces
)
else:
universal_data = []
MappedWrapPolicy._wrap_(self, ob)
if not hasattr(ob, "_public_methods_") and not hasattr(ob, "_typelib_guid_"):
raise error(
"Object does not support DesignatedWrapPolicy, as it does not have either _public_methods_ or _typelib_guid_ attributes."
)
# Copy existing _dispid_to_func_ entries to _name_to_dispid_
for dispid, name in self._dispid_to_func_.items():
self._name_to_dispid_[name.lower()] = dispid
for dispid, name in self._dispid_to_get_.items():
self._name_to_dispid_[name.lower()] = dispid
for dispid, name in self._dispid_to_put_.items():
self._name_to_dispid_[name.lower()] = dispid
# Patch up the universal stuff.
for dispid, invkind, name in universal_data:
self._name_to_dispid_[name.lower()] = dispid
if invkind == DISPATCH_METHOD:
self._dispid_to_func_[dispid] = name
elif invkind in (DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF):
self._dispid_to_put_[dispid] = name
elif invkind == DISPATCH_PROPERTYGET:
self._dispid_to_get_[dispid] = name
else:
raise ValueError("unexpected invkind: %d (%s)" % (invkind, name))
# look for reserved methods
if hasattr(ob, "_value_"):
self._dispid_to_get_[DISPID_VALUE] = "_value_"
self._dispid_to_put_[DISPID_PROPERTYPUT] = "_value_"
if hasattr(ob, "_NewEnum"):
self._name_to_dispid_["_newenum"] = DISPID_NEWENUM
self._dispid_to_func_[DISPID_NEWENUM] = "_NewEnum"
if hasattr(ob, "_Evaluate"):
self._name_to_dispid_["_evaluate"] = DISPID_EVALUATE
self._dispid_to_func_[DISPID_EVALUATE] = "_Evaluate"
next_dispid = self._allocnextdispid(999)
# note: funcs have precedence over attrs (install attrs first)
if hasattr(ob, "_public_attrs_"):
if hasattr(ob, "_readonly_attrs_"):
readonly = ob._readonly_attrs_
else:
readonly = []
for name in ob._public_attrs_:
dispid = self._name_to_dispid_.get(name.lower())
if dispid is None:
dispid = next_dispid
self._name_to_dispid_[name.lower()] = dispid
next_dispid = self._allocnextdispid(next_dispid)
self._dispid_to_get_[dispid] = name
if name not in readonly:
self._dispid_to_put_[dispid] = name
for name in getattr(ob, "_public_methods_", []):
dispid = self._name_to_dispid_.get(name.lower())
if dispid is None:
dispid = next_dispid
self._name_to_dispid_[name.lower()] = dispid
next_dispid = self._allocnextdispid(next_dispid)
self._dispid_to_func_[dispid] = name
self._typeinfos_ = None # load these on demand.
def _build_typeinfos_(self):
# Can only ever be one for now.
tlb_guid = getattr(self._obj_, "_typelib_guid_", None)
if tlb_guid is None:
return []
tlb_major, tlb_minor = getattr(self._obj_, "_typelib_version_", (1, 0))
tlb = pythoncom.LoadRegTypeLib(tlb_guid, tlb_major, tlb_minor)
typecomp = tlb.GetTypeComp()
# Not 100% sure what semantics we should use for the default interface.
# Look for the first name in _com_interfaces_ that exists in the typelib.
for iname in self._obj_._com_interfaces_:
try:
type_info, type_comp = typecomp.BindType(iname)
if type_info is not None:
return [type_info]
except pythoncom.com_error:
pass
return []
def _GetTypeInfoCount_(self):
if self._typeinfos_ is None:
self._typeinfos_ = self._build_typeinfos_()
return len(self._typeinfos_)
def _GetTypeInfo_(self, index, lcid):
if self._typeinfos_ is None:
self._typeinfos_ = self._build_typeinfos_()
if index < 0 or index >= len(self._typeinfos_):
raise COMException(scode=winerror.DISP_E_BADINDEX)
return 0, self._typeinfos_[index]
def _allocnextdispid(self, last_dispid):
while 1:
last_dispid = last_dispid + 1
if (
last_dispid not in self._dispid_to_func_
and last_dispid not in self._dispid_to_get_
and last_dispid not in self._dispid_to_put_
):
return last_dispid
def _invokeex_(self, dispid, lcid, wFlags, args, kwArgs, serviceProvider):
### note: lcid is being ignored...
if wFlags & DISPATCH_METHOD:
try:
funcname = self._dispid_to_func_[dispid]
except KeyError:
if not wFlags & DISPATCH_PROPERTYGET:
raise COMException(
scode=winerror.DISP_E_MEMBERNOTFOUND
) # not found
else:
try:
func = getattr(self._obj_, funcname)
except AttributeError:
# May have a dispid, but that doesnt mean we have the function!
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND)
# Should check callable here
try:
return func(*args)
except TypeError as v:
# Particularly nasty is "wrong number of args" type error
# This helps you see what 'func' and 'args' actually is
if str(v).find("arguments") >= 0:
print(
"** TypeError %s calling function %r(%r)" % (v, func, args)
)
raise
if wFlags & DISPATCH_PROPERTYGET:
try:
name = self._dispid_to_get_[dispid]
except KeyError:
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) # not found
retob = getattr(self._obj_, name)
if type(retob) == types.MethodType: # a method as a property - call it.
retob = retob(*args)
return retob
if wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF): ### correct?
try:
name = self._dispid_to_put_[dispid]
except KeyError:
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) # read-only
# If we have a method of that name (ie, a property get function), and
# we have an equiv. property set function, use that instead.
if (
type(getattr(self._obj_, name, None)) == types.MethodType
and type(getattr(self._obj_, "Set" + name, None)) == types.MethodType
):
fn = getattr(self._obj_, "Set" + name)
fn(*args)
else:
# just set the attribute
setattr(self._obj_, name, args[0])
return
raise COMException(scode=winerror.E_INVALIDARG, desc="invalid wFlags")
class EventHandlerPolicy(DesignatedWrapPolicy):
"""The default policy used by event handlers in the win32com.client package.
In addition to the base policy, this provides argument conversion semantics for
params
* dispatch params are converted to dispatch objects.
* Unicode objects are converted to strings (1.5.2 and earlier)
NOTE: Later, we may allow the object to override this process??
"""
def _transform_args_(self, args, kwArgs, dispid, lcid, wFlags, serviceProvider):
ret = []
for arg in args:
arg_type = type(arg)
if arg_type == IDispatchType:
import win32com.client
arg = win32com.client.Dispatch(arg)
elif arg_type == IUnknownType:
try:
import win32com.client
arg = win32com.client.Dispatch(
arg.QueryInterface(pythoncom.IID_IDispatch)
)
except pythoncom.error:
pass # Keep it as IUnknown
ret.append(arg)
return tuple(ret), kwArgs
def _invokeex_(self, dispid, lcid, wFlags, args, kwArgs, serviceProvider):
# transform the args.
args, kwArgs = self._transform_args_(
args, kwArgs, dispid, lcid, wFlags, serviceProvider
)
return DesignatedWrapPolicy._invokeex_(
self, dispid, lcid, wFlags, args, kwArgs, serviceProvider
)
class DynamicPolicy(BasicWrapPolicy):
"""A policy which dynamically (ie, at run-time) determines public interfaces.
A dynamic policy is used to dynamically dispatch methods and properties to the
wrapped object. The list of objects and properties does not need to be known in
advance, and methods or properties added to the wrapped object after construction
are also handled.
The wrapped object must provide the following attributes:
_dynamic_ -- A method that will be called whenever an invoke on the object
is called. The method is called with the name of the underlying method/property
(ie, the mapping of dispid to/from name has been resolved.) This name property
may also be '_value_' to indicate the default, and '_NewEnum' to indicate a new
enumerator is requested.
"""
def _wrap_(self, object):
BasicWrapPolicy._wrap_(self, object)
if not hasattr(self._obj_, "_dynamic_"):
raise error("Object does not support Dynamic COM Policy")
self._next_dynamic_ = self._min_dynamic_ = 1000
self._dyn_dispid_to_name_ = {
DISPID_VALUE: "_value_",
DISPID_NEWENUM: "_NewEnum",
}
def _getdispid_(self, name, fdex):
# TODO - Look at fdex flags.
lname = name.lower()
try:
return self._name_to_dispid_[lname]
except KeyError:
dispid = self._next_dynamic_ = self._next_dynamic_ + 1
self._name_to_dispid_[lname] = dispid
self._dyn_dispid_to_name_[dispid] = name # Keep case in this map...
return dispid
def _invoke_(self, dispid, lcid, wFlags, args):
return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None)
def _invokeex_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
### note: lcid is being ignored...
### note: kwargs is being ignored...
### note: serviceProvider is being ignored...
### there might be assigned DISPID values to properties, too...
try:
name = self._dyn_dispid_to_name_[dispid]
except KeyError:
raise COMException(
scode=winerror.DISP_E_MEMBERNOTFOUND, desc="Member not found"
)
return self._obj_._dynamic_(name, lcid, wFlags, args)
DefaultPolicy = DesignatedWrapPolicy
def resolve_func(spec):
"""Resolve a function by name
Given a function specified by 'module.function', return a callable object
(ie, the function itself)
"""
try:
idx = spec.rindex(".")
mname = spec[:idx]
fname = spec[idx + 1 :]
# Dont attempt to optimize by looking in sys.modules,
# as another thread may also be performing the import - this
# way we take advantage of the built-in import lock.
module = _import_module(mname)
return getattr(module, fname)
except ValueError: # No "." in name - assume in this module
return globals()[spec]
def call_func(spec, *args):
"""Call a function specified by name.
Call a function specified by 'module.function' and return the result.
"""
return resolve_func(spec)(*args)
def _import_module(mname):
"""Import a module just like the 'import' statement.
Having this function is much nicer for importing arbitrary modules than
using the 'exec' keyword. It is more efficient and obvious to the reader.
"""
__import__(mname)
# Eeek - result of _import_ is "win32com" - not "win32com.a.b.c"
# Get the full module from sys.modules
return sys.modules[mname]
#######
#
# Temporary hacks until all old code moves.
#
# These have been moved to a new source file, but some code may
# still reference them here. These will end up being removed.
try:
from .dispatcher import DispatcherTrace, DispatcherWin32trace
except ImportError: # Quite likely a frozen executable that doesnt need dispatchers
pass

View file

@ -0,0 +1,672 @@
"""Utilities for registering objects.
This module contains utility functions to register Python objects as
valid COM Servers. The RegisterServer function provides all information
necessary to allow the COM framework to respond to a request for a COM object,
construct the necessary Python object, and dispatch COM events.
"""
import os
import sys
import pythoncom
import win32api
import win32con
import winerror
CATID_PythonCOMServer = "{B3EF80D0-68E2-11D0-A689-00C04FD658FF}"
def _set_subkeys(keyName, valueDict, base=win32con.HKEY_CLASSES_ROOT):
hkey = win32api.RegCreateKey(base, keyName)
try:
for key, value in valueDict.items():
win32api.RegSetValueEx(hkey, key, None, win32con.REG_SZ, value)
finally:
win32api.RegCloseKey(hkey)
def _set_string(path, value, base=win32con.HKEY_CLASSES_ROOT):
"Set a string value in the registry."
win32api.RegSetValue(base, path, win32con.REG_SZ, value)
def _get_string(path, base=win32con.HKEY_CLASSES_ROOT):
"Get a string value from the registry."
try:
return win32api.RegQueryValue(base, path)
except win32api.error:
return None
def _remove_key(path, base=win32con.HKEY_CLASSES_ROOT):
"Remove a string from the registry."
try:
win32api.RegDeleteKey(base, path)
except win32api.error as xxx_todo_changeme1:
(code, fn, msg) = xxx_todo_changeme1.args
if code != winerror.ERROR_FILE_NOT_FOUND:
raise win32api.error(code, fn, msg)
def recurse_delete_key(path, base=win32con.HKEY_CLASSES_ROOT):
"""Recursively delete registry keys.
This is needed since you can't blast a key when subkeys exist.
"""
try:
h = win32api.RegOpenKey(base, path)
except win32api.error as xxx_todo_changeme2:
(code, fn, msg) = xxx_todo_changeme2.args
if code != winerror.ERROR_FILE_NOT_FOUND:
raise win32api.error(code, fn, msg)
else:
# parent key found and opened successfully. do some work, making sure
# to always close the thing (error or no).
try:
# remove all of the subkeys
while 1:
try:
subkeyname = win32api.RegEnumKey(h, 0)
except win32api.error as xxx_todo_changeme:
(code, fn, msg) = xxx_todo_changeme.args
if code != winerror.ERROR_NO_MORE_ITEMS:
raise win32api.error(code, fn, msg)
break
recurse_delete_key(path + "\\" + subkeyname, base)
# remove the parent key
_remove_key(path, base)
finally:
win32api.RegCloseKey(h)
def _cat_registrar():
return pythoncom.CoCreateInstance(
pythoncom.CLSID_StdComponentCategoriesMgr,
None,
pythoncom.CLSCTX_INPROC_SERVER,
pythoncom.IID_ICatRegister,
)
def _find_localserver_exe(mustfind):
if not sys.platform.startswith("win32"):
return sys.executable
if pythoncom.__file__.find("_d") < 0:
exeBaseName = "pythonw.exe"
else:
exeBaseName = "pythonw_d.exe"
# First see if in the same directory as this .EXE
exeName = os.path.join(os.path.split(sys.executable)[0], exeBaseName)
if not os.path.exists(exeName):
# See if in our sys.prefix directory
exeName = os.path.join(sys.prefix, exeBaseName)
if not os.path.exists(exeName):
# See if in our sys.prefix/pcbuild directory (for developers)
if "64 bit" in sys.version:
exeName = os.path.join(sys.prefix, "PCbuild", "amd64", exeBaseName)
else:
exeName = os.path.join(sys.prefix, "PCbuild", exeBaseName)
if not os.path.exists(exeName):
# See if the registry has some info.
try:
key = "SOFTWARE\\Python\\PythonCore\\%s\\InstallPath" % sys.winver
path = win32api.RegQueryValue(win32con.HKEY_LOCAL_MACHINE, key)
exeName = os.path.join(path, exeBaseName)
except (AttributeError, win32api.error):
pass
if not os.path.exists(exeName):
if mustfind:
raise RuntimeError("Can not locate the program '%s'" % exeBaseName)
return None
return exeName
def _find_localserver_module():
import win32com.server
path = win32com.server.__path__[0]
baseName = "localserver"
pyfile = os.path.join(path, baseName + ".py")
try:
os.stat(pyfile)
except os.error:
# See if we have a compiled extension
if __debug__:
ext = ".pyc"
else:
ext = ".pyo"
pyfile = os.path.join(path, baseName + ext)
try:
os.stat(pyfile)
except os.error:
raise RuntimeError(
"Can not locate the Python module 'win32com.server.%s'" % baseName
)
return pyfile
def RegisterServer(
clsid,
pythonInstString=None,
desc=None,
progID=None,
verProgID=None,
defIcon=None,
threadingModel="both",
policy=None,
catids=[],
other={},
addPyComCat=None,
dispatcher=None,
clsctx=None,
addnPath=None,
):
"""Registers a Python object as a COM Server. This enters almost all necessary
information in the system registry, allowing COM to use the object.
clsid -- The (unique) CLSID of the server.
pythonInstString -- A string holding the instance name that will be created
whenever COM requests a new object.
desc -- The description of the COM object.
progID -- The user name of this object (eg, Word.Document)
verProgId -- The user name of this version's implementation (eg Word.6.Document)
defIcon -- The default icon for the object.
threadingModel -- The threading model this object supports.
policy -- The policy to use when creating this object.
catids -- A list of category ID's this object belongs in.
other -- A dictionary of extra items to be registered.
addPyComCat -- A flag indicating if the object should be added to the list
of Python servers installed on the machine. If None (the default)
then it will be registered when running from python source, but
not registered if running in a frozen environment.
dispatcher -- The dispatcher to use when creating this object.
clsctx -- One of the CLSCTX_* constants.
addnPath -- An additional path the COM framework will add to sys.path
before attempting to create the object.
"""
### backwards-compat check
### Certain policies do not require a "class name", just the policy itself.
if not pythonInstString and not policy:
raise TypeError(
"You must specify either the Python Class or Python Policy which implement the COM object."
)
keyNameRoot = "CLSID\\%s" % str(clsid)
_set_string(keyNameRoot, desc)
# Also register as an "Application" so DCOM etc all see us.
_set_string("AppID\\%s" % clsid, progID)
# Depending on contexts requested, register the specified server type.
# Set default clsctx.
if not clsctx:
clsctx = pythoncom.CLSCTX_INPROC_SERVER | pythoncom.CLSCTX_LOCAL_SERVER
# And if we are frozen, ignore the ones that don't make sense in this
# context.
if pythoncom.frozen:
assert (
sys.frozen
), "pythoncom is frozen, but sys.frozen is not set - don't know the context!"
if sys.frozen == "dll":
clsctx = clsctx & pythoncom.CLSCTX_INPROC_SERVER
else:
clsctx = clsctx & pythoncom.CLSCTX_LOCAL_SERVER
# Now setup based on the clsctx left over.
if clsctx & pythoncom.CLSCTX_INPROC_SERVER:
# get the module to use for registration.
# nod to Gordon's installer - if sys.frozen and sys.frozendllhandle
# exist, then we are being registered via a DLL - use this DLL as the
# file name.
if pythoncom.frozen:
if hasattr(sys, "frozendllhandle"):
dllName = win32api.GetModuleFileName(sys.frozendllhandle)
else:
raise RuntimeError(
"We appear to have a frozen DLL, but I don't know the DLL to use"
)
else:
# Normal case - running from .py file, so register pythoncom's DLL.
# Although now we prefer a 'loader' DLL if it exists to avoid some
# manifest issues (the 'loader' DLL has a manifest, but pythoncom does not)
pythoncom_dir = os.path.dirname(pythoncom.__file__)
suffix = "_d" if "_d" in pythoncom.__file__ else ""
# Always register with the full path to the DLLs.
loadername = os.path.join(
pythoncom_dir,
"pythoncomloader%d%d%s.dll"
% (sys.version_info[0], sys.version_info[1], suffix),
)
dllName = loadername if os.path.isfile(loadername) else pythoncom.__file__
_set_subkeys(
keyNameRoot + "\\InprocServer32",
{
None: dllName,
"ThreadingModel": threadingModel,
},
)
else: # Remove any old InProcServer32 registrations
_remove_key(keyNameRoot + "\\InprocServer32")
if clsctx & pythoncom.CLSCTX_LOCAL_SERVER:
if pythoncom.frozen:
# If we are frozen, we write "{exe} /Automate", just
# like "normal" .EXEs do
exeName = win32api.GetShortPathName(sys.executable)
command = "%s /Automate" % (exeName,)
else:
# Running from .py sources - we need to write
# 'python.exe win32com\server\localserver.py {clsid}"
exeName = _find_localserver_exe(1)
exeName = win32api.GetShortPathName(exeName)
pyfile = _find_localserver_module()
command = '%s "%s" %s' % (exeName, pyfile, str(clsid))
_set_string(keyNameRoot + "\\LocalServer32", command)
else: # Remove any old LocalServer32 registrations
_remove_key(keyNameRoot + "\\LocalServer32")
if pythonInstString:
_set_string(keyNameRoot + "\\PythonCOM", pythonInstString)
else:
_remove_key(keyNameRoot + "\\PythonCOM")
if policy:
_set_string(keyNameRoot + "\\PythonCOMPolicy", policy)
else:
_remove_key(keyNameRoot + "\\PythonCOMPolicy")
if dispatcher:
_set_string(keyNameRoot + "\\PythonCOMDispatcher", dispatcher)
else:
_remove_key(keyNameRoot + "\\PythonCOMDispatcher")
if defIcon:
_set_string(keyNameRoot + "\\DefaultIcon", defIcon)
else:
_remove_key(keyNameRoot + "\\DefaultIcon")
if addnPath:
_set_string(keyNameRoot + "\\PythonCOMPath", addnPath)
else:
_remove_key(keyNameRoot + "\\PythonCOMPath")
if addPyComCat is None:
addPyComCat = pythoncom.frozen == 0
if addPyComCat:
catids = catids + [CATID_PythonCOMServer]
# Set up the implemented categories
if catids:
regCat = _cat_registrar()
regCat.RegisterClassImplCategories(clsid, catids)
# set up any other reg values they might have
if other:
for key, value in other.items():
_set_string(keyNameRoot + "\\" + key, value)
if progID:
# set the progID as the most specific that was given to us
if verProgID:
_set_string(keyNameRoot + "\\ProgID", verProgID)
else:
_set_string(keyNameRoot + "\\ProgID", progID)
# Set up the root entries - version independent.
if desc:
_set_string(progID, desc)
_set_string(progID + "\\CLSID", str(clsid))
# Set up the root entries - version dependent.
if verProgID:
# point from independent to the current version
_set_string(progID + "\\CurVer", verProgID)
# point to the version-independent one
_set_string(keyNameRoot + "\\VersionIndependentProgID", progID)
# set up the versioned progID
if desc:
_set_string(verProgID, desc)
_set_string(verProgID + "\\CLSID", str(clsid))
def GetUnregisterServerKeys(clsid, progID=None, verProgID=None, customKeys=None):
"""Given a server, return a list of of ("key", root), which are keys recursively
and uncondtionally deleted at unregister or uninstall time.
"""
# remove the main CLSID registration
ret = [("CLSID\\%s" % str(clsid), win32con.HKEY_CLASSES_ROOT)]
# remove the versioned ProgID registration
if verProgID:
ret.append((verProgID, win32con.HKEY_CLASSES_ROOT))
# blow away the independent ProgID. we can't leave it since we just
# torched the class.
### could potentially check the CLSID... ?
if progID:
ret.append((progID, win32con.HKEY_CLASSES_ROOT))
# The DCOM config tool may write settings to the AppID key for our CLSID
ret.append(("AppID\\%s" % str(clsid), win32con.HKEY_CLASSES_ROOT))
# Any custom keys?
if customKeys:
ret = ret + customKeys
return ret
def UnregisterServer(clsid, progID=None, verProgID=None, customKeys=None):
"""Unregisters a Python COM server."""
for args in GetUnregisterServerKeys(clsid, progID, verProgID, customKeys):
recurse_delete_key(*args)
### it might be nice at some point to "roll back" the independent ProgID
### to an earlier version if one exists, and just blowing away the
### specified version of the ProgID (and its corresponding CLSID)
### another time, though...
### NOTE: ATL simply blows away the above three keys without the
### potential checks that I describe. Assuming that defines the
### "standard" then we have no additional changes necessary.
def GetRegisteredServerOption(clsid, optionName):
"""Given a CLSID for a server and option name, return the option value"""
keyNameRoot = "CLSID\\%s\\%s" % (str(clsid), str(optionName))
return _get_string(keyNameRoot)
def _get(ob, attr, default=None):
try:
return getattr(ob, attr)
except AttributeError:
pass
# look down sub-classes
try:
bases = ob.__bases__
except AttributeError:
# ob is not a class - no probs.
return default
for base in bases:
val = _get(base, attr, None)
if val is not None:
return val
return default
def RegisterClasses(*classes, **flags):
quiet = "quiet" in flags and flags["quiet"]
debugging = "debug" in flags and flags["debug"]
for cls in classes:
clsid = cls._reg_clsid_
progID = _get(cls, "_reg_progid_")
desc = _get(cls, "_reg_desc_", progID)
spec = _get(cls, "_reg_class_spec_")
verProgID = _get(cls, "_reg_verprogid_")
defIcon = _get(cls, "_reg_icon_")
threadingModel = _get(cls, "_reg_threading_", "both")
catids = _get(cls, "_reg_catids_", [])
options = _get(cls, "_reg_options_", {})
policySpec = _get(cls, "_reg_policy_spec_")
clsctx = _get(cls, "_reg_clsctx_")
tlb_filename = _get(cls, "_reg_typelib_filename_")
# default to being a COM category only when not frozen.
addPyComCat = not _get(cls, "_reg_disable_pycomcat_", pythoncom.frozen != 0)
addnPath = None
if debugging:
# If the class has a debugging dispatcher specified, use it, otherwise
# use our default dispatcher.
dispatcherSpec = _get(cls, "_reg_debug_dispatcher_spec_")
if dispatcherSpec is None:
dispatcherSpec = "win32com.server.dispatcher.DefaultDebugDispatcher"
# And remember the debugging flag as servers may wish to use it at runtime.
debuggingDesc = "(for debugging)"
options["Debugging"] = "1"
else:
dispatcherSpec = _get(cls, "_reg_dispatcher_spec_")
debuggingDesc = ""
options["Debugging"] = "0"
if spec is None:
moduleName = cls.__module__
if moduleName == "__main__":
# Use argv[0] to determine the module name.
try:
# Use the win32api to find the case-sensitive name
moduleName = os.path.splitext(
win32api.FindFiles(sys.argv[0])[0][8]
)[0]
except (IndexError, win32api.error):
# Can't find the script file - the user must explicitely set the _reg_... attribute.
raise TypeError(
"Can't locate the script hosting the COM object - please set _reg_class_spec_ in your object"
)
spec = moduleName + "." + cls.__name__
# Frozen apps don't need their directory on sys.path
if not pythoncom.frozen:
scriptDir = os.path.split(sys.argv[0])[0]
if not scriptDir:
scriptDir = "."
addnPath = win32api.GetFullPathName(scriptDir)
RegisterServer(
clsid,
spec,
desc,
progID,
verProgID,
defIcon,
threadingModel,
policySpec,
catids,
options,
addPyComCat,
dispatcherSpec,
clsctx,
addnPath,
)
if not quiet:
print("Registered:", progID or spec, debuggingDesc)
# Register the typelibrary
if tlb_filename:
tlb_filename = os.path.abspath(tlb_filename)
typelib = pythoncom.LoadTypeLib(tlb_filename)
pythoncom.RegisterTypeLib(typelib, tlb_filename)
if not quiet:
print("Registered type library:", tlb_filename)
extra = flags.get("finalize_register")
if extra:
extra()
def UnregisterClasses(*classes, **flags):
quiet = "quiet" in flags and flags["quiet"]
for cls in classes:
clsid = cls._reg_clsid_
progID = _get(cls, "_reg_progid_")
verProgID = _get(cls, "_reg_verprogid_")
customKeys = _get(cls, "_reg_remove_keys_")
unregister_typelib = _get(cls, "_reg_typelib_filename_") is not None
UnregisterServer(clsid, progID, verProgID, customKeys)
if not quiet:
print("Unregistered:", progID or str(clsid))
if unregister_typelib:
tlb_guid = _get(cls, "_typelib_guid_")
if tlb_guid is None:
# I guess I could load the typelib, but they need the GUID anyway.
print("Have typelib filename, but no GUID - can't unregister")
else:
major, minor = _get(cls, "_typelib_version_", (1, 0))
lcid = _get(cls, "_typelib_lcid_", 0)
try:
pythoncom.UnRegisterTypeLib(tlb_guid, major, minor, lcid)
if not quiet:
print("Unregistered type library")
except pythoncom.com_error:
pass
extra = flags.get("finalize_unregister")
if extra:
extra()
#
# Unregister info is for installers or external uninstallers.
# The WISE installer, for example firstly registers the COM server,
# then queries for the Unregister info, appending it to its
# install log. Uninstalling the package will the uninstall the server
def UnregisterInfoClasses(*classes, **flags):
ret = []
for cls in classes:
clsid = cls._reg_clsid_
progID = _get(cls, "_reg_progid_")
verProgID = _get(cls, "_reg_verprogid_")
customKeys = _get(cls, "_reg_remove_keys_")
ret = ret + GetUnregisterServerKeys(clsid, progID, verProgID, customKeys)
return ret
# Attempt to 're-execute' our current process with elevation.
def ReExecuteElevated(flags):
import tempfile
import win32event # we've already checked we are running XP above
import win32process
import winxpgui
from win32com.shell import shellcon
from win32com.shell.shell import ShellExecuteEx
if not flags["quiet"]:
print("Requesting elevation and retrying...")
new_params = " ".join(['"' + a + '"' for a in sys.argv])
# If we aren't already in unattended mode, we want our sub-process to
# be.
if not flags["unattended"]:
new_params += " --unattended"
# specifying the parent means the dialog is centered over our window,
# which is a good usability clue.
# hwnd is unlikely on the command-line, but flags may come from elsewhere
hwnd = flags.get("hwnd", None)
if hwnd is None:
try:
hwnd = winxpgui.GetConsoleWindow()
except winxpgui.error:
hwnd = 0
# Redirect output so we give the user some clue what went wrong. This
# also means we need to use COMSPEC. However, the "current directory"
# appears to end up ignored - so we execute things via a temp batch file.
tempbase = tempfile.mktemp("pycomserverreg")
outfile = tempbase + ".out"
batfile = tempbase + ".bat"
# If registering from pythonwin, need to run python console instead since
# pythonwin will just open script for editting
current_exe = os.path.split(sys.executable)[1].lower()
exe_to_run = None
if current_exe == "pythonwin.exe":
exe_to_run = os.path.join(sys.prefix, "python.exe")
elif current_exe == "pythonwin_d.exe":
exe_to_run = os.path.join(sys.prefix, "python_d.exe")
if not exe_to_run or not os.path.exists(exe_to_run):
exe_to_run = sys.executable
try:
batf = open(batfile, "w")
try:
cwd = os.getcwd()
print("@echo off", file=batf)
# nothing is 'inherited' by the elevated process, including the
# environment. I wonder if we need to set more?
print("set PYTHONPATH=%s" % os.environ.get("PYTHONPATH", ""), file=batf)
# may be on a different drive - select that before attempting to CD.
print(os.path.splitdrive(cwd)[0], file=batf)
print('cd "%s"' % os.getcwd(), file=batf)
print(
'%s %s > "%s" 2>&1'
% (win32api.GetShortPathName(exe_to_run), new_params, outfile),
file=batf,
)
finally:
batf.close()
executable = os.environ.get("COMSPEC", "cmd.exe")
rc = ShellExecuteEx(
hwnd=hwnd,
fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
lpVerb="runas",
lpFile=executable,
lpParameters='/C "%s"' % batfile,
nShow=win32con.SW_SHOW,
)
hproc = rc["hProcess"]
win32event.WaitForSingleObject(hproc, win32event.INFINITE)
exit_code = win32process.GetExitCodeProcess(hproc)
outf = open(outfile)
try:
output = outf.read()
finally:
outf.close()
if exit_code:
# Even if quiet you get to see this message.
print("Error: registration failed (exit code %s)." % exit_code)
# if we are quiet then the output if likely to already be nearly
# empty, so always print it.
print(output, end=" ")
finally:
for f in (outfile, batfile):
try:
os.unlink(f)
except os.error as exc:
print("Failed to remove tempfile '%s': %s" % (f, exc))
def UseCommandLine(*classes, **flags):
unregisterInfo = "--unregister_info" in sys.argv
unregister = "--unregister" in sys.argv
flags["quiet"] = flags.get("quiet", 0) or "--quiet" in sys.argv
flags["debug"] = flags.get("debug", 0) or "--debug" in sys.argv
flags["unattended"] = flags.get("unattended", 0) or "--unattended" in sys.argv
if unregisterInfo:
return UnregisterInfoClasses(*classes, **flags)
try:
if unregister:
UnregisterClasses(*classes, **flags)
else:
RegisterClasses(*classes, **flags)
except win32api.error as exc:
# If we are on xp+ and have "access denied", retry using
# ShellExecuteEx with 'runas' verb to force elevation (vista) and/or
# admin login dialog (vista/xp)
if (
flags["unattended"]
or exc.winerror != winerror.ERROR_ACCESS_DENIED
or sys.getwindowsversion()[0] < 5
):
raise
ReExecuteElevated(flags)
def RegisterPyComCategory():
"""Register the Python COM Server component category."""
regCat = _cat_registrar()
regCat.RegisterCategories([(CATID_PythonCOMServer, 0x0409, "Python COM Server")])
if not pythoncom.frozen:
try:
win32api.RegQueryValue(
win32con.HKEY_CLASSES_ROOT,
"Component Categories\\%s" % CATID_PythonCOMServer,
)
except win32api.error:
try:
RegisterPyComCategory()
except pythoncom.error: # Error with the COM category manager - oh well.
pass

229
lib/win32com/server/util.py Normal file
View file

@ -0,0 +1,229 @@
""" General Server side utilities
"""
import pythoncom
import winerror
from . import policy
from .exception import COMException
def wrap(ob, iid=None, usePolicy=None, useDispatcher=None):
"""Wraps an object in a PyGDispatch gateway.
Returns a client side PyI{iid} interface.
Interface and gateway support must exist for the specified IID, as
the QueryInterface() method is used.
"""
if usePolicy is None:
usePolicy = policy.DefaultPolicy
if useDispatcher == 1: # True will also work here.
import win32com.server.dispatcher
useDispatcher = win32com.server.dispatcher.DefaultDebugDispatcher
if useDispatcher is None or useDispatcher == 0:
ob = usePolicy(ob)
else:
ob = useDispatcher(usePolicy, ob)
# get a PyIDispatch, which interfaces to PyGDispatch
ob = pythoncom.WrapObject(ob)
if iid is not None:
ob = ob.QueryInterface(iid) # Ask the PyIDispatch if it supports it?
return ob
def unwrap(ob):
"""Unwraps an interface.
Given an interface which wraps up a Gateway, return the object behind
the gateway.
"""
ob = pythoncom.UnwrapObject(ob)
# see if the object is a dispatcher
if hasattr(ob, "policy"):
ob = ob.policy
return ob._obj_
class ListEnumerator:
"""A class to expose a Python sequence as an EnumVARIANT.
Create an instance of this class passing a sequence (list, tuple, or
any sequence protocol supporting object) and it will automatically
support the EnumVARIANT interface for the object.
See also the @NewEnum@ function, which can be used to turn the
instance into an actual COM server.
"""
_public_methods_ = ["Next", "Skip", "Reset", "Clone"]
def __init__(self, data, index=0, iid=pythoncom.IID_IEnumVARIANT):
self._list_ = data
self.index = index
self._iid_ = iid
def _query_interface_(self, iid):
if iid == self._iid_:
return 1
def Next(self, count):
result = self._list_[self.index : self.index + count]
self.Skip(count)
return result
def Skip(self, count):
end = self.index + count
if end > len(self._list_):
end = len(self._list_)
self.index = end
def Reset(self):
self.index = 0
def Clone(self):
return self._wrap(self.__class__(self._list_, self.index))
def _wrap(self, ob):
return wrap(ob)
class ListEnumeratorGateway(ListEnumerator):
"""A List Enumerator which wraps a sequence's items in gateways.
If a sequence contains items (objects) that have not been wrapped for
return through the COM layers, then a ListEnumeratorGateway can be
used to wrap those items before returning them (from the Next() method).
See also the @ListEnumerator@ class and the @NewEnum@ function.
"""
def Next(self, count):
result = self._list_[self.index : self.index + count]
self.Skip(count)
return map(self._wrap, result)
def NewEnum(
seq,
cls=ListEnumerator,
iid=pythoncom.IID_IEnumVARIANT,
usePolicy=None,
useDispatcher=None,
):
"""Creates a new enumerator COM server.
This function creates a new COM Server that implements the
IID_IEnumVARIANT interface.
A COM server that can enumerate the passed in sequence will be
created, then wrapped up for return through the COM framework.
Optionally, a custom COM server for enumeration can be passed
(the default is @ListEnumerator@), and the specific IEnum
interface can be specified.
"""
ob = cls(seq, iid=iid)
return wrap(ob, iid, usePolicy=usePolicy, useDispatcher=useDispatcher)
class Collection:
"A collection of VARIANT values."
_public_methods_ = ["Item", "Count", "Add", "Remove", "Insert"]
def __init__(self, data=None, readOnly=0):
if data is None:
data = []
self.data = data
# disable Add/Remove if read-only. note that we adjust _public_methods_
# on this instance only.
if readOnly:
self._public_methods_ = ["Item", "Count"]
# This method is also used as the "default" method.
# Thus "print ob" will cause this to be called with zero
# params. Handle this slightly more elegantly here.
# Ideally the policy should handle this.
def Item(self, *args):
if len(args) != 1:
raise COMException(scode=winerror.DISP_E_BADPARAMCOUNT)
try:
return self.data[args[0]]
except IndexError as desc:
raise COMException(scode=winerror.DISP_E_BADINDEX, desc=str(desc))
_value_ = Item
def Count(self):
return len(self.data)
def Add(self, value):
self.data.append(value)
def Remove(self, index):
try:
del self.data[index]
except IndexError as desc:
raise COMException(scode=winerror.DISP_E_BADINDEX, desc=str(desc))
def Insert(self, index, value):
try:
index = int(index)
except (ValueError, TypeError):
raise COMException(scode=winerror.DISP_E_TYPEMISMATCH)
self.data.insert(index, value)
def _NewEnum(self):
return NewEnum(self.data)
def NewCollection(seq, cls=Collection):
"""Creates a new COM collection object
This function creates a new COM Server that implements the
common collection protocols, including enumeration. (_NewEnum)
A COM server that can enumerate the passed in sequence will be
created, then wrapped up for return through the COM framework.
Optionally, a custom COM server for enumeration can be passed
(the default is @Collection@).
"""
return pythoncom.WrapObject(
policy.DefaultPolicy(cls(seq)), pythoncom.IID_IDispatch, pythoncom.IID_IDispatch
)
class FileStream:
_public_methods_ = ["Read", "Write", "Clone", "CopyTo", "Seek"]
_com_interfaces_ = [pythoncom.IID_IStream]
def __init__(self, file):
self.file = file
def Read(self, amount):
return self.file.read(amount)
def Write(self, data):
self.file.write(data)
return len(data)
def Clone(self):
return self._wrap(self.__class__(self.file))
def CopyTo(self, dest, cb):
data = self.file.read(cb)
cbread = len(data)
dest.Write(data) ## ??? Write does not currently return the length ???
return cbread, cbread
def Seek(self, offset, origin):
# how convient that the 'origin' values are the same as the CRT :)
self.file.seek(offset, origin)
return self.file.tell()
def _wrap(self, ob):
return wrap(ob)

View file

@ -0,0 +1,47 @@
import sys
import time
class Tools:
_public_methods_ = ["reload", "adddir", "echo", "sleep"]
def reload(self, module):
if module in sys.modules:
from importlib import reload
reload(sys.modules[module])
return "reload succeeded."
return "no reload performed."
def adddir(self, dir):
if type(dir) == type(""):
sys.path.append(dir)
return str(sys.path)
def echo(self, arg):
return repr(arg)
def sleep(self, t):
time.sleep(t)
if __name__ == "__main__":
from win32com.server.register import RegisterServer, UnregisterServer
clsid = "{06ce7630-1d81-11d0-ae37-c2fa70000000}"
progid = "Python.Tools"
verprogid = "Python.Tools.1"
if "--unregister" in sys.argv:
print("Unregistering...")
UnregisterServer(clsid, progid, verprogid)
print("Unregistered OK")
else:
print("Registering COM server...")
RegisterServer(
clsid,
"win32com.servers.PythonTools.Tools",
"Python Tools",
progid,
verprogid,
)
print("Class registered.")

View file

View file

@ -0,0 +1,137 @@
"""Python.Dictionary COM Server.
This module implements a simple COM server that acts much like a Python
dictionary or as a standard string-keyed VB Collection. The keys of
the dictionary are strings and are case-insensitive.
It uses a highly customized policy to fine-tune the behavior exposed to
the COM client.
The object exposes the following properties:
int Count (readonly)
VARIANT Item(BSTR key) (propget for Item)
Item(BSTR key, VARIANT value) (propput for Item)
Note that 'Item' is the default property, so the following forms of
VB code are acceptable:
set ob = CreateObject("Python.Dictionary")
ob("hello") = "there"
ob.Item("hi") = ob("HELLO")
All keys are defined, returning VT_NULL (None) if a value has not been
stored. To delete a key, simply assign VT_NULL to the key.
The object responds to the _NewEnum method by returning an enumerator over
the dictionary's keys. This allows for the following type of VB code:
for each name in ob
debug.print name, ob(name)
next
"""
import pythoncom
import pywintypes
import winerror
from pythoncom import DISPATCH_METHOD, DISPATCH_PROPERTYGET
from win32com.server import policy, util
from win32com.server.exception import COMException
from winerror import S_OK
class DictionaryPolicy(policy.BasicWrapPolicy):
### BasicWrapPolicy looks for this
_com_interfaces_ = []
### BasicWrapPolicy looks for this
_name_to_dispid_ = {
"item": pythoncom.DISPID_VALUE,
"_newenum": pythoncom.DISPID_NEWENUM,
"count": 1,
}
### Auto-Registration process looks for these...
_reg_desc_ = "Python Dictionary"
_reg_clsid_ = "{39b61048-c755-11d0-86fa-00c04fc2e03e}"
_reg_progid_ = "Python.Dictionary"
_reg_verprogid_ = "Python.Dictionary.1"
_reg_policy_spec_ = "win32com.servers.dictionary.DictionaryPolicy"
def _CreateInstance_(self, clsid, reqIID):
self._wrap_({})
return pythoncom.WrapObject(self, reqIID)
def _wrap_(self, ob):
self._obj_ = ob # ob should be a dictionary
def _invokeex_(self, dispid, lcid, wFlags, args, kwargs, serviceProvider):
if dispid == 0: # item
l = len(args)
if l < 1:
raise COMException(
desc="not enough parameters", scode=winerror.DISP_E_BADPARAMCOUNT
)
key = args[0]
if type(key) not in [str, str]:
### the nArgErr thing should be 0-based, not reversed... sigh
raise COMException(
desc="Key must be a string", scode=winerror.DISP_E_TYPEMISMATCH
)
key = key.lower()
if wFlags & (DISPATCH_METHOD | DISPATCH_PROPERTYGET):
if l > 1:
raise COMException(scode=winerror.DISP_E_BADPARAMCOUNT)
try:
return self._obj_[key]
except KeyError:
return None # unknown keys return None (VT_NULL)
if l != 2:
raise COMException(scode=winerror.DISP_E_BADPARAMCOUNT)
if args[1] is None:
# delete a key when None is assigned to it
try:
del self._obj_[key]
except KeyError:
pass
else:
self._obj_[key] = args[1]
return S_OK
if dispid == 1: # count
if not wFlags & DISPATCH_PROPERTYGET:
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) # not found
if len(args) != 0:
raise COMException(scode=winerror.DISP_E_BADPARAMCOUNT)
return len(self._obj_)
if dispid == pythoncom.DISPID_NEWENUM:
return util.NewEnum(list(self._obj_.keys()))
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND)
def _getidsofnames_(self, names, lcid):
### this is a copy of MappedWrapPolicy._getidsofnames_ ...
name = names[0].lower()
try:
return (self._name_to_dispid_[name],)
except KeyError:
raise COMException(
scode=winerror.DISP_E_MEMBERNOTFOUND, desc="Member not found"
)
def Register():
from win32com.server.register import UseCommandLine
return UseCommandLine(DictionaryPolicy)
if __name__ == "__main__":
Register()

View file

@ -0,0 +1,55 @@
"""Python.Interpreter COM Server
This module implements a very very simple COM server which
exposes the Python interpreter.
This is designed more as a demonstration than a full blown COM server.
General functionality and Error handling are both limited.
To use this object, ensure it is registered by running this module
from Python.exe. Then, from Visual Basic, use "CreateObject('Python.Interpreter')",
and call its methods!
"""
import winerror
from win32com.server.exception import Exception
# Expose the Python interpreter.
class Interpreter:
"""The interpreter object exposed via COM"""
_public_methods_ = ["Exec", "Eval"]
# All registration stuff to support fully automatic register/unregister
_reg_verprogid_ = "Python.Interpreter.2"
_reg_progid_ = "Python.Interpreter"
_reg_desc_ = "Python Interpreter"
_reg_clsid_ = "{30BD3490-2632-11cf-AD5B-524153480001}"
_reg_class_spec_ = "win32com.servers.interp.Interpreter"
def __init__(self):
self.dict = {}
def Eval(self, exp):
"""Evaluate an expression."""
if type(exp) != str:
raise Exception(desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH)
return eval(str(exp), self.dict)
def Exec(self, exp):
"""Execute a statement."""
if type(exp) != str:
raise Exception(desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH)
exec(str(exp), self.dict)
def Register():
import win32com.server.register
return win32com.server.register.UseCommandLine(Interpreter)
if __name__ == "__main__":
print("Registering COM server...")
Register()

View file

@ -0,0 +1,34 @@
"""A COM Server which exposes the NT Performance monitor in a very rudimentary way
Usage from VB:
set ob = CreateObject("Python.PerfmonQuery")
freeBytes = ob.Query("Memory", "Available Bytes")
"""
import pythoncom
import win32pdhutil
import winerror
from win32com.server import exception, register
class PerfMonQuery:
_reg_verprogid_ = "Python.PerfmonQuery.1"
_reg_progid_ = "Python.PerfmonQuery"
_reg_desc_ = "Python Performance Monitor query object"
_reg_clsid_ = "{64cef7a0-8ece-11d1-a65a-00aa00125a98}"
_reg_class_spec_ = "win32com.servers.perfmon.PerfMonQuery"
_public_methods_ = ["Query"]
def Query(self, object, counter, instance=None, machine=None):
try:
return win32pdhutil.GetPerformanceAttributes(
object, counter, instance, machine=machine
)
except win32pdhutil.error as exc:
raise exception.Exception(desc=exc.strerror)
except TypeError as desc:
raise exception.Exception(desc=desc, scode=winerror.DISP_E_TYPEMISMATCH)
if __name__ == "__main__":
print("Registering COM server...")
register.UseCommandLine(PerfMonQuery)

View file

@ -0,0 +1,182 @@
# This is part of the Python test suite.
# The object is registered when you first run the test suite.
# (and hopefully unregistered once done ;-)
import pythoncom
import winerror
# Ensure the vtables in the tlb are known.
from win32com import universal
from win32com.client import constants, gencache
from win32com.server.exception import COMException
from win32com.server.util import wrap
pythoncom.__future_currency__ = True
# We use the constants from the module, so must insist on a gencache.
# Otherwise, use of gencache is not necessary (tho still advised)
gencache.EnsureModule("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)
class PyCOMTest:
_typelib_guid_ = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
_typelib_version = 1, 0
_com_interfaces_ = ["IPyCOMTest"]
_reg_clsid_ = "{e743d9cd-cb03-4b04-b516-11d3a81c1597}"
_reg_progid_ = "Python.Test.PyCOMTest"
def DoubleString(self, str):
return str * 2
def DoubleInOutString(self, str):
return str * 2
def Fire(self, nID):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetLastVarArgs(self):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetMultipleInterfaces(self, outinterface1, outinterface2):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetSafeArrays(self, attrs, attrs2, ints):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetSetDispatch(self, indisp):
raise COMException(hresult=winerror.E_NOTIMPL)
# Result is of type IPyCOMTest
def GetSetInterface(self, ininterface):
return wrap(self)
def GetSetVariant(self, indisp):
return indisp
def TestByRefVariant(self, v):
return v * 2
def TestByRefString(self, v):
return v * 2
# Result is of type IPyCOMTest
def GetSetInterfaceArray(self, ininterface):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetSetUnknown(self, inunk):
raise COMException(hresult=winerror.E_NOTIMPL)
# Result is of type ISimpleCounter
def GetSimpleCounter(self):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetSimpleSafeArray(self, ints):
raise COMException(hresult=winerror.E_NOTIMPL)
def GetStruct(self):
raise COMException(hresult=winerror.E_NOTIMPL)
def SetIntSafeArray(self, ints):
return len(ints)
def SetLongLongSafeArray(self, ints):
return len(ints)
def SetULongLongSafeArray(self, ints):
return len(ints)
def SetBinSafeArray(self, buf):
return len(buf)
def SetVarArgs(self, *args):
raise COMException(hresult=winerror.E_NOTIMPL)
def SetVariantSafeArray(self, vars):
raise COMException(hresult=winerror.E_NOTIMPL)
def Start(self):
raise COMException(hresult=winerror.E_NOTIMPL)
def Stop(self, nID):
raise COMException(hresult=winerror.E_NOTIMPL)
def StopAll(self):
raise COMException(hresult=winerror.E_NOTIMPL)
def TakeByRefDispatch(self, inout):
raise COMException(hresult=winerror.E_NOTIMPL)
def TakeByRefTypedDispatch(self, inout):
raise COMException(hresult=winerror.E_NOTIMPL)
def Test(self, key, inval):
return not inval
def Test2(self, inval):
return inval
def Test3(self, inval):
raise COMException(hresult=winerror.E_NOTIMPL)
def Test4(self, inval):
raise COMException(hresult=winerror.E_NOTIMPL)
def Test5(self, inout):
if inout == constants.TestAttr1:
return constants.TestAttr1_1
elif inout == constants.TestAttr1_1:
return constants.TestAttr1
else:
return -1
def Test6(self, inval):
return inval
def TestInOut(self, fval, bval, lval):
return winerror.S_OK, fval * 2, not bval, lval * 2
def TestOptionals(self, strArg="def", sval=0, lval=1, dval=3.1400001049041748):
raise COMException(hresult=winerror.E_NOTIMPL)
def TestOptionals2(self, dval, strval="", sval=1):
raise COMException(hresult=winerror.E_NOTIMPL)
def CheckVariantSafeArray(self, data):
return 1
def LongProp(self):
return self.longval
def SetLongProp(self, val):
self.longval = val
def ULongProp(self):
return self.ulongval
def SetULongProp(self, val):
self.ulongval = val
def IntProp(self):
return self.intval
def SetIntProp(self, val):
self.intval = val
class PyCOMTestMI(PyCOMTest):
_typelib_guid_ = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
_typelib_version = 1, 0
# Interfaces with a interface name, a real IID, and an IID as a string
_com_interfaces_ = [
"IPyCOMTest",
pythoncom.IID_IStream,
str(pythoncom.IID_IStorage),
]
_reg_clsid_ = "{F506E9A1-FB46-4238-A597-FA4EB69787CA}"
_reg_progid_ = "Python.Test.PyCOMTestMI"
if __name__ == "__main__":
import win32com.server.register
win32com.server.register.UseCommandLine(PyCOMTest)
win32com.server.register.UseCommandLine(PyCOMTestMI)

142
lib/win32com/storagecon.py Normal file
View file

@ -0,0 +1,142 @@
"""Constants related to IStorage and related interfaces
This file was generated by h2py from d:\msdev\include\objbase.h
then hand edited, a few extra constants added, etc.
"""
STGC_DEFAULT = 0
STGC_OVERWRITE = 1
STGC_ONLYIFCURRENT = 2
STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 4
STGC_CONSOLIDATE = 8
STGTY_STORAGE = 1
STGTY_STREAM = 2
STGTY_LOCKBYTES = 3
STGTY_PROPERTY = 4
STREAM_SEEK_SET = 0
STREAM_SEEK_CUR = 1
STREAM_SEEK_END = 2
LOCK_WRITE = 1
LOCK_EXCLUSIVE = 2
LOCK_ONLYONCE = 4
# Generated as from here.
CWCSTORAGENAME = 32
STGM_DIRECT = 0x00000000
STGM_TRANSACTED = 0x00010000
STGM_SIMPLE = 0x08000000
STGM_READ = 0x00000000
STGM_WRITE = 0x00000001
STGM_READWRITE = 0x00000002
STGM_SHARE_DENY_NONE = 0x00000040
STGM_SHARE_DENY_READ = 0x00000030
STGM_SHARE_DENY_WRITE = 0x00000020
STGM_SHARE_EXCLUSIVE = 0x00000010
STGM_PRIORITY = 0x00040000
STGM_DELETEONRELEASE = 0x04000000
STGM_NOSCRATCH = 0x00100000
STGM_CREATE = 0x00001000
STGM_CONVERT = 0x00020000
STGM_FAILIFTHERE = 0x00000000
STGM_NOSNAPSHOT = 0x00200000
ASYNC_MODE_COMPATIBILITY = 0x00000001
ASYNC_MODE_DEFAULT = 0x00000000
STGTY_REPEAT = 0x00000100
STG_TOEND = 0xFFFFFFFF
STG_LAYOUT_SEQUENTIAL = 0x00000000
STG_LAYOUT_INTERLEAVED = 0x00000001
## access rights used with COM server ACL's
COM_RIGHTS_EXECUTE = 1
COM_RIGHTS_EXECUTE_LOCAL = 2
COM_RIGHTS_EXECUTE_REMOTE = 4
COM_RIGHTS_ACTIVATE_LOCAL = 8
COM_RIGHTS_ACTIVATE_REMOTE = 16
STGFMT_DOCUMENT = 0
STGFMT_STORAGE = 0
STGFMT_NATIVE = 1
STGFMT_FILE = 3
STGFMT_ANY = 4
STGFMT_DOCFILE = 5
PID_DICTIONARY = 0
PID_CODEPAGE = 1
PID_FIRST_USABLE = 2
PID_FIRST_NAME_DEFAULT = 4095
PID_LOCALE = -2147483648
PID_MODIFY_TIME = -2147483647
PID_SECURITY = -2147483646
PID_BEHAVIOR = -2147483645
PID_ILLEGAL = -1
PID_MIN_READONLY = -2147483648
PID_MAX_READONLY = -1073741825
## DiscardableInformation
PIDDI_THUMBNAIL = 0x00000002
## SummaryInformation
PIDSI_TITLE = 2
PIDSI_SUBJECT = 3
PIDSI_AUTHOR = 4
PIDSI_KEYWORDS = 5
PIDSI_COMMENTS = 6
PIDSI_TEMPLATE = 7
PIDSI_LASTAUTHOR = 8
PIDSI_REVNUMBER = 9
PIDSI_EDITTIME = 10
PIDSI_LASTPRINTED = 11
PIDSI_CREATE_DTM = 12
PIDSI_LASTSAVE_DTM = 13
PIDSI_PAGECOUNT = 14
PIDSI_WORDCOUNT = 15
PIDSI_CHARCOUNT = 16
PIDSI_THUMBNAIL = 17
PIDSI_APPNAME = 18
PIDSI_DOC_SECURITY = 19
## DocSummaryInformation
PIDDSI_CATEGORY = 2
PIDDSI_PRESFORMAT = 3
PIDDSI_BYTECOUNT = 4
PIDDSI_LINECOUNT = 5
PIDDSI_PARCOUNT = 6
PIDDSI_SLIDECOUNT = 7
PIDDSI_NOTECOUNT = 8
PIDDSI_HIDDENCOUNT = 9
PIDDSI_MMCLIPCOUNT = 10
PIDDSI_SCALE = 11
PIDDSI_HEADINGPAIR = 12
PIDDSI_DOCPARTS = 13
PIDDSI_MANAGER = 14
PIDDSI_COMPANY = 15
PIDDSI_LINKSDIRTY = 16
## MediaFileSummaryInfo
PIDMSI_EDITOR = 2
PIDMSI_SUPPLIER = 3
PIDMSI_SOURCE = 4
PIDMSI_SEQUENCE_NO = 5
PIDMSI_PROJECT = 6
PIDMSI_STATUS = 7
PIDMSI_OWNER = 8
PIDMSI_RATING = 9
PIDMSI_PRODUCTION = 10
PIDMSI_COPYRIGHT = 11
## PROPSETFLAG enum
PROPSETFLAG_DEFAULT = 0
PROPSETFLAG_NONSIMPLE = 1
PROPSETFLAG_ANSI = 2
PROPSETFLAG_UNBUFFERED = 4
PROPSETFLAG_CASE_SENSITIVE = 8
## STGMOVE enum
STGMOVE_MOVE = 0
STGMOVE_COPY = 1
STGMOVE_SHALLOWCOPY = 2

View file

@ -0,0 +1,95 @@
#
# Generate scripts needed for serious testing!
#
import os
import sys
import pythoncom
import win32com
import win32com.client.makepy
import win32com.test
genList = [
("msword8", "{00020905-0000-0000-C000-000000000046}", 1033, 8, 0),
]
genDir = "Generated4Test"
def GetGenPath():
import win32api
return os.path.join(win32api.GetFullPathName(win32com.test.__path__[0]), genDir)
def GenerateFromRegistered(fname, *loadArgs):
# tlb = apply(pythoncom.LoadRegTypeLib, loadArgs)
genPath = GetGenPath()
try:
os.stat(genPath)
except os.error:
os.mkdir(genPath)
# Ensure an __init__ exists.
open(os.path.join(genPath, "__init__.py"), "w").close()
print(fname, ": generating -", end=" ")
f = open(os.path.join(genPath, fname + ".py"), "w")
win32com.client.makepy.GenerateFromTypeLibSpec(
loadArgs, f, bQuiet=1, bGUIProgress=1
)
f.close()
print("compiling -", end=" ")
fullModName = "win32com.test.%s.%s" % (genDir, fname)
exec("import " + fullModName)
# Inject the generated module as a top level module.
sys.modules[fname] = sys.modules[fullModName]
print("done")
def GenerateAll():
for args in genList:
try:
GenerateFromRegistered(*args)
except KeyboardInterrupt:
print("** Interrupted ***")
break
except pythoncom.com_error:
print("** Could not generate test code for ", args[0])
def CleanAll():
print("Cleaning generated test scripts...")
try: # Clear exceptions!
1 / 0
except:
pass
genPath = GetGenPath()
for args in genList:
try:
name = args[0] + ".py"
os.unlink(os.path.join(genPath, name))
except os.error as details:
if type(details) == type(()) and details[0] != 2:
print("Could not deleted generated", name, details)
try:
name = args[0] + ".pyc"
os.unlink(os.path.join(genPath, name))
except os.error as details:
if type(details) == type(()) and details[0] != 2:
print("Could not deleted generated", name, details)
try:
os.unlink(os.path.join(genPath, "__init__.py"))
except:
pass
try:
os.unlink(os.path.join(genPath, "__init__.pyc"))
except:
pass
try:
os.rmdir(genPath)
except os.error as details:
print("Could not delete test directory -", details)
if __name__ == "__main__":
GenerateAll()
CleanAll()

View file

@ -0,0 +1,64 @@
<scriptlet>
<Registration
Description="TestPys"
ProgID="TestPys.Scriptlet"
Version="1"
ClassID="{2eeb6080-cd58-11d1-b81e-00a0240b2fef}">
<SCRIPT LANGUAGE="VBScript">
Function Register()
Msgbox "Scriptlet 'Test' registered."
End Function
Function Unregister()
Msgbox "Scriptlet 'Test' unregistered."
End Function
</SCRIPT>
</Registration>
<implements id=Automation type=Automation>
<property name=PyProp1>
<get/>
<put/>
</property>
<property name=PyProp2>
<get/>
<put/>
</property>
<method name=PyMethod1>
</method>
<method name=PyMethod2>
</method>
</implements>
<script language=python>
PyProp1 = "PyScript Property1";
PyProp2 = "PyScript Property2";
def get_PyProp1():
return PyProp1
def put_PyProp1(newValue):
global PyProp1
PyProp1 = newValue
def get_PyProp2():
return PyProp2
def put_PyProp2(newValue):
global PyProp2
PyProp2 = newValue
def PyMethod1():
return "PyMethod1 called"
def PyMethod2():
return "PyMethod2 called"
</script>
</scriptlet>

View file

@ -0,0 +1 @@
# Empty file to designate a Python package

View file

@ -0,0 +1,90 @@
# import dao3032
# No longer imported here - callers responsibility to load
#
import win32com.client
def DumpDB(db, bDeep=1):
# MUST be a DB object.
DumpTables(db, bDeep)
DumpRelations(db, bDeep)
DumpAllContainers(db, bDeep)
def DumpTables(db, bDeep=1):
for tab in db.TableDefs:
tab = db.TableDefs(tab.Name) # Redundant lookup for testing purposes.
print(
"Table %s - Fields: %d, Attributes:%d"
% (tab.Name, len(tab.Fields), tab.Attributes)
)
if bDeep:
DumpFields(tab.Fields)
def DumpFields(fields):
for field in fields:
print(
" %s, size=%d, reqd=%d, type=%d, defVal=%s"
% (
field.Name,
field.Size,
field.Required,
field.Type,
str(field.DefaultValue),
)
)
def DumpRelations(db, bDeep=1):
for relation in db.Relations:
print(
"Relation %s - %s->%s"
% (relation.Name, relation.Table, relation.ForeignTable)
)
#### This dont work. TLB says it is a Fields collection, but apparently not!
#### if bDeep: DumpFields(relation.Fields)
def DumpAllContainers(db, bDeep=1):
for cont in db.Containers:
print("Container %s - %d documents" % (cont.Name, len(cont.Documents)))
if bDeep:
DumpContainerDocuments(cont)
def DumpContainerDocuments(container):
for doc in container.Documents:
import time
timeStr = time.ctime(int(doc.LastUpdated))
print(" %s - updated %s (" % (doc.Name, timeStr), end=" ")
print(doc.LastUpdated, ")") # test the _print_ method?
def TestEngine(engine):
import sys
if len(sys.argv) > 1:
dbName = sys.argv[1]
else:
dbName = "e:\\temp\\TestPython.mdb"
db = engine.OpenDatabase(dbName)
DumpDB(db)
def test():
for progid in ("DAO.DBEngine.36", "DAO.DBEngine.35", "DAO.DBEngine.30"):
try:
ob = win32com.client.gencache.EnsureDispatch(progid)
except pythoncom.com_error:
print(progid, "does not seem to be installed")
else:
TestEngine(ob)
break
if __name__ == "__main__":
test()

View file

@ -0,0 +1,259 @@
# errorSemantics.py
# Test the Python error handling semantics. Specifically:
#
# * When a Python COM object is called via IDispatch, the nominated
# scode is placed in the exception tuple, and the HRESULT is
# DISP_E_EXCEPTION
# * When the same interface is called via IWhatever, the
# nominated scode is returned directly (with the scode also
# reflected in the exception tuple)
# * In all cases, the description etc end up in the exception tuple
# * "Normal" Python exceptions resolve to an E_FAIL "internal error"
import pythoncom
import winerror
from win32com.client import Dispatch
from win32com.server.exception import COMException
from win32com.server.util import wrap
from win32com.test.util import CaptureWriter
class error(Exception):
def __init__(self, msg, com_exception=None):
Exception.__init__(self, msg, str(com_exception))
# Our COM server.
class TestServer:
_public_methods_ = ["Clone", "Commit", "LockRegion", "Read"]
_com_interfaces_ = [pythoncom.IID_IStream]
def Clone(self):
raise COMException("Not today", scode=winerror.E_UNEXPECTED)
def Commit(self, flags):
# Testing unicode: 1F600 '😀'; GRINNING FACE
# Use the 'name' just for fun!
if flags == 0:
# A non com-specific exception.
raise Exception("\N{GRINNING FACE}")
# An explicit com_error, which is a bit of an edge-case, but might happen if
# a COM server itself calls another COM object and it fails.
excepinfo = (
winerror.E_UNEXPECTED,
"source",
"\N{GRINNING FACE}",
"helpfile",
1,
winerror.E_FAIL,
)
raise pythoncom.com_error(winerror.E_UNEXPECTED, "desc", excepinfo, None)
def test():
# Call via a native interface.
com_server = wrap(TestServer(), pythoncom.IID_IStream)
try:
com_server.Clone()
raise error("Expecting this call to fail!")
except pythoncom.com_error as com_exc:
if com_exc.hresult != winerror.E_UNEXPECTED:
raise error(
"Calling the object natively did not yield the correct scode", com_exc
)
exc = com_exc.excepinfo
if not exc or exc[-1] != winerror.E_UNEXPECTED:
raise error(
"The scode element of the exception tuple did not yield the correct scode",
com_exc,
)
if exc[2] != "Not today":
raise error(
"The description in the exception tuple did not yield the correct string",
com_exc,
)
cap = CaptureWriter()
try:
cap.capture()
try:
com_server.Commit(0)
finally:
cap.release()
raise error("Expecting this call to fail!")
except pythoncom.com_error as com_exc:
if com_exc.hresult != winerror.E_FAIL:
raise error("The hresult was not E_FAIL for an internal error", com_exc)
if com_exc.excepinfo[1] != "Python COM Server Internal Error":
raise error(
"The description in the exception tuple did not yield the correct string",
com_exc,
)
# Check we saw a traceback in stderr
if cap.get_captured().find("Traceback") < 0:
raise error("Could not find a traceback in stderr: %r" % (cap.get_captured(),))
# Now do it all again, but using IDispatch
com_server = Dispatch(wrap(TestServer()))
try:
com_server.Clone()
raise error("Expecting this call to fail!")
except pythoncom.com_error as com_exc:
if com_exc.hresult != winerror.DISP_E_EXCEPTION:
raise error(
"Calling the object via IDispatch did not yield the correct scode",
com_exc,
)
exc = com_exc.excepinfo
if not exc or exc[-1] != winerror.E_UNEXPECTED:
raise error(
"The scode element of the exception tuple did not yield the correct scode",
com_exc,
)
if exc[2] != "Not today":
raise error(
"The description in the exception tuple did not yield the correct string",
com_exc,
)
cap.clear()
try:
cap.capture()
try:
com_server.Commit(0)
finally:
cap.release()
raise error("Expecting this call to fail!")
except pythoncom.com_error as com_exc:
if com_exc.hresult != winerror.DISP_E_EXCEPTION:
raise error(
"Calling the object via IDispatch did not yield the correct scode",
com_exc,
)
exc = com_exc.excepinfo
if not exc or exc[-1] != winerror.E_FAIL:
raise error(
"The scode element of the exception tuple did not yield the correct scode",
com_exc,
)
if exc[1] != "Python COM Server Internal Error":
raise error(
"The description in the exception tuple did not yield the correct string",
com_exc,
)
# Check we saw a traceback in stderr
if cap.get_captured().find("Traceback") < 0:
raise error("Could not find a traceback in stderr: %r" % (cap.get_captured(),))
# And an explicit com_error
cap.clear()
try:
cap.capture()
try:
com_server.Commit(1)
finally:
cap.release()
raise error("Expecting this call to fail!")
except pythoncom.com_error as com_exc:
if com_exc.hresult != winerror.DISP_E_EXCEPTION:
raise error(
"Calling the object via IDispatch did not yield the correct scode",
com_exc,
)
exc = com_exc.excepinfo
if not exc or exc[-1] != winerror.E_FAIL:
raise error(
"The scode element of the exception tuple did not yield the correct scode",
com_exc,
)
if exc[1] != "source":
raise error(
"The source in the exception tuple did not yield the correct string",
com_exc,
)
if exc[2] != "\U0001F600":
raise error(
"The description in the exception tuple did not yield the correct string",
com_exc,
)
if exc[3] != "helpfile":
raise error(
"The helpfile in the exception tuple did not yield the correct string",
com_exc,
)
if exc[4] != 1:
raise error(
"The help context in the exception tuple did not yield the correct string",
com_exc,
)
try:
import logging
except ImportError:
logging = None
if logging is not None:
import win32com
class TestLogHandler(logging.Handler):
def __init__(self):
self.reset()
logging.Handler.__init__(self)
def reset(self):
self.num_emits = 0
self.last_record = None
def emit(self, record):
self.num_emits += 1
self.last_record = self.format(record)
return
print("--- record start")
print(self.last_record)
print("--- record end")
def testLogger():
assert not hasattr(win32com, "logger")
handler = TestLogHandler()
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
log = logging.getLogger("win32com_test")
log.addHandler(handler)
win32com.logger = log
# Now throw some exceptions!
# Native interfaces
com_server = wrap(TestServer(), pythoncom.IID_IStream)
try:
com_server.Commit(0)
raise RuntimeError("should have failed")
except pythoncom.error as exc:
# `excepinfo` is a tuple with elt 2 being the traceback we captured.
message = exc.excepinfo[2]
assert message.endswith("Exception: \U0001F600\n")
assert handler.num_emits == 1, handler.num_emits
assert handler.last_record.startswith(
"pythoncom error: Unexpected exception in gateway method 'Commit'"
)
handler.reset()
# IDispatch
com_server = Dispatch(wrap(TestServer()))
try:
com_server.Commit(0)
raise RuntimeError("should have failed")
except pythoncom.error as exc:
# `excepinfo` is a tuple with elt 2 being the traceback we captured.
message = exc.excepinfo[2]
assert message.endswith("Exception: \U0001F600\n")
assert handler.num_emits == 1, handler.num_emits
handler.reset()
if __name__ == "__main__":
test()
if logging is not None:
testLogger()
from win32com.test.util import CheckClean
CheckClean()
print("error semantic tests worked")

View file

@ -0,0 +1,66 @@
// TestServer.idl : IDL source for TestServer.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (TestServer.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(50086EE8-F535-464B-806E-365ADBB727CF),
dual,
helpstring("ITestServerApp Interface"),
pointer_default(unique)
]
interface ITestServerApp : IDispatch
{
[id(1), helpstring("method Test1")] HRESULT Test1([out, retval] ITestServerApp **pVal);
[id(2), helpstring("method Test2")] HRESULT Test2([out, retval] VARIANT *pVar);
[propget, id(3), helpstring("property MyProp1")] HRESULT MyProp1([out, retval] long *pVal);
};
[
object,
uuid(618DB2A3-D5BD-4850-B66A-828727EB37E5),
dual,
helpstring("IPippo Interface"),
pointer_default(unique)
]
interface IPippo : IDispatch
{
[id(1), helpstring("method Method1")] HRESULT Method1([out, retval] IPippo **val);
[propget, id(2), helpstring("property MyProp1")] HRESULT MyProp1([out, retval] long *pVal);
[id(3), helpstring("method Method2")] HRESULT Method2([in] long in1, [in, out] long *inout1,
[out, retval] long *val);
[id(4), helpstring("method Method3")] HRESULT Method3([in] VARIANT in1,
[out, retval] VARIANT *val);
};
[
uuid(7783054E-9A20-4584-8C62-6ED2A08F6AC6),
version(1.0),
helpstring("TestServer 1.0 Type Library")
]
library TESTSERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
importlib("msado15.dll");
[
uuid(49E44E89-5A72-4456-B1D5-68268A19E798),
helpstring("TestServerApp Class")
]
coclass TestServerApp
{
[default] interface ITestServerApp;
};
[
uuid(1F0F75D6-BD63-41B9-9F88-2D9D2E1AA5C3),
helpstring("Pippo Class")
]
coclass Pippo
{
[default] interface IPippo;
};
};

View file

@ -0,0 +1,97 @@
# A little test server, complete with typelib, we can use for testing.
# Originally submitted with bug:
# [ 753154 ] memory leak wrapping object having _typelib_guid_ attribute
# but modified by mhammond for use as part of the test suite.
import os
import sys
import pythoncom
import win32com
import winerror
from win32com.server.util import wrap
class CPippo:
#
# COM declarations
#
_reg_clsid_ = "{1F0F75D6-BD63-41B9-9F88-2D9D2E1AA5C3}"
_reg_desc_ = "Pippo Python test object"
_reg_progid_ = "Python.Test.Pippo"
# _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
###
### Link to typelib
_typelib_guid_ = "{7783054E-9A20-4584-8C62-6ED2A08F6AC6}"
_typelib_version_ = 1, 0
_com_interfaces_ = ["IPippo"]
def __init__(self):
self.MyProp1 = 10
def Method1(self):
return wrap(CPippo())
def Method2(self, in1, inout1):
return in1, inout1 * 2
def Method3(self, in1):
# in1 will be a tuple, not a list.
# Yet, we are not allowed to return a tuple, but need to convert it to a list first. (Bug?)
return list(in1)
def BuildTypelib():
from distutils.dep_util import newer
this_dir = os.path.dirname(__file__)
idl = os.path.abspath(os.path.join(this_dir, "pippo.idl"))
tlb = os.path.splitext(idl)[0] + ".tlb"
if newer(idl, tlb):
print("Compiling %s" % (idl,))
rc = os.system('midl "%s"' % (idl,))
if rc:
raise RuntimeError("Compiling MIDL failed!")
# Can't work out how to prevent MIDL from generating the stubs.
# just nuke them
for fname in "dlldata.c pippo_i.c pippo_p.c pippo.h".split():
os.remove(os.path.join(this_dir, fname))
print("Registering %s" % (tlb,))
tli = pythoncom.LoadTypeLib(tlb)
pythoncom.RegisterTypeLib(tli, tlb)
def UnregisterTypelib():
k = CPippo
try:
pythoncom.UnRegisterTypeLib(
k._typelib_guid_,
k._typelib_version_[0],
k._typelib_version_[1],
0,
pythoncom.SYS_WIN32,
)
print("Unregistered typelib")
except pythoncom.error as details:
if details[0] == winerror.TYPE_E_REGISTRYACCESS:
pass
else:
raise
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
if "--unregister" in argv:
# Unregister the type-libraries.
UnregisterTypelib()
else:
# Build and register the type-libraries.
BuildTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CPippo)
if __name__ == "__main__":
main(sys.argv)

View file

@ -0,0 +1,116 @@
import unittest
import pythoncom
import win32com.client
import win32com.server.util
import win32com.test.util
import winerror
class Error(Exception):
pass
# An object representing a list of numbers
class PythonSemanticClass:
_public_methods_ = ["In"] # DISPIDs are allocated.
_dispid_to_func_ = {10: "Add", 11: "Remove"} # DISPIDs specified by the object.
def __init__(self):
self.list = []
def _NewEnum(self):
return win32com.server.util.NewEnum(self.list)
def _value_(self):
# should return an array.
return self.list
def _Evaluate(self):
# return the sum
return sum(self.list)
def In(self, value):
return value in self.list
def Add(self, value):
self.list.append(value)
def Remove(self, value):
self.list.remove(value)
def DispExTest(ob):
if not __debug__:
print("WARNING: Tests dressed up as assertions are being skipped!")
assert ob.GetDispID("Add", 0) == 10, "Policy did not honour the dispid"
# Not impl
# assert ob.GetMemberName(10, 0)=="add", "Policy did not give me the correct function for the dispid"
assert ob.GetDispID("Remove", 0) == 11, "Policy did not honour the dispid"
assert ob.GetDispID("In", 0) == 1000, "Allocated dispid unexpected value"
assert (
ob.GetDispID("_NewEnum", 0) == pythoncom.DISPID_NEWENUM
), "_NewEnum() got unexpected DISPID"
dispids = []
dispid = -1
while 1:
try:
dispid = ob.GetNextDispID(0, dispid)
dispids.append(dispid)
except pythoncom.com_error as xxx_todo_changeme:
(hr, desc, exc, arg) = xxx_todo_changeme.args
assert hr == winerror.S_FALSE, "Bad result at end of enum"
break
dispids.sort()
if dispids != [pythoncom.DISPID_EVALUATE, pythoncom.DISPID_NEWENUM, 10, 11, 1000]:
raise Error("Got back the wrong dispids: %s" % dispids)
def SemanticTest(ob):
# First just check our object "generally" as expected.
ob.Add(1)
ob.Add(2)
ob.Add(3)
# invoke _value_
if ob() != (1, 2, 3):
raise Error("Bad result - got %s" % (repr(ob())))
dispob = ob._oleobj_
rc = dispob.Invoke(
pythoncom.DISPID_EVALUATE,
0,
pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET,
1,
)
if rc != 6:
raise Error("Evaluate returned %d" % rc)
class Tester(win32com.test.util.TestCase):
def setUp(self):
debug = 0
import win32com.server.dispatcher
if debug:
dispatcher = win32com.server.dispatcher.DefaultDebugDispatcher
else:
dispatcher = None
disp = win32com.server.util.wrap(
PythonSemanticClass(), useDispatcher=dispatcher
)
self.ob = win32com.client.Dispatch(disp)
def tearDown(self):
self.ob = None
def testSemantics(self):
SemanticTest(self.ob)
def testIDispatchEx(self):
dispexob = self.ob._oleobj_.QueryInterface(pythoncom.IID_IDispatchEx)
DispExTest(dispexob)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,18 @@
COM Test Suite Readme
---------------------
Running the test suite:
-----------------------
* Open a command prompt
* Change to the "win32com\test" directory.
* run "testall.py". This will perform level 1 testing.
You may specify 1, 2, or 3 on the command line ("testutil 3")
to execute more tests.
In general, this should just run the best it can, utilizing what is available
on the machine. It is likely some tests will refuse to run due to objects not
being locally available - this is normal.
The win32com source tree has source code to a C++ and VB component used purely
for testing. You may like to build and register these, particularly if you
are doing anything related to argument/result handling.

View file

@ -0,0 +1,100 @@
import os
import time
import pythoncom
from win32com.client import Dispatch, DispatchWithEvents, constants
finished = 0 # Flag for the wait loop from (3) to test
class ADOEvents: # event handler class
def OnWillConnect(self, str, user, pw, opt, sts, cn):
# Must have this event, as if it is not handled, ADO assumes the
# operation is cancelled, and raises an error (Operation cancelled
# by the user)
pass
def OnConnectComplete(self, error, status, connection):
# Assume no errors, until we have the basic stuff
# working. Now, "connection" should be an open
# connection to my data source
# Do the "something" from (2). For now, just
# print the connection data source
print("connection is", connection)
print("Connected to", connection.Properties("Data Source"))
# OK, our work is done. Let the main loop know
global finished
finished = 1
def OnCommitTransComplete(self, pError, adStatus, pConnection):
pass
def OnInfoMessage(self, pError, adStatus, pConnection):
pass
def OnDisconnect(self, adStatus, pConnection):
pass
def OnBeginTransComplete(self, TransactionLevel, pError, adStatus, pConnection):
pass
def OnRollbackTransComplete(self, pError, adStatus, pConnection):
pass
def OnExecuteComplete(
self, RecordsAffected, pError, adStatus, pCommand, pRecordset, pConnection
):
pass
def OnWillExecute(
self,
Source,
CursorType,
LockType,
Options,
adStatus,
pCommand,
pRecordset,
pConnection,
):
pass
def TestConnection(dbname):
# Create the ADO connection object, and link the event
# handlers into it
c = DispatchWithEvents("ADODB.Connection", ADOEvents)
# Initiate the asynchronous open
dsn = "Driver={Microsoft Access Driver (*.mdb)};Dbq=%s" % dbname
user = "system"
pw = "manager"
c.Open(dsn, user, pw, constants.adAsyncConnect)
# Sit in a loop, until our event handler (above) sets the
# "finished" flag or we time out.
end_time = time.clock() + 10
while time.clock() < end_time:
# Pump messages so that COM gets a look in
pythoncom.PumpWaitingMessages()
if not finished:
print("XXX - Failed to connect!")
def Test():
from . import testAccess
try:
testAccess.GenerateSupport()
except pythoncom.com_error:
print("*** Can not import the MSAccess type libraries - tests skipped")
return
dbname = testAccess.CreateTestAccessDatabase()
try:
TestConnection(dbname)
finally:
os.unlink(dbname)
if __name__ == "__main__":
Test()

View file

@ -0,0 +1,43 @@
# Test AXScripting the best we can in an automated fashion...
import os
import sys
import win32api
import win32com.axscript
import win32com.axscript.client
import win32com.test.util
verbose = "-v" in sys.argv
class AXScript(win32com.test.util.TestCase):
def setUp(self):
file = win32api.GetFullPathName(
os.path.join(win32com.axscript.client.__path__[0], "pyscript.py")
)
from win32com.test.util import RegisterPythonServer
self.verbose = verbose
RegisterPythonServer(file, "python", verbose=self.verbose)
def testHost(self):
file = win32api.GetFullPathName(
os.path.join(win32com.axscript.__path__[0], "test\\testHost.py")
)
cmd = '%s "%s"' % (win32api.GetModuleFileName(0), file)
if verbose:
print("Testing Python Scripting host")
win32com.test.util.ExecuteShellCommand(cmd, self)
def testCScript(self):
file = win32api.GetFullPathName(
os.path.join(win32com.axscript.__path__[0], "Demos\\Client\\wsh\\test.pys")
)
cmd = 'cscript.exe "%s"' % (file)
if verbose:
print("Testing Windows Scripting host with Python script")
win32com.test.util.ExecuteShellCommand(cmd, self)
if __name__ == "__main__":
win32com.test.util.testmain()

View file

@ -0,0 +1,187 @@
#
# This assumes that you have MSAccess and DAO installed.
# You need to run makepy.py over "msaccess.tlb" and
# "dao3032.dll", and ensure the generated files are on the
# path.
# You can run this with no args, and a test database will be generated.
# You can optionally pass a dbname on the command line, in which case it will be dumped.
import os
import sys
import pythoncom
import win32api
from win32com.client import Dispatch, constants, gencache
def CreateTestAccessDatabase(dbname=None):
# Creates a test access database - returns the filename.
if dbname is None:
dbname = os.path.join(win32api.GetTempPath(), "COMTestSuiteTempDatabase.mdb")
access = Dispatch("Access.Application")
dbEngine = access.DBEngine
workspace = dbEngine.Workspaces(0)
try:
os.unlink(dbname)
except os.error:
print(
"WARNING - Unable to delete old test database - expect a COM exception RSN!"
)
newdb = workspace.CreateDatabase(
dbname, constants.dbLangGeneral, constants.dbEncrypt
)
# Create one test table.
table = newdb.CreateTableDef("Test Table 1")
table.Fields.Append(table.CreateField("First Name", constants.dbText))
table.Fields.Append(table.CreateField("Last Name", constants.dbText))
index = table.CreateIndex("UniqueIndex")
index.Fields.Append(index.CreateField("First Name"))
index.Fields.Append(index.CreateField("Last Name"))
index.Unique = -1
table.Indexes.Append(index)
newdb.TableDefs.Append(table)
# Create a second test table.
table = newdb.CreateTableDef("Test Table 2")
table.Fields.Append(table.CreateField("First Name", constants.dbText))
table.Fields.Append(table.CreateField("Last Name", constants.dbText))
newdb.TableDefs.Append(table)
# Create a relationship between them
relation = newdb.CreateRelation("TestRelationship")
relation.Table = "Test Table 1"
relation.ForeignTable = "Test Table 2"
field = relation.CreateField("First Name")
field.ForeignName = "First Name"
relation.Fields.Append(field)
field = relation.CreateField("Last Name")
field.ForeignName = "Last Name"
relation.Fields.Append(field)
relation.Attributes = (
constants.dbRelationDeleteCascade + constants.dbRelationUpdateCascade
)
newdb.Relations.Append(relation)
# Finally we can add some data to the table.
tab1 = newdb.OpenRecordset("Test Table 1")
tab1.AddNew()
tab1.Fields("First Name").Value = "Mark"
tab1.Fields("Last Name").Value = "Hammond"
tab1.Update()
tab1.MoveFirst()
# We do a simple bookmark test which tests our optimized VT_SAFEARRAY|VT_UI1 support.
# The bookmark will be a buffer object - remember it for later.
bk = tab1.Bookmark
# Add a second record.
tab1.AddNew()
tab1.Fields("First Name").Value = "Second"
tab1.Fields("Last Name").Value = "Person"
tab1.Update()
# Reset the bookmark to the one we saved.
# But first check the test is actually doing something!
tab1.MoveLast()
if tab1.Fields("First Name").Value != "Second":
raise RuntimeError("Unexpected record is last - makes bookmark test pointless!")
tab1.Bookmark = bk
if tab1.Bookmark != bk:
raise RuntimeError("The bookmark data is not the same")
if tab1.Fields("First Name").Value != "Mark":
raise RuntimeError("The bookmark did not reset the record pointer correctly")
return dbname
def DoDumpAccessInfo(dbname):
from . import daodump
a = forms = None
try:
sys.stderr.write("Creating Access Application...\n")
a = Dispatch("Access.Application")
print("Opening database %s" % dbname)
a.OpenCurrentDatabase(dbname)
db = a.CurrentDb()
daodump.DumpDB(db, 1)
forms = a.Forms
print("There are %d forms open." % (len(forms)))
# Uncommenting these lines means Access remains open.
# for form in forms:
# print " %s" % form.Name
reports = a.Reports
print("There are %d reports open" % (len(reports)))
finally:
if not a is None:
sys.stderr.write("Closing database\n")
try:
a.CloseCurrentDatabase()
except pythoncom.com_error:
pass
# Generate all the support we can.
def GenerateSupport():
# dao
gencache.EnsureModule("{00025E01-0000-0000-C000-000000000046}", 0, 4, 0)
# Access
# gencache.EnsureModule("{4AFFC9A0-5F99-101B-AF4E-00AA003F0F07}", 0, 8, 0)
gencache.EnsureDispatch("Access.Application")
def DumpAccessInfo(dbname):
amod = gencache.GetModuleForProgID("Access.Application")
dmod = gencache.GetModuleForProgID("DAO.DBEngine.35")
if amod is None and dmod is None:
DoDumpAccessInfo(dbname)
# Now generate all the support we can.
GenerateSupport()
else:
sys.stderr.write(
"testAccess not doing dynamic test, as generated code already exists\n"
)
# Now a generated version.
DoDumpAccessInfo(dbname)
def test(dbname=None):
if dbname is None:
# We need makepy support to create a database (just for the constants!)
try:
GenerateSupport()
except pythoncom.com_error:
print("*** Can not import the MSAccess type libraries - tests skipped")
return
dbname = CreateTestAccessDatabase()
print("A test database at '%s' was created" % dbname)
DumpAccessInfo(dbname)
if __name__ == "__main__":
import sys
from .util import CheckClean
dbname = None
if len(sys.argv) > 1:
dbname = sys.argv[1]
test(dbname)
CheckClean()

View file

@ -0,0 +1,99 @@
# Originally contributed by Stefan Schukat as part of this arbitrary-sized
# arrays patch.
from win32com.client import gencache
from win32com.test import util
ZeroD = 0
OneDEmpty = []
OneD = [1, 2, 3]
TwoD = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
TwoD1 = [[[1, 2, 3, 5], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]
OneD1 = [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]]
OneD2 = [
[1, 2, 3],
[1, 2, 3, 4, 5],
[[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]],
]
ThreeD = [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]
FourD = [
[
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
],
[
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
],
]
LargeD = [
[[list(range(10))] * 10],
] * 512
def _normalize_array(a):
if type(a) != type(()):
return a
ret = []
for i in a:
ret.append(_normalize_array(i))
return ret
class ArrayTest(util.TestCase):
def setUp(self):
self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest")
def tearDown(self):
self.arr = None
def _doTest(self, array):
self.arr.Array = array
self.assertEqual(_normalize_array(self.arr.Array), array)
def testZeroD(self):
self._doTest(ZeroD)
def testOneDEmpty(self):
self._doTest(OneDEmpty)
def testOneD(self):
self._doTest(OneD)
def testTwoD(self):
self._doTest(TwoD)
def testThreeD(self):
self._doTest(ThreeD)
def testFourD(self):
self._doTest(FourD)
def testTwoD1(self):
self._doTest(TwoD1)
def testOneD1(self):
self._doTest(OneD1)
def testOneD2(self):
self._doTest(OneD2)
def testLargeD(self):
self._doTest(LargeD)
if __name__ == "__main__":
try:
util.testmain()
except SystemExit as rc:
if not rc:
raise

View file

@ -0,0 +1,170 @@
# testClipboard.py
import unittest
import pythoncom
import win32clipboard
import win32con
import winerror
from win32com.server.exception import COMException
from win32com.server.util import NewEnum, wrap
IDataObject_Methods = """GetData GetDataHere QueryGetData
GetCanonicalFormatEtc SetData EnumFormatEtc
DAdvise DUnadvise EnumDAdvise""".split()
# A COM object implementing IDataObject used for basic testing.
num_do_objects = 0
def WrapCOMObject(ob, iid=None):
return wrap(ob, iid=iid, useDispatcher=0)
class TestDataObject:
_com_interfaces_ = [pythoncom.IID_IDataObject]
_public_methods_ = IDataObject_Methods
def __init__(self, bytesval):
global num_do_objects
num_do_objects += 1
self.bytesval = bytesval
self.supported_fe = []
for cf in (win32con.CF_TEXT, win32con.CF_UNICODETEXT):
fe = cf, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL
self.supported_fe.append(fe)
def __del__(self):
global num_do_objects
num_do_objects -= 1
def _query_interface_(self, iid):
if iid == pythoncom.IID_IEnumFORMATETC:
return NewEnum(self.supported_fe, iid=iid)
def GetData(self, fe):
ret_stg = None
cf, target, aspect, index, tymed = fe
if aspect & pythoncom.DVASPECT_CONTENT and tymed == pythoncom.TYMED_HGLOBAL:
if cf == win32con.CF_TEXT:
ret_stg = pythoncom.STGMEDIUM()
ret_stg.set(pythoncom.TYMED_HGLOBAL, self.bytesval)
elif cf == win32con.CF_UNICODETEXT:
ret_stg = pythoncom.STGMEDIUM()
ret_stg.set(pythoncom.TYMED_HGLOBAL, self.bytesval.decode("latin1"))
if ret_stg is None:
raise COMException(hresult=winerror.E_NOTIMPL)
return ret_stg
def GetDataHere(self, fe):
raise COMException(hresult=winerror.E_NOTIMPL)
def QueryGetData(self, fe):
cf, target, aspect, index, tymed = fe
if aspect & pythoncom.DVASPECT_CONTENT == 0:
raise COMException(hresult=winerror.DV_E_DVASPECT)
if tymed != pythoncom.TYMED_HGLOBAL:
raise COMException(hresult=winerror.DV_E_TYMED)
return None # should check better
def GetCanonicalFormatEtc(self, fe):
RaiseCOMException(winerror.DATA_S_SAMEFORMATETC)
# return fe
def SetData(self, fe, medium):
raise COMException(hresult=winerror.E_NOTIMPL)
def EnumFormatEtc(self, direction):
if direction != pythoncom.DATADIR_GET:
raise COMException(hresult=winerror.E_NOTIMPL)
return NewEnum(self.supported_fe, iid=pythoncom.IID_IEnumFORMATETC)
def DAdvise(self, fe, flags, sink):
raise COMException(hresult=winerror.E_NOTIMPL)
def DUnadvise(self, connection):
raise COMException(hresult=winerror.E_NOTIMPL)
def EnumDAdvise(self):
raise COMException(hresult=winerror.E_NOTIMPL)
class ClipboardTester(unittest.TestCase):
def setUp(self):
pythoncom.OleInitialize()
def tearDown(self):
try:
pythoncom.OleFlushClipboard()
except pythoncom.com_error:
# We never set anything!
pass
def testIsCurrentClipboard(self):
do = TestDataObject(b"Hello from Python")
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
pythoncom.OleSetClipboard(do)
self.assertTrue(pythoncom.OleIsCurrentClipboard(do))
def testComToWin32(self):
# Set the data via our DataObject
do = TestDataObject(b"Hello from Python")
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
pythoncom.OleSetClipboard(do)
# Then get it back via the standard win32 clipboard functions.
win32clipboard.OpenClipboard()
got = win32clipboard.GetClipboardData(win32con.CF_TEXT)
# CF_TEXT gives bytes.
expected = b"Hello from Python"
self.assertEqual(got, expected)
# Now check unicode
got = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
self.assertEqual(got, "Hello from Python")
win32clipboard.CloseClipboard()
def testWin32ToCom(self):
# Set the data via the std win32 clipboard functions.
val = b"Hello again!" # always bytes
win32clipboard.OpenClipboard()
win32clipboard.SetClipboardData(win32con.CF_TEXT, val)
win32clipboard.CloseClipboard()
# and get it via an IDataObject provided by COM
do = pythoncom.OleGetClipboard()
cf = (
win32con.CF_TEXT,
None,
pythoncom.DVASPECT_CONTENT,
-1,
pythoncom.TYMED_HGLOBAL,
)
stg = do.GetData(cf)
got = stg.data
# The data we get back has the \0, as our STGMEDIUM has no way of
# knowing if it meant to be a string, or a binary buffer, so
# it must return it too.
self.assertTrue(got, b"Hello again!\0")
def testDataObjectFlush(self):
do = TestDataObject(b"Hello from Python")
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
pythoncom.OleSetClipboard(do)
self.assertEqual(num_do_objects, 1)
do = None # clear my ref!
pythoncom.OleFlushClipboard()
self.assertEqual(num_do_objects, 0)
def testDataObjectReset(self):
do = TestDataObject(b"Hello from Python")
do = WrapCOMObject(do)
pythoncom.OleSetClipboard(do)
do = None # clear my ref!
self.assertEqual(num_do_objects, 1)
pythoncom.OleSetClipboard(None)
self.assertEqual(num_do_objects, 0)
if __name__ == "__main__":
from win32com.test import util
util.testmain()

View file

@ -0,0 +1,166 @@
# testCollections.py
#
# This code tests both the client and server side of collections
# and enumerators.
#
# Also has the side effect of testing some of the PythonCOM error semantics.
import sys
import pythoncom
import pywintypes
import win32com.client
import win32com.server.util
import win32com.test.util
import winerror
L = pywintypes.Unicode
import unittest
error = "collection test error"
def MakeEmptyEnum():
# create the Python enumerator object as a real COM object
o = win32com.server.util.wrap(win32com.server.util.Collection())
return win32com.client.Dispatch(o)
def MakeTestEnum():
# create a sub-collection, just to make sure it works :-)
sub = win32com.server.util.wrap(
win32com.server.util.Collection(["Sub1", 2, "Sub3"])
)
# create the Python enumerator object as a real COM object
o = win32com.server.util.wrap(win32com.server.util.Collection([1, "Two", 3, sub]))
return win32com.client.Dispatch(o)
def TestEnumAgainst(o, check):
for i in range(len(check)):
if o(i) != check[i]:
raise error(
"Using default method gave the incorrect value - %s/%s"
% (repr(o(i)), repr(check[i]))
)
for i in range(len(check)):
if o.Item(i) != check[i]:
raise error(
"Using Item method gave the incorrect value - %s/%s"
% (repr(o(i)), repr(check[i]))
)
# First try looping.
cmp = []
for s in o:
cmp.append(s)
if cmp[: len(check)] != check:
raise error(
"Result after looping isnt correct - %s/%s"
% (repr(cmp[: len(check)]), repr(check))
)
for i in range(len(check)):
if o[i] != check[i]:
raise error("Using indexing gave the incorrect value")
def TestEnum(quiet=None):
if quiet is None:
quiet = not "-v" in sys.argv
if not quiet:
print("Simple enum test")
o = MakeTestEnum()
check = [1, "Two", 3]
TestEnumAgainst(o, check)
if not quiet:
print("sub-collection test")
sub = o[3]
TestEnumAgainst(sub, ["Sub1", 2, "Sub3"])
# Remove the sublist for this test!
o.Remove(o.Count() - 1)
if not quiet:
print("Remove item test")
del check[1]
o.Remove(1)
TestEnumAgainst(o, check)
if not quiet:
print("Add item test")
o.Add("New Item")
check.append("New Item")
TestEnumAgainst(o, check)
if not quiet:
print("Insert item test")
o.Insert(2, -1)
check.insert(2, -1)
TestEnumAgainst(o, check)
### This does not work!
# if not quiet: print "Indexed replace item test"
# o[2] = 'Replaced Item'
# check[2] = 'Replaced Item'
# TestEnumAgainst(o, check)
try:
o()
raise error("default method with no args worked when it shouldnt have!")
except pythoncom.com_error as exc:
if exc.hresult != winerror.DISP_E_BADPARAMCOUNT:
raise error("Expected DISP_E_BADPARAMCOUNT - got %s" % (exc,))
try:
o.Insert("foo", 2)
raise error("Insert worked when it shouldnt have!")
except pythoncom.com_error as exc:
if exc.hresult != winerror.DISP_E_TYPEMISMATCH:
raise error("Expected DISP_E_TYPEMISMATCH - got %s" % (exc,))
# Remove the sublist for this test!
try:
o.Remove(o.Count())
raise error("Remove worked when it shouldnt have!")
except pythoncom.com_error as exc:
if exc.hresult != winerror.DISP_E_BADINDEX:
raise error("Expected DISP_E_BADINDEX - got %s" % (exc,))
# Test an empty collection
if not quiet:
print("Empty collection test")
o = MakeEmptyEnum()
for item in o:
raise error("Empty list performed an iteration")
try:
ob = o[1]
raise error("Empty list could be indexed")
except IndexError:
pass
try:
ob = o[0]
raise error("Empty list could be indexed")
except IndexError:
pass
try:
ob = o(0)
raise error("Empty list could be indexed")
except pythoncom.com_error as exc:
if exc.hresult != winerror.DISP_E_BADINDEX:
raise error("Expected DISP_E_BADINDEX - got %s" % (exc,))
class TestCase(win32com.test.util.TestCase):
def testEnum(self):
TestEnum()
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,39 @@
import unittest
import win32com.client
import win32com.server.util
import win32com.test.util
class Tester:
_public_methods_ = ["TestValue"]
def TestValue(self, v):
pass
def test_ob():
return win32com.client.Dispatch(win32com.server.util.wrap(Tester()))
class TestException(Exception):
pass
# The object we try and pass - pywin32 will call __float__ as a last resort.
class BadConversions:
def __float__(self):
raise TestException()
class TestCase(win32com.test.util.TestCase):
def test_float(self):
try:
test_ob().TestValue(BadConversions())
raise Exception("Should not have worked")
except Exception as e:
assert isinstance(e, TestException)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,50 @@
# testDCOM
usage = """\
testDCOM.py - Simple DCOM test
Usage: testDCOM.py serverName
Attempts to start the Python.Interpreter object on the named machine,
and checks that the object is indeed running remotely.
Requires the named server be configured to run DCOM (using dcomcnfg.exe),
and the Python.Interpreter object installed and registered on that machine.
The Python.Interpreter object must be installed on the local machine,
but no special DCOM configuration should be necessary.
"""
import string
import sys
# NOTE: If you configured the object locally using dcomcnfg, you could
# simple use Dispatch rather than DispatchEx.
import pythoncom
import win32api
import win32com.client
def test(serverName):
if string.lower(serverName) == string.lower(win32api.GetComputerName()):
print("You must specify a remote server name, not the local machine!")
return
# Hack to overcome a DCOM limitation. As the Python.Interpreter object
# is probably installed locally as an InProc object, DCOM seems to ignore
# all settings, and use the local object.
clsctx = pythoncom.CLSCTX_SERVER & ~pythoncom.CLSCTX_INPROC_SERVER
ob = win32com.client.DispatchEx("Python.Interpreter", serverName, clsctx=clsctx)
ob.Exec("import win32api")
actualName = ob.Eval("win32api.GetComputerName()")
if string.lower(serverName) != string.lower(actualName):
print(
"Error: The object created on server '%s' reported its name as '%s'"
% (serverName, actualName)
)
else:
print("Object created and tested OK on server '%s'" % serverName)
if __name__ == "__main__":
if len(sys.argv) == 2:
test(sys.argv[1])
else:
print(usage)

View file

@ -0,0 +1,74 @@
import unittest
from datetime import datetime
import pywintypes
import win32com.client
import win32com.server.util
import win32com.test.util
from win32timezone import TimeZoneInfo
# A COM object so we can pass dates to and from the COM boundary.
class Tester:
_public_methods_ = ["TestDate"]
def TestDate(self, d):
assert isinstance(d, datetime)
return d
def test_ob():
return win32com.client.Dispatch(win32com.server.util.wrap(Tester()))
class TestCase(win32com.test.util.TestCase):
def check(self, d, expected=None):
if not issubclass(pywintypes.TimeType, datetime):
self.skipTest("this is testing pywintypes and datetime")
got = test_ob().TestDate(d)
self.assertEqual(got, expected or d)
def testUTC(self):
self.check(
datetime(
year=2000,
month=12,
day=25,
microsecond=500000,
tzinfo=TimeZoneInfo.utc(),
)
)
def testLocal(self):
self.check(
datetime(
year=2000,
month=12,
day=25,
microsecond=500000,
tzinfo=TimeZoneInfo.local(),
)
)
def testMSTruncated(self):
# milliseconds are kept but microseconds are lost after rounding.
self.check(
datetime(
year=2000,
month=12,
day=25,
microsecond=500500,
tzinfo=TimeZoneInfo.utc(),
),
datetime(
year=2000,
month=12,
day=25,
microsecond=500000,
tzinfo=TimeZoneInfo.utc(),
),
)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,99 @@
# testDictionary.py
#
import sys
import unittest
import pythoncom
import pywintypes
import win32com.client
import win32com.server.util
import win32com.test.util
import win32timezone
import winerror
def MakeTestDictionary():
return win32com.client.Dispatch("Python.Dictionary")
def TestDictAgainst(dict, check):
for key, value in list(check.items()):
if dict(key) != value:
raise Exception(
"Indexing for '%s' gave the incorrect value - %s/%s"
% (repr(key), repr(dict[key]), repr(check[key]))
)
# Ensure we have the correct version registered.
def Register(quiet):
import win32com.servers.dictionary
from win32com.test.util import RegisterPythonServer
RegisterPythonServer(win32com.servers.dictionary.__file__, "Python.Dictionary")
def TestDict(quiet=None):
if quiet is None:
quiet = not "-v" in sys.argv
Register(quiet)
if not quiet:
print("Simple enum test")
dict = MakeTestDictionary()
checkDict = {}
TestDictAgainst(dict, checkDict)
dict["NewKey"] = "NewValue"
checkDict["NewKey"] = "NewValue"
TestDictAgainst(dict, checkDict)
dict["NewKey"] = None
del checkDict["NewKey"]
TestDictAgainst(dict, checkDict)
now = win32timezone.now()
# We want to keep the milliseconds but discard microseconds as they
# don't survive the conversion.
now = now.replace(microsecond=round(now.microsecond / 1000) * 1000)
dict["Now"] = now
checkDict["Now"] = now
TestDictAgainst(dict, checkDict)
if not quiet:
print("Failure tests")
try:
dict()
raise Exception("default method with no args worked when it shouldnt have!")
except pythoncom.com_error as xxx_todo_changeme:
(hr, desc, exc, argErr) = xxx_todo_changeme.args
if hr != winerror.DISP_E_BADPARAMCOUNT:
raise Exception("Expected DISP_E_BADPARAMCOUNT - got %d (%s)" % (hr, desc))
try:
dict("hi", "there")
raise Exception("multiple args worked when it shouldnt have!")
except pythoncom.com_error as xxx_todo_changeme1:
(hr, desc, exc, argErr) = xxx_todo_changeme1.args
if hr != winerror.DISP_E_BADPARAMCOUNT:
raise Exception("Expected DISP_E_BADPARAMCOUNT - got %d (%s)" % (hr, desc))
try:
dict(0)
raise Exception("int key worked when it shouldnt have!")
except pythoncom.com_error as xxx_todo_changeme2:
(hr, desc, exc, argErr) = xxx_todo_changeme2.args
if hr != winerror.DISP_E_TYPEMISMATCH:
raise Exception("Expected DISP_E_TYPEMISMATCH - got %d (%s)" % (hr, desc))
if not quiet:
print("Python.Dictionary tests complete.")
class TestCase(win32com.test.util.TestCase):
def testDict(self):
TestDict()
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,26 @@
' Test Pyhon.Dictionary using VBScript - this uses
' IDispatchEx, so is an interesting test.
set ob = CreateObject("Python.Dictionary")
ob("hello") = "there"
' Our keys are case insensitive.
ob.Item("hi") = ob("HELLO")
dim ok
ok = true
if ob("hello") <> "there" then
WScript.Echo "**** The dictionary value was wrong!!"
ok = false
end if
if ob("hi") <> "there" then
WScript.Echo "**** The other dictionary value was wrong!!"
ok = false
end if
if ok then
WScript.Echo "VBScript has successfully tested Python.Dictionary"
end if

View file

@ -0,0 +1,89 @@
# Test dynamic policy, and running object table.
import pythoncom
import winerror
from win32com.server.exception import Exception
error = "testDynamic error"
iid = pythoncom.MakeIID("{b48969a0-784b-11d0-ae71-d23f56000000}")
class VeryPermissive:
def _dynamic_(self, name, lcid, wFlags, args):
if wFlags & pythoncom.DISPATCH_METHOD:
return getattr(self, name)(*args)
if wFlags & pythoncom.DISPATCH_PROPERTYGET:
try:
# to avoid problems with byref param handling, tuple results are converted to lists.
ret = self.__dict__[name]
if type(ret) == type(()):
ret = list(ret)
return ret
except KeyError: # Probably a method request.
raise Exception(scode=winerror.DISP_E_MEMBERNOTFOUND)
if wFlags & (
pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF
):
setattr(self, name, args[0])
return
raise Exception(scode=winerror.E_INVALIDARG, desc="invalid wFlags")
def write(self, *args):
if len(args) == 0:
raise Exception(
scode=winerror.DISP_E_BADPARAMCOUNT
) # Probably call as PROPGET.
for arg in args[:-1]:
print(str(arg), end=" ")
print(str(args[-1]))
def Test():
import win32com.server.policy
import win32com.server.util
# import win32dbg;win32dbg.brk()
ob = win32com.server.util.wrap(
VeryPermissive(), usePolicy=win32com.server.policy.DynamicPolicy
)
try:
handle = pythoncom.RegisterActiveObject(ob, iid, 0)
except pythoncom.com_error as details:
print("Warning - could not register the object in the ROT:", details)
handle = None
try:
import win32com.client.dynamic
client = win32com.client.dynamic.Dispatch(iid)
client.ANewAttr = "Hello"
if client.ANewAttr != "Hello":
raise error("Could not set dynamic property")
v = ["Hello", "From", "Python", 1.4]
client.TestSequence = v
if v != list(client.TestSequence):
raise error(
"Dynamic sequences not working! %r/%r"
% (repr(v), repr(client.testSequence))
)
client.write("This", "output", "has", "come", "via", "testDynamic.py")
# Check our new "_FlagAsMethod" works (kinda!)
client._FlagAsMethod("NotReallyAMethod")
if not callable(client.NotReallyAMethod):
raise error("Method I flagged as callable isn't!")
client = None
finally:
if handle is not None:
pythoncom.RevokeActiveObject(handle)
print("Test worked!")
if __name__ == "__main__":
Test()

View file

@ -0,0 +1,126 @@
# TestExchange = Exchange Server Dump
# Note that this code uses "CDO", which is unlikely to get the best choice.
# You should use the Outlook object model, or
# the win32com.mapi examples for a low-level interface.
import os
import pythoncom
from win32com.client import constants, gencache
ammodule = None # was the generated module!
def GetDefaultProfileName():
import win32api
import win32con
try:
key = win32api.RegOpenKey(
win32con.HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles",
)
try:
return win32api.RegQueryValueEx(key, "DefaultProfile")[0]
finally:
key.Close()
except win32api.error:
return None
#
# Recursive dump of folders.
#
def DumpFolder(folder, indent=0):
print(" " * indent, folder.Name)
folders = folder.Folders
folder = folders.GetFirst()
while folder:
DumpFolder(folder, indent + 1)
folder = folders.GetNext()
def DumpFolders(session):
try:
infostores = session.InfoStores
except AttributeError:
# later outlook?
store = session.DefaultStore
folder = store.GetRootFolder()
DumpFolder(folder)
return
print(infostores)
print("There are %d infostores" % infostores.Count)
for i in range(infostores.Count):
infostore = infostores[i + 1]
print("Infostore = ", infostore.Name)
try:
folder = infostore.RootFolder
except pythoncom.com_error as details:
hr, msg, exc, arg = details
# -2147221219 == MAPI_E_FAILONEPROVIDER - a single provider temporarily not available.
if exc and exc[-1] == -2147221219:
print("This info store is currently not available")
continue
DumpFolder(folder)
# Build a dictionary of property tags, so I can reverse look-up
#
PropTagsById = {}
if ammodule:
for name, val in ammodule.constants.__dict__.items():
PropTagsById[val] = name
def TestAddress(session):
# entry = session.GetAddressEntry("Skip")
# print entry
pass
def TestUser(session):
ae = session.CurrentUser
fields = getattr(ae, "Fields", [])
print("User has %d fields" % len(fields))
for f in range(len(fields)):
field = fields[f + 1]
try:
id = PropTagsById[field.ID]
except KeyError:
id = field.ID
print("%s/%s=%s" % (field.Name, id, field.Value))
def test():
import win32com.client
oldcwd = os.getcwd()
try:
session = gencache.EnsureDispatch("MAPI.Session")
try:
session.Logon(GetDefaultProfileName())
except pythoncom.com_error as details:
print("Could not log on to MAPI:", details)
return
except pythoncom.error:
# no mapi.session - let's try outlook
app = gencache.EnsureDispatch("Outlook.Application")
session = app.Session
try:
TestUser(session)
TestAddress(session)
DumpFolders(session)
finally:
session.Logoff()
# It appears Exchange will change the cwd on us :(
os.chdir(oldcwd)
if __name__ == "__main__":
from .util import CheckClean
test()
CheckClean()

View file

@ -0,0 +1,145 @@
# testExplorer -
import os
import time
import pythoncom
import win32api
import win32com.client.dynamic
import win32con
import win32gui
import winerror
from win32com.client import Dispatch
from win32com.test.util import CheckClean
bVisibleEventFired = 0
# These are errors we might see when this is run in automation (eg, on github)
# Not sure exactly what -2125463506 is, but google shows it's a common error
# possibly related to how IE is configured WRT site permissions etc.
HRESULTS_IN_AUTOMATION = [-2125463506, winerror.MK_E_UNAVAILABLE]
class ExplorerEvents:
def OnVisible(self, visible):
global bVisibleEventFired
bVisibleEventFired = 1
def TestExplorerEvents():
global bVisibleEventFired
try:
iexplore = win32com.client.DispatchWithEvents(
"InternetExplorer.Application", ExplorerEvents
)
except pythoncom.com_error as exc:
# In automation we see this error trying to connect to events
# It's a little surprising that the non-event tests seem to work, but
# whatever...
if exc.hresult not in HRESULTS_IN_AUTOMATION:
raise
print("IE events appear to not be available, so skipping this test")
return
iexplore.Visible = 1
if not bVisibleEventFired:
raise RuntimeError("The IE event did not appear to fire!")
iexplore.Quit()
iexplore = None
bVisibleEventFired = 0
ie = win32com.client.Dispatch("InternetExplorer.Application")
ie_events = win32com.client.DispatchWithEvents(ie, ExplorerEvents)
ie.Visible = 1
if not bVisibleEventFired:
raise RuntimeError("The IE event did not appear to fire!")
ie.Quit()
ie = None
print("IE Event tests worked.")
def TestObjectFromWindow():
# Check we can use ObjectFromLresult to get the COM object from the
# HWND - see KB Q249232
# Locating the HWND is different than the KB says...
hwnd = win32gui.FindWindow("IEFrame", None)
for child_class in [
"TabWindowClass",
"Shell DocObject View",
"Internet Explorer_Server",
]:
hwnd = win32gui.FindWindowEx(hwnd, 0, child_class, None)
# ack - not working for markh on vista with IE8 (or maybe it is the
# lack of the 'accessibility' components mentioned in Q249232)
# either way - not working!
return
# But here is the point - once you have an 'Internet Explorer_Server',
# you can send a message and use ObjectFromLresult to get it back.
msg = win32gui.RegisterWindowMessage("WM_HTML_GETOBJECT")
rc, result = win32gui.SendMessageTimeout(
hwnd, msg, 0, 0, win32con.SMTO_ABORTIFHUNG, 1000
)
ob = pythoncom.ObjectFromLresult(result, pythoncom.IID_IDispatch, 0)
doc = Dispatch(ob)
# just to prove it works, set the background color of the document.
for color in "red green blue orange white".split():
doc.bgColor = color
time.sleep(0.2)
def TestExplorer(iexplore):
if not iexplore.Visible:
iexplore.Visible = -1
filename = os.path.join(os.path.dirname(__file__), "..\\readme.html")
iexplore.Navigate(win32api.GetFullPathName(filename))
win32api.Sleep(1000)
TestObjectFromWindow()
win32api.Sleep(3000)
try:
iexplore.Quit()
except (AttributeError, pythoncom.com_error):
# User got sick of waiting :)
pass
def TestAll():
try:
try:
try:
iexplore = win32com.client.dynamic.Dispatch(
"InternetExplorer.Application"
)
except pythoncom.com_error as exc:
if exc.hresult not in HRESULTS_IN_AUTOMATION:
raise
print("IE appears to not be available, so skipping this test")
return
TestExplorer(iexplore)
win32api.Sleep(1000)
iexplore = None
# Test IE events.
TestExplorerEvents()
# Give IE a chance to shutdown, else it can get upset on fast machines.
time.sleep(2)
# Note that the TextExplorerEvents will force makepy - hence
# this gencache is really no longer needed.
from win32com.client import gencache
gencache.EnsureModule("{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1)
iexplore = win32com.client.Dispatch("InternetExplorer.Application")
TestExplorer(iexplore)
except pythoncom.com_error as exc:
if exc.hresult != winerror.RPC_E_DISCONNECTED: # user closed the app!
raise
finally:
iexplore = None
if __name__ == "__main__":
TestAll()
CheckClean()

View file

@ -0,0 +1,145 @@
"""Testing pasing object between multiple COM threads
Uses standard COM marshalling to pass objects between threads. Even
though Python generally seems to work when you just pass COM objects
between threads, it shouldnt.
This shows the "correct" way to do it.
It shows that although we create new threads to use the Python.Interpreter,
COM marshalls back all calls to that object to the main Python thread,
which must be running a message loop (as this sample does).
When this test is run in "free threaded" mode (at this stage, you must
manually mark the COM objects as "ThreadingModel=Free", or run from a
service which has marked itself as free-threaded), then no marshalling
is done, and the Python.Interpreter object start doing the "expected" thing
- ie, it reports being on the same thread as its caller!
Python.exe needs a good way to mark itself as FreeThreaded - at the moment
this is a pain in the but!
"""
import _thread
import traceback
import pythoncom
import win32api
import win32com.client
import win32event
def TestInterp(interp):
if interp.Eval("1+1") != 2:
raise ValueError("The interpreter returned the wrong result.")
try:
interp.Eval(1 + 1)
raise ValueError("The interpreter did not raise an exception")
except pythoncom.com_error as details:
import winerror
if details[0] != winerror.DISP_E_TYPEMISMATCH:
raise ValueError(
"The interpreter exception was not winerror.DISP_E_TYPEMISMATCH."
)
def TestInterpInThread(stopEvent, cookie):
try:
DoTestInterpInThread(cookie)
finally:
win32event.SetEvent(stopEvent)
def CreateGIT():
return pythoncom.CoCreateInstance(
pythoncom.CLSID_StdGlobalInterfaceTable,
None,
pythoncom.CLSCTX_INPROC,
pythoncom.IID_IGlobalInterfaceTable,
)
def DoTestInterpInThread(cookie):
try:
pythoncom.CoInitialize()
myThread = win32api.GetCurrentThreadId()
GIT = CreateGIT()
interp = GIT.GetInterfaceFromGlobal(cookie, pythoncom.IID_IDispatch)
interp = win32com.client.Dispatch(interp)
TestInterp(interp)
interp.Exec("import win32api")
print(
"The test thread id is %d, Python.Interpreter's thread ID is %d"
% (myThread, interp.Eval("win32api.GetCurrentThreadId()"))
)
interp = None
pythoncom.CoUninitialize()
except:
traceback.print_exc()
def BeginThreadsSimpleMarshal(numThreads, cookie):
"""Creates multiple threads using simple (but slower) marshalling.
Single interpreter object, but a new stream is created per thread.
Returns the handles the threads will set when complete.
"""
ret = []
for i in range(numThreads):
hEvent = win32event.CreateEvent(None, 0, 0, None)
_thread.start_new(TestInterpInThread, (hEvent, cookie))
ret.append(hEvent)
return ret
def test(fn):
print("The main thread is %d" % (win32api.GetCurrentThreadId()))
GIT = CreateGIT()
interp = win32com.client.Dispatch("Python.Interpreter")
cookie = GIT.RegisterInterfaceInGlobal(interp._oleobj_, pythoncom.IID_IDispatch)
events = fn(4, cookie)
numFinished = 0
while 1:
try:
rc = win32event.MsgWaitForMultipleObjects(
events, 0, 2000, win32event.QS_ALLINPUT
)
if rc >= win32event.WAIT_OBJECT_0 and rc < win32event.WAIT_OBJECT_0 + len(
events
):
numFinished = numFinished + 1
if numFinished >= len(events):
break
elif rc == win32event.WAIT_OBJECT_0 + len(events): # a message
# This is critical - whole apartment model demo will hang.
pythoncom.PumpWaitingMessages()
else: # Timeout
print(
"Waiting for thread to stop with interfaces=%d, gateways=%d"
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
)
except KeyboardInterrupt:
break
GIT.RevokeInterfaceFromGlobal(cookie)
del interp
del GIT
if __name__ == "__main__":
test(BeginThreadsSimpleMarshal)
win32api.Sleep(500)
# Doing CoUninit here stop Pythoncom.dll hanging when DLLMain shuts-down the process
pythoncom.CoUninitialize()
if pythoncom._GetInterfaceCount() != 0 or pythoncom._GetGatewayCount() != 0:
print(
"Done with interfaces=%d, gateways=%d"
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
)
else:
print("Done.")

View file

@ -0,0 +1,149 @@
# The purpose of this test is to ensure that the gateways objects
# do the right thing WRT COM rules about object identity etc.
# Also includes a basic test that we support inheritance correctly in
# gateway interfaces.
# For our test, we create an object of type IID_IPersistStorage
# This interface derives from IPersist.
# Therefore, QI's for IID_IDispatch, IID_IUnknown, IID_IPersist and
# IID_IPersistStorage should all return the same gateway object.
#
# In addition, the interface should only need to declare itself as
# using the IPersistStorage interface, and as the gateway derives
# from IPersist, it should automatically be available without declaration.
#
# We also create an object of type IID_I??, and perform a QI for it.
# We then jump through a number of hoops, ensuring that the objects
# returned by the QIs follow all the rules.
#
# Here is Gregs summary of the rules:
# 1) the set of supported interfaces is static and unchanging
# 2) symmetric: if you QI an interface for that interface, it succeeds
# 3) reflexive: if you QI against A for B, the new pointer must succeed
# for a QI for A
# 4) transitive: if you QI for B, then QI that for C, then QI'ing A for C
# must succeed
#
#
# Note that 1) Requires cooperation of the Python programmer. The rule to keep is:
# "whenever you return an _object_ from _query_interface_(), you must return the
# same object each time for a given IID. Note that you must return the same
# _wrapped_ object
# you
# The rest are tested here.
import pythoncom
from win32com.server.util import wrap
from .util import CheckClean
numErrors = 0
# Check that the 2 objects both have identical COM pointers.
def CheckSameCOMObject(ob1, ob2):
addr1 = repr(ob1).split()[6][:-1]
addr2 = repr(ob2).split()[6][:-1]
return addr1 == addr2
# Check that the objects conform to COM identity rules.
def CheckObjectIdentity(ob1, ob2):
u1 = ob1.QueryInterface(pythoncom.IID_IUnknown)
u2 = ob2.QueryInterface(pythoncom.IID_IUnknown)
return CheckSameCOMObject(u1, u2)
def FailObjectIdentity(ob1, ob2, when):
if not CheckObjectIdentity(ob1, ob2):
global numErrors
numErrors = numErrors + 1
print(when, "are not identical (%s, %s)" % (repr(ob1), repr(ob2)))
class Dummy:
_public_methods_ = [] # We never attempt to make a call on this object.
_com_interfaces_ = [pythoncom.IID_IPersistStorage]
class Dummy2:
_public_methods_ = [] # We never attempt to make a call on this object.
_com_interfaces_ = [
pythoncom.IID_IPersistStorage,
pythoncom.IID_IExternalConnection,
]
class DeletgatedDummy:
_public_methods_ = []
class Dummy3:
_public_methods_ = [] # We never attempt to make a call on this object.
_com_interfaces_ = [pythoncom.IID_IPersistStorage]
def _query_interface_(self, iid):
if iid == pythoncom.IID_IExternalConnection:
# This will NEVER work - can only wrap the object once!
return wrap(DelegatedDummy())
def TestGatewayInheritance():
# By default, wrap() creates and discards a temporary object.
# This is not necessary, but just the current implementation of wrap.
# As the object is correctly discarded, it doesnt affect this test.
o = wrap(Dummy(), pythoncom.IID_IPersistStorage)
o2 = o.QueryInterface(pythoncom.IID_IUnknown)
FailObjectIdentity(o, o2, "IID_IPersistStorage->IID_IUnknown")
o3 = o2.QueryInterface(pythoncom.IID_IDispatch)
FailObjectIdentity(o2, o3, "IID_IUnknown->IID_IDispatch")
FailObjectIdentity(o, o3, "IID_IPersistStorage->IID_IDispatch")
o4 = o3.QueryInterface(pythoncom.IID_IPersistStorage)
FailObjectIdentity(o, o4, "IID_IPersistStorage->IID_IPersistStorage(2)")
FailObjectIdentity(o2, o4, "IID_IUnknown->IID_IPersistStorage(2)")
FailObjectIdentity(o3, o4, "IID_IDispatch->IID_IPersistStorage(2)")
o5 = o4.QueryInterface(pythoncom.IID_IPersist)
FailObjectIdentity(o, o5, "IID_IPersistStorage->IID_IPersist")
FailObjectIdentity(o2, o5, "IID_IUnknown->IID_IPersist")
FailObjectIdentity(o3, o5, "IID_IDispatch->IID_IPersist")
FailObjectIdentity(o4, o5, "IID_IPersistStorage(2)->IID_IPersist")
def TestMultiInterface():
o = wrap(Dummy2(), pythoncom.IID_IPersistStorage)
o2 = o.QueryInterface(pythoncom.IID_IExternalConnection)
FailObjectIdentity(o, o2, "IID_IPersistStorage->IID_IExternalConnection")
# Make the same QI again, to make sure it is stable.
o22 = o.QueryInterface(pythoncom.IID_IExternalConnection)
FailObjectIdentity(o, o22, "IID_IPersistStorage->IID_IExternalConnection")
FailObjectIdentity(
o2, o22, "IID_IPersistStorage->IID_IExternalConnection (stability)"
)
o3 = o2.QueryInterface(pythoncom.IID_IPersistStorage)
FailObjectIdentity(o2, o3, "IID_IExternalConnection->IID_IPersistStorage")
FailObjectIdentity(
o, o3, "IID_IPersistStorage->IID_IExternalConnection->IID_IPersistStorage"
)
def test():
TestGatewayInheritance()
TestMultiInterface()
if numErrors == 0:
print("Worked ok")
else:
print("There were", numErrors, "errors.")
if __name__ == "__main__":
test()
CheckClean()

View file

@ -0,0 +1,12 @@
set o = CreateObject("Python.Interpreter")
if o.Eval("1+1") <> 2 Then
WScript.Echo "Eval('1+1') failed"
bFailed = True
end if
if bFailed then
WScript.Echo "*********** VBScript tests failed *********"
else
WScript.Echo "VBScript test worked OK"
end if

View file

@ -0,0 +1,140 @@
# Some raw iter tests. Some "high-level" iterator tests can be found in
# testvb.py and testOutlook.py
import sys
import unittest
import pythoncom
import win32com.server.util
import win32com.test.util
from win32com.client import Dispatch
from win32com.client.gencache import EnsureDispatch
class _BaseTestCase(win32com.test.util.TestCase):
def test_enumvariant_vb(self):
ob, iter = self.iter_factory()
got = []
for v in iter:
got.append(v)
self.assertEqual(got, self.expected_data)
def test_yield(self):
ob, i = self.iter_factory()
got = []
for v in iter(i):
got.append(v)
self.assertEqual(got, self.expected_data)
def _do_test_nonenum(self, object):
try:
for i in object:
pass
self.fail("Could iterate over a non-iterable object")
except TypeError:
pass # this is expected.
self.assertRaises(TypeError, iter, object)
self.assertRaises(AttributeError, getattr, object, "next")
def test_nonenum_wrapper(self):
# Check our raw PyIDispatch
ob = self.object._oleobj_
try:
for i in ob:
pass
self.fail("Could iterate over a non-iterable object")
except TypeError:
pass # this is expected.
self.assertRaises(TypeError, iter, ob)
self.assertRaises(AttributeError, getattr, ob, "next")
# And our Dispatch wrapper
ob = self.object
try:
for i in ob:
pass
self.fail("Could iterate over a non-iterable object")
except TypeError:
pass # this is expected.
# Note that as our object may be dynamic, we *do* have a __getitem__
# method, meaning we *can* call iter() on the object. In this case
# actual iteration is what fails.
# So either the 'iter(); will raise a type error, or an attempt to
# fetch it
try:
next(iter(ob))
self.fail("Expected a TypeError fetching this iterator")
except TypeError:
pass
# And it should never have a 'next' method
self.assertRaises(AttributeError, getattr, ob, "next")
class VBTestCase(_BaseTestCase):
def setUp(self):
def factory():
# Our VB test harness exposes a property with IEnumVariant.
ob = self.object.EnumerableCollectionProperty
for i in self.expected_data:
ob.Add(i)
# Get the raw IEnumVARIANT.
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
iter = ob._oleobj_.InvokeTypes(
pythoncom.DISPID_NEWENUM, 0, invkind, (13, 10), ()
)
return ob, iter.QueryInterface(pythoncom.IID_IEnumVARIANT)
# We *need* generated dispatch semantics, so dynamic __getitem__ etc
# don't get in the way of our tests.
self.object = EnsureDispatch("PyCOMVBTest.Tester")
self.expected_data = [1, "Two", "3"]
self.iter_factory = factory
def tearDown(self):
self.object = None
# Test our client semantics, but using a wrapped Python list object.
# This has the effect of re-using our client specific tests, but in this
# case is exercising the server side.
class SomeObject:
_public_methods_ = ["GetCollection"]
def __init__(self, data):
self.data = data
def GetCollection(self):
return win32com.server.util.NewCollection(self.data)
class WrappedPythonCOMServerTestCase(_BaseTestCase):
def setUp(self):
def factory():
ob = self.object.GetCollection()
flags = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
enum = ob._oleobj_.Invoke(pythoncom.DISPID_NEWENUM, 0, flags, 1)
return ob, enum.QueryInterface(pythoncom.IID_IEnumVARIANT)
self.expected_data = [1, "Two", 3]
sv = win32com.server.util.wrap(SomeObject(self.expected_data))
self.object = Dispatch(sv)
self.iter_factory = factory
def tearDown(self):
self.object = None
def suite():
# We dont want our base class run
suite = unittest.TestSuite()
for item in list(globals().values()):
if (
type(item) == type(unittest.TestCase)
and issubclass(item, unittest.TestCase)
and item != _BaseTestCase
):
suite.addTest(unittest.makeSuite(item))
return suite
if __name__ == "__main__":
unittest.main(argv=sys.argv + ["suite"])

View file

@ -0,0 +1,209 @@
# Test MSOffice
#
# Main purpose of test is to ensure that Dynamic COM objects
# work as expected.
# Assumes Word and Excel installed on your machine.
import traceback
import pythoncom
import win32api
import win32com
import win32com.client.dynamic
from pywintypes import Unicode
from win32com.client import gencache
from win32com.test.util import CheckClean
error = "MSOffice test error"
# Test a few of the MSOffice components.
def TestWord():
# Try and load the object exposed by Word 8
# Office 97 - _totally_ different object model!
try:
# NOTE - using "client.Dispatch" would return an msword8.py instance!
print("Starting Word 8 for dynamic test")
word = win32com.client.dynamic.Dispatch("Word.Application")
TestWord8(word)
word = None
# Now we will test Dispatch without the new "lazy" capabilities
print("Starting Word 8 for non-lazy dynamic test")
dispatch = win32com.client.dynamic._GetGoodDispatch("Word.Application")
typeinfo = dispatch.GetTypeInfo()
attr = typeinfo.GetTypeAttr()
olerepr = win32com.client.build.DispatchItem(typeinfo, attr, None, 0)
word = win32com.client.dynamic.CDispatch(dispatch, olerepr)
dispatch = typeinfo = attr = olerepr = None
TestWord8(word)
except pythoncom.com_error:
print("Starting Word 7 for dynamic test")
word = win32com.client.Dispatch("Word.Basic")
TestWord7(word)
except Exception as e:
print("Word dynamic tests failed", e)
traceback.print_exc()
print("Starting MSWord for generated test")
try:
from win32com.client import gencache
word = gencache.EnsureDispatch("Word.Application.8")
TestWord8(word)
except Exception as e:
print("Word generated tests failed", e)
traceback.print_exc()
def TestWord7(word):
word.FileNew()
# If not shown, show the app.
if not word.AppShow():
word._proc_("AppShow")
for i in range(12):
word.FormatFont(Color=i + 1, Points=i + 12)
word.Insert("Hello from Python %d\n" % i)
word.FileClose(2)
def TestWord8(word):
word.Visible = 1
doc = word.Documents.Add()
wrange = doc.Range()
for i in range(10):
wrange.InsertAfter("Hello from Python %d\n" % i)
paras = doc.Paragraphs
for i in range(len(paras)):
# *sob* - in Word 2019, `p = paras(i+1)` seems to work to get a para
# but `p.Font` then blows up.
# p = paras[i]()
p = paras(i + 1)
p.Font.ColorIndex = i + 1
p.Font.Size = 12 + (4 * i)
# XXX - note that
# for para in paras:
# para().Font...
# doesnt seem to work - no error, just doesnt work
# Should check if it works for VB!
doc.Close(SaveChanges=0)
word.Quit()
win32api.Sleep(1000) # Wait for word to close, else we
# may get OA error.
def TestWord8OldStyle():
try:
import win32com.test.Generated4Test.msword8
except ImportError:
print("Can not do old style test")
def TextExcel(xl):
xl.Visible = 0
if xl.Visible:
raise error("Visible property is true.")
xl.Visible = 1
if not xl.Visible:
raise error("Visible property not true.")
if int(xl.Version[0]) >= 8:
xl.Workbooks.Add()
else:
xl.Workbooks().Add()
xl.Range("A1:C1").Value = (1, 2, 3)
xl.Range("A2:C2").Value = ("x", "y", "z")
xl.Range("A3:C3").Value = ("3", "2", "1")
for i in range(20):
xl.Cells(i + 1, i + 1).Value = "Hi %d" % i
if xl.Range("A1").Value != "Hi 0":
raise error("Single cell range failed")
if xl.Range("A1:B1").Value != ((Unicode("Hi 0"), 2),):
raise error("flat-horizontal cell range failed")
if xl.Range("A1:A2").Value != ((Unicode("Hi 0"),), (Unicode("x"),)):
raise error("flat-vertical cell range failed")
if xl.Range("A1:C3").Value != (
(Unicode("Hi 0"), 2, 3),
(Unicode("x"), Unicode("Hi 1"), Unicode("z")),
(3, 2, Unicode("Hi 2")),
):
raise error("square cell range failed")
xl.Range("A1:C3").Value = ((3, 2, 1), ("x", "y", "z"), (1, 2, 3))
if xl.Range("A1:C3").Value != (
(3, 2, 1),
(Unicode("x"), Unicode("y"), Unicode("z")),
(1, 2, 3),
):
raise error("Range was not what I set it to!")
# test dates out with Excel
xl.Cells(5, 1).Value = "Excel time"
xl.Cells(5, 2).Formula = "=Now()"
import time
xl.Cells(6, 1).Value = "Python time"
xl.Cells(6, 2).Value = pythoncom.MakeTime(time.time())
xl.Cells(6, 2).NumberFormat = "d/mm/yy h:mm"
xl.Columns("A:B").EntireColumn.AutoFit()
xl.Workbooks(1).Close(0)
xl.Quit()
def TestAll():
TestWord()
try:
print("Starting Excel for Dynamic test...")
xl = win32com.client.dynamic.Dispatch("Excel.Application")
TextExcel(xl)
except Exception as e:
worked = False
print("Excel tests failed", e)
traceback.print_exc()
try:
print("Starting Excel 8 for generated excel8.py test...")
mod = gencache.EnsureModule(
"{00020813-0000-0000-C000-000000000046}", 0, 1, 2, bForDemand=1
)
xl = win32com.client.Dispatch("Excel.Application")
TextExcel(xl)
except ImportError:
print("Could not import the generated Excel 97 wrapper")
except Exception as e:
print("Generated Excel tests failed", e)
traceback.print_exc()
try:
import xl5en32
mod = gencache.EnsureModule("{00020813-0000-0000-C000-000000000046}", 9, 1, 0)
xl = win32com.client.Dispatch("Excel.Application.5")
print("Starting Excel 95 for makepy test...")
TextExcel(xl)
except ImportError:
print("Could not import the generated Excel 95 wrapper")
except Exception as e:
print("Excel 95 tests failed", e)
traceback.print_exc()
if __name__ == "__main__":
TestAll()
CheckClean()
pythoncom.CoUninitialize()

View file

@ -0,0 +1,136 @@
# OfficeEvents - test/demonstrate events with Word and Excel.
import msvcrt
import sys
import threading
import time
import types
import pythoncom
from win32com.client import Dispatch, DispatchWithEvents
stopEvent = threading.Event()
def TestExcel():
class ExcelEvents:
def OnNewWorkbook(self, wb):
if type(wb) != types.InstanceType:
raise RuntimeError(
"The transformer doesnt appear to have translated this for us!"
)
self.seen_events["OnNewWorkbook"] = None
def OnWindowActivate(self, wb, wn):
if type(wb) != types.InstanceType or type(wn) != types.InstanceType:
raise RuntimeError(
"The transformer doesnt appear to have translated this for us!"
)
self.seen_events["OnWindowActivate"] = None
def OnWindowDeactivate(self, wb, wn):
self.seen_events["OnWindowDeactivate"] = None
def OnSheetDeactivate(self, sh):
self.seen_events["OnSheetDeactivate"] = None
def OnSheetBeforeDoubleClick(self, Sh, Target, Cancel):
if Target.Column % 2 == 0:
print("You can double-click there...")
else:
print("You can not double-click there...")
# This function is a void, so the result ends up in
# the only ByRef - Cancel.
return 1
class WorkbookEvents:
def OnActivate(self):
print("workbook OnActivate")
def OnBeforeRightClick(self, Target, Cancel):
print("It's a Worksheet Event")
e = DispatchWithEvents("Excel.Application", ExcelEvents)
e.seen_events = {}
e.Visible = 1
book = e.Workbooks.Add()
book = DispatchWithEvents(book, WorkbookEvents)
print("Have book", book)
# sheet = e.Worksheets(1)
# sheet = DispatchWithEvents(sheet, WorksheetEvents)
print("Double-click in a few of the Excel cells...")
print("Press any key when finished with Excel, or wait 10 seconds...")
if not _WaitForFinish(e, 10):
e.Quit()
if not _CheckSeenEvents(e, ["OnNewWorkbook", "OnWindowActivate"]):
sys.exit(1)
def TestWord():
class WordEvents:
def OnDocumentChange(self):
self.seen_events["OnDocumentChange"] = None
def OnWindowActivate(self, doc, wn):
self.seen_events["OnWindowActivate"] = None
def OnQuit(self):
self.seen_events["OnQuit"] = None
stopEvent.set()
w = DispatchWithEvents("Word.Application", WordEvents)
w.seen_events = {}
w.Visible = 1
w.Documents.Add()
print("Press any key when finished with Word, or wait 10 seconds...")
if not _WaitForFinish(w, 10):
w.Quit()
if not _CheckSeenEvents(w, ["OnDocumentChange", "OnWindowActivate"]):
sys.exit(1)
def _WaitForFinish(ob, timeout):
end = time.time() + timeout
while 1:
if msvcrt.kbhit():
msvcrt.getch()
break
pythoncom.PumpWaitingMessages()
stopEvent.wait(0.2)
if stopEvent.isSet():
stopEvent.clear()
break
try:
if not ob.Visible:
# Gone invisible - we need to pretend we timed
# out, so the app is quit.
return 0
except pythoncom.com_error:
# Excel is busy (eg, editing the cell) - ignore
pass
if time.time() > end:
return 0
return 1
def _CheckSeenEvents(o, events):
rc = 1
for e in events:
if e not in o.seen_events:
print("ERROR: Expected event did not trigger", e)
rc = 0
return rc
def test():
import sys
if "noword" not in sys.argv[1:]:
TestWord()
if "noexcel" not in sys.argv[1:]:
TestExcel()
print("Word and Excel event tests passed.")
if __name__ == "__main__":
test()

View file

@ -0,0 +1,160 @@
"""Testing pasing object between multiple COM threads
Uses standard COM marshalling to pass objects between threads. Even
though Python generally seems to work when you just pass COM objects
between threads, it shouldnt.
This shows the "correct" way to do it.
It shows that although we create new threads to use the Python.Interpreter,
COM marshalls back all calls to that object to the main Python thread,
which must be running a message loop (as this sample does).
When this test is run in "free threaded" mode (at this stage, you must
manually mark the COM objects as "ThreadingModel=Free", or run from a
service which has marked itself as free-threaded), then no marshalling
is done, and the Python.Interpreter object start doing the "expected" thing
- ie, it reports being on the same thread as its caller!
Python.exe needs a good way to mark itself as FreeThreaded - at the moment
this is a pain in the but!
"""
import threading
import unittest
import pythoncom
import win32api
import win32com.client
import win32event
from .testServers import InterpCase
freeThreaded = 1
class ThreadInterpCase(InterpCase):
def _testInterpInThread(self, stopEvent, interp):
try:
self._doTestInThread(interp)
finally:
win32event.SetEvent(stopEvent)
def _doTestInThread(self, interp):
pythoncom.CoInitialize()
myThread = win32api.GetCurrentThreadId()
if freeThreaded:
interp = pythoncom.CoGetInterfaceAndReleaseStream(
interp, pythoncom.IID_IDispatch
)
interp = win32com.client.Dispatch(interp)
interp.Exec("import win32api")
# print "The test thread id is %d, Python.Interpreter's thread ID is %d" % (myThread, interp.Eval("win32api.GetCurrentThreadId()"))
pythoncom.CoUninitialize()
def BeginThreadsSimpleMarshal(self, numThreads):
"""Creates multiple threads using simple (but slower) marshalling.
Single interpreter object, but a new stream is created per thread.
Returns the handles the threads will set when complete.
"""
interp = win32com.client.Dispatch("Python.Interpreter")
events = []
threads = []
for i in range(numThreads):
hEvent = win32event.CreateEvent(None, 0, 0, None)
events.append(hEvent)
interpStream = pythoncom.CoMarshalInterThreadInterfaceInStream(
pythoncom.IID_IDispatch, interp._oleobj_
)
t = threading.Thread(
target=self._testInterpInThread, args=(hEvent, interpStream)
)
t.setDaemon(1) # so errors dont cause shutdown hang
t.start()
threads.append(t)
interp = None
return threads, events
#
# NOTE - this doesnt quite work - Im not even sure it should, but Greg reckons
# you should be able to avoid the marshal per thread!
# I think that refers to CoMarshalInterface though...
def BeginThreadsFastMarshal(self, numThreads):
"""Creates multiple threads using fast (but complex) marshalling.
The marshal stream is created once, and each thread uses the same stream
Returns the handles the threads will set when complete.
"""
interp = win32com.client.Dispatch("Python.Interpreter")
if freeThreaded:
interp = pythoncom.CoMarshalInterThreadInterfaceInStream(
pythoncom.IID_IDispatch, interp._oleobj_
)
events = []
threads = []
for i in range(numThreads):
hEvent = win32event.CreateEvent(None, 0, 0, None)
t = threading.Thread(target=self._testInterpInThread, args=(hEvent, interp))
t.setDaemon(1) # so errors dont cause shutdown hang
t.start()
events.append(hEvent)
threads.append(t)
return threads, events
def _DoTestMarshal(self, fn, bCoWait=0):
# print "The main thread is %d" % (win32api.GetCurrentThreadId())
threads, events = fn(2)
numFinished = 0
while 1:
try:
if bCoWait:
rc = pythoncom.CoWaitForMultipleHandles(0, 2000, events)
else:
# Specifying "bWaitAll" here will wait for messages *and* all events
# (which is pretty useless)
rc = win32event.MsgWaitForMultipleObjects(
events, 0, 2000, win32event.QS_ALLINPUT
)
if (
rc >= win32event.WAIT_OBJECT_0
and rc < win32event.WAIT_OBJECT_0 + len(events)
):
numFinished = numFinished + 1
if numFinished >= len(events):
break
elif rc == win32event.WAIT_OBJECT_0 + len(events): # a message
# This is critical - whole apartment model demo will hang.
pythoncom.PumpWaitingMessages()
else: # Timeout
print(
"Waiting for thread to stop with interfaces=%d, gateways=%d"
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
)
except KeyboardInterrupt:
break
for t in threads:
t.join(2)
self.assertFalse(t.is_alive(), "thread failed to stop!?")
threads = None # threads hold references to args
# Seems to be a leak here I can't locate :(
# self.assertEqual(pythoncom._GetInterfaceCount(), 0)
# self.assertEqual(pythoncom._GetGatewayCount(), 0)
def testSimpleMarshal(self):
self._DoTestMarshal(self.BeginThreadsSimpleMarshal)
def testSimpleMarshalCoWait(self):
self._DoTestMarshal(self.BeginThreadsSimpleMarshal, 1)
# def testFastMarshal(self):
# self._DoTestMarshal(self.BeginThreadsFastMarshal)
if __name__ == "__main__":
unittest.main("testMarshal")

View file

@ -0,0 +1,27 @@
## AHH - I cant make this work!!!
# But this is the general idea.
import sys
import netscape
error = "Netscape Test Error"
if __name__ == "__main__":
n = netscape.CNetworkCX()
rc = n.Open("http://d|/temp/apyext.html", 0, None, 0, None)
if not rc:
raise error("Open method of Netscape failed")
while 1:
num, str = n.Read(None, 0)
print("Got ", num, str)
if num == 0:
break # used to be continue - no idea!!
if num == -1:
break
# sys.stdout.write(str)
n.Close()
print("Done!")
del n
sys.last_type = sys.last_value = sys.last_traceback = None

View file

@ -0,0 +1,231 @@
import os
import pythoncom
import pywintypes
import win32api
import win32com
import win32com.client
import win32com.client.dynamic
import win32com.server.util
import win32ui
from pywin32_testutil import str2bytes
from pywintypes import Unicode
from win32com import storagecon
from win32com.axcontrol import axcontrol
from win32com.test.util import CheckClean
S_OK = 0
import win32timezone
now = win32timezone.now()
class LockBytes:
_public_methods_ = [
"ReadAt",
"WriteAt",
"Flush",
"SetSize",
"LockRegion",
"UnlockRegion",
"Stat",
]
_com_interfaces_ = [pythoncom.IID_ILockBytes]
def __init__(self, data=""):
self.data = str2bytes(data)
self.ctime = now
self.mtime = now
self.atime = now
def ReadAt(self, offset, cb):
print("ReadAt")
result = self.data[offset : offset + cb]
return result
def WriteAt(self, offset, data):
print("WriteAt " + str(offset))
print("len " + str(len(data)))
print("data:")
# print data
if len(self.data) >= offset:
newdata = self.data[0:offset] + data
print(len(newdata))
if len(self.data) >= offset + len(data):
newdata = newdata + self.data[offset + len(data) :]
print(len(newdata))
self.data = newdata
return len(data)
def Flush(self, whatsthis=0):
print("Flush" + str(whatsthis))
fname = os.path.join(win32api.GetTempPath(), "persist.doc")
open(fname, "wb").write(self.data)
return S_OK
def SetSize(self, size):
print("Set Size" + str(size))
if size > len(self.data):
self.data = self.data + str2bytes("\000" * (size - len(self.data)))
else:
self.data = self.data[0:size]
return S_OK
def LockRegion(self, offset, size, locktype):
print("LockRegion")
def UnlockRegion(self, offset, size, locktype):
print("UnlockRegion")
def Stat(self, statflag):
print("returning Stat " + str(statflag))
return (
"PyMemBytes",
storagecon.STGTY_LOCKBYTES,
len(self.data),
self.mtime,
self.ctime,
self.atime,
storagecon.STGM_DIRECT | storagecon.STGM_READWRITE | storagecon.STGM_CREATE,
storagecon.STGM_SHARE_EXCLUSIVE,
"{00020905-0000-0000-C000-000000000046}",
0, # statebits ?
0,
)
class OleClientSite:
_public_methods_ = [
"SaveObject",
"GetMoniker",
"GetContainer",
"ShowObject",
"OnShowWindow",
"RequestNewObjectLayout",
]
_com_interfaces_ = [axcontrol.IID_IOleClientSite]
def __init__(self, data=""):
self.IPersistStorage = None
self.IStorage = None
def SetIPersistStorage(self, IPersistStorage):
self.IPersistStorage = IPersistStorage
def SetIStorage(self, IStorage):
self.IStorage = IStorage
def SaveObject(self):
print("SaveObject")
if self.IPersistStorage != None and self.IStorage != None:
self.IPersistStorage.Save(self.IStorage, 1)
self.IStorage.Commit(0)
return S_OK
def GetMoniker(self, dwAssign, dwWhichMoniker):
print("GetMoniker " + str(dwAssign) + " " + str(dwWhichMoniker))
def GetContainer(self):
print("GetContainer")
def ShowObject(self):
print("ShowObject")
def OnShowWindow(self, fShow):
print("ShowObject" + str(fShow))
def RequestNewObjectLayout(self):
print("RequestNewObjectLayout")
def test():
# create a LockBytes object and
# wrap it as a COM object
# import win32com.server.dispatcher
lbcom = win32com.server.util.wrap(
LockBytes(), pythoncom.IID_ILockBytes
) # , useDispatcher=win32com.server.dispatcher.DispatcherWin32trace)
# create a structured storage on the ILockBytes object
stcom = pythoncom.StgCreateDocfileOnILockBytes(
lbcom,
storagecon.STGM_DIRECT
| storagecon.STGM_CREATE
| storagecon.STGM_READWRITE
| storagecon.STGM_SHARE_EXCLUSIVE,
0,
)
# create our ClientSite
ocs = OleClientSite()
# wrap it as a COM object
ocscom = win32com.server.util.wrap(ocs, axcontrol.IID_IOleClientSite)
# create a Word OLE Document, connect it to our site and our storage
oocom = axcontrol.OleCreate(
"{00020906-0000-0000-C000-000000000046}",
axcontrol.IID_IOleObject,
0,
(0,),
ocscom,
stcom,
)
mf = win32ui.GetMainFrame()
hwnd = mf.GetSafeHwnd()
# Set the host and document name
# for unknown reason document name becomes hostname, and document name
# is not set, debugged it, but don't know where the problem is?
oocom.SetHostNames("OTPython", "This is Cool")
# activate the OLE document
oocom.DoVerb(-1, ocscom, 0, hwnd, mf.GetWindowRect())
# set the hostnames again
oocom.SetHostNames("OTPython2", "ThisisCool2")
# get IDispatch of Word
doc = win32com.client.Dispatch(oocom.QueryInterface(pythoncom.IID_IDispatch))
# get IPersistStorage of Word
dpcom = oocom.QueryInterface(pythoncom.IID_IPersistStorage)
# let our ClientSite know the interfaces
ocs.SetIPersistStorage(dpcom)
ocs.SetIStorage(stcom)
# use IDispatch to do the Office Word test
# pasted from TestOffice.py
wrange = doc.Range()
for i in range(10):
wrange.InsertAfter("Hello from Python %d\n" % i)
paras = doc.Paragraphs
for i in range(len(paras)):
paras[i]().Font.ColorIndex = i + 1
paras[i]().Font.Size = 12 + (4 * i)
# XXX - note that
# for para in paras:
# para().Font...
# doesnt seem to work - no error, just doesnt work
# Should check if it works for VB!
dpcom.Save(stcom, 0)
dpcom.HandsOffStorage()
# oocom.Close(axcontrol.OLECLOSE_NOSAVE) # or OLECLOSE_SAVEIFDIRTY, but it fails???
# Save the ILockBytes data to "persist2.doc"
lbcom.Flush()
# exiting Winword will automatically update the ILockBytes data
# and flush it to "%TEMP%\persist.doc"
doc.Application.Quit()
if __name__ == "__main__":
test()
pythoncom.CoUninitialize()
CheckClean()

View file

@ -0,0 +1,82 @@
import sys
import unittest
import pythoncom
from win32com.client import Dispatch
from win32com.client.gencache import EnsureDispatch
class PippoTester(unittest.TestCase):
def setUp(self):
from win32com.test import pippo_server
from win32com.test.util import RegisterPythonServer
RegisterPythonServer(pippo_server.__file__, "Python.Test.Pippo")
# create it.
self.object = Dispatch("Python.Test.Pippo")
def testLeaks(self):
try:
gtrc = sys.gettotalrefcount
except AttributeError:
print("Please run this with python_d for leak tests")
gtrc = lambda: 0
# note creating self.object() should have consumed our "one time" leaks
self.object.Method1()
start = gtrc()
for i in range(1000):
object = Dispatch("Python.Test.Pippo")
object.Method1()
object = None
end = gtrc()
if end - start > 5:
self.fail("We lost %d references!" % (end - start,))
def testResults(self):
rc, out1 = self.object.Method2(123, 111)
self.assertEqual(rc, 123)
self.assertEqual(out1, 222)
def testPythonArrays(self):
self._testArray([-3, -2, -1, 0, 1, 2, 3])
self._testArray([-3.14, -2, -0.1, 0.0, 1.1, 2.5, 3])
def testNumpyArrays(self):
try:
import numpy
except:
print("Numpy test not possible because numpy module failed to import")
return
self._testArray(numpy.array([-3, -2, -1, 0, 1, 2, 3]))
self._testArray(numpy.array([-3.14, -2, -0.1, 0.0, 1.1, 2.5, 3]))
def testByteArrays(self):
if "bytes" in dir(__builtins__):
# Use eval to avoid compilation error in Python 2.
self._testArray(eval("b'abcdef'"))
self._testArray(eval("bytearray(b'abcdef')"))
def _testArray(self, inArray):
outArray = self.object.Method3(inArray)
self.assertEqual(list(outArray), list(inArray))
def testLeaksGencache(self):
try:
gtrc = sys.gettotalrefcount
except AttributeError:
print("Please run this with python_d for leak tests")
gtrc = lambda: 0
# note creating self.object() should have consumed our "one time" leaks
object = EnsureDispatch("Python.Test.Pippo")
start = gtrc()
for i in range(1000):
object = EnsureDispatch("Python.Test.Pippo")
object.Method1()
object = None
end = gtrc()
if end - start > 10:
self.fail("We lost %d references!" % (end - start,))
if __name__ == "__main__":
unittest.main()

Some files were not shown because too many files have changed in this diff Show more