196 lines
13 KiB
HTML
196 lines
13 KiB
HTML
<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 don<6F>t 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<72>]</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>>>> import pythoncom<BR>
|
||
>>> 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>
|
||
	# ni only for 1.4!<BR>
|
||
	import ni, win32com.server.register <BR>
|
||
	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<61>) </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>
|