[omniORB] Re: A Stab at Python COM-to-CORBA Binding Generation

W. Eliot Kimber eliot@isogen.com
Thu, 04 Jan 2001 09:21:41 -0600


"W. Eliot Kimber" wrote:

> Here is the code. Enjoy.

We've refined the code to the point that I think it is pretty complete.
We've tested it by writing a little VB GUI against our CORBA objects.

We've fixed a nasty ommission in the first version I posted, added
support
for "narrow", and figured out how to autogenerate all classes, including
the "top level" class that makes the initial CORBA connection.

Below are two packages, "gencombind.py" and "com2corba.py".
gencombind.py is the omniidl back end, com2corba.py is a set of static
classes and methods used by the generated classes. The code assumes 
the default mapping of IDL module/interface to python packages used
by OmniIDL.

Typical usage of the generated classes is (here using VB code):

    dim myServer as Object
    Set myServer = CreateObject("MyProject.MyServer")
    myServer.initializeCorbaReference ("corbaname::localhost#MyServer")
    If Err.Number <> 0 Then
        MsgBox "Creation of my server object failed: " & Err.Description
        Return
    End If
    dim obj2 as Object
    set obj2 = myServer.getSomeOtherObject()
    print obj2.getName()  
' Prints the objects name. This would be from the IDL-defined methods.

Each corba class has a built-in ClassName method and exposes the
narrow() method, e.g.:

    dim obj3 as Object
    set obj3 = obj2.narrow("myproject.Module1.Interface2")
    if not obj3 is Nothing:
       print obj3.ClassName() ' should be "Interface2"

It would easy to expose other CORBA-specific properties, such as the
fully-qualified interface name, but I haven't done that yet just because
I haven't found a need for it yet.

It would also be easy to generate the inverse of this binding: Python
that exposes COM objects through CORBA using OmniORB. That could be
really useful for quickly corbatizing legacy COM systems at almost zero
cost.

gencombind.py:

--- cut here ---
#-------------------------------------------------------------
#
# COM-to-IDL Binding Generator
#
# Copyright (c) 2000, 2001 DataChannel, Inc.
#
# This software may be used for any purpose without restriction as long
# as the above copyright notice is maintained.
#
# $Id$
#---------------------------------------------------------------

"""
OmniORB back end for generating Python COM server classes that
implement the IDL and delegate to the corresponding Python
CORBA classes.

To use this back end, use the omniidl command (provided with the OmniORB
package).
Go to the directory that contains the IDL you want to process and issue
this command:

omniidl -I. -bbonnellcorba.gencombind Bonnell.idl > outputfile.py

After generating the COM binding file (outputfile.py), you must run that
file
to register the COM classes:

python outputfile.py

"""

from omniidl import idlast, idlvisitor, idlutil, idltype
import string
import pythoncom

progidMain = "myProduct" # FIXME: get this from an argument
declaredClasses = []    # List of classes declared, used to generate COM
registrations
narrowLookups = {}      # Dictionary for mapping IDL defined classname
			# to (stub class, wrap class) 

class ComVisitor (idlvisitor.AstVisitor):

    def visitAST(self, node):
        for n in node.declarations():
            n.accept(self)

    def visitModule(self, node):
        print "import %s" % string.join(node.scopedName(), ".")
        for n in node.definitions():
            n.accept(self)

    def visitInterface(self, node):
        name = node.identifier()
        fqName = string.join(node.scopedName(), ".")
        narrowLookups[fqName] = name
        global declaredClasses
        declaredClasses.append(name)
        guid = pythoncom.CreateGuid()
        desc = name # FIXME: generate a better description.
        progid = "%s.%s" % (progidMain, name)
        print """
class %s(CorbaWrapper):

    _reg_clsid_ = "%s"
    _reg_desc_ = "%s"
    _reg_progid_ = "%s"
    _public_attrs_ = []
    _readonly_attrs_ = []
    scopedName = "%s"
    corbaStubClass = %s
""" % (name, guid, desc, progid, fqName, fqName)

        # Now generate the list of public methods
        spacingVar = " " * 22
        publicMethodList = \
"'narrow',\n%s'ClassName',\n%s'initializeCorbaReference',\n%s" % \
                           (spacingVar, spacingVar, spacingVar)
        callables = node.callables()
        for callable in callables:
            if callable.__class__ == idlast.Operation:
                publicMethodList = "%s'%s',\n%s" % (publicMethodList,
                                                  callable.identifier(),
                                                  spacingVar)

        print "    _public_methods_=[%s]" % publicMethodList

        print """

    def lookupWrapClassForStubClass(self, stubClassName):
        return narrowLookups[stubClassName]
"""
        for callable in callables:
            if callable.__class__ == idlast.Operation:
                generateMethodForOperation(callable)
            

def generateMethodForOperation(operation):
    """
    Given an operation, generates the corresponding COM-specific
    Python method.
    """

    methodName = operation.identifier()
    comParms = "self"
    parms = operation.parameters()
    corbaParms = ""
    if len(parms) > 0:
        for param in parms:
            if param.is_in():
                comParms = "%s, %s" % (comParms, param.identifier())
                corbaParms = "%s%s," % (corbaParms,
					comparm2corbaparm(param))
        corbaParms = corbaParms[:-1]  # Slice off trailing ","
    
    print "\n    def %s(%s):" % (methodName, comParms)

    returnType = operation.returnType()
    returnKind = returnType.kind()
    if returnKind == idltype.tk_alias:
        returnType = operation.returnType().unalias()
        returnKind = returnType.kind()

    # print "### returnKind=%s" % returnKind
    if returnKind == idltype.tk_void:
        print "        self.corbaObject.%s(%s)" % (methodName,
						   corbaParms)
    elif returnKind == idltype.tk_objref:
        print "        return wrap(%s(self.corbaObject.%s(%s)))" % \
              (returnType.name(), methodName, corbaParms)
    elif returnKind == idltype.tk_sequence:
        seqType = returnType.seqType()
        print "        objs = self.corbaObject.%s(%s)" % (methodName,
							  corbaParms)
        print "        comObjs = []"
        if seqType.kind() == idltype.tk_objref:
            print "        for obj in objs:"
            print "            comObjs.append(wrap(%s(obj)))" % \
				seqType.name()
        elif seqType.kind() == idltype.tk_string:
            print "        for string in objs:"
            print "            comObjs.append(str(string))"
        else:
            print "        comObjs = objs"
        print "        return wrap(Collection(data = comObjs))"

    else:
        print "        return self.corbaObject.%s(%s)" % (methodName,
							  corbaParms)

def comparm2corbaparm(corbaParm):
    """
    Given a corba parameter, returns the appropriate Python parameter.
    """
    result = ""
    type = corbaParm.paramType()
    if type.kind() == idltype.tk_string:
        result = "str(%s)" % corbaParm.identifier()
    elif type.kind() == idltype.tk_objref:
        result = "unwrap(%s).corbaObject" % corbaParm.identifier()
    else:
        result = corbaParm.identifier()
    return result
        
def run(tree, args):
    visitor = ComVisitor()
    print "#----------------------------------------------------------"
    print "# Generated by gencombind.py "
    print "#----------------------------------------------------------"

    print """
import sys

from win32com.server.util import wrap, Collection  
# NOTE: we override unwrap below
from win32com.server.exception import COMException
from win32com.server import util

from com2corba import *
import traceback
import CosNaming
from omniORB import CORBA
        
"""
    tree.accept(visitor)

    print "\nnarrowLookups = {"
    for (key, wrapClass) in narrowLookups.items():
        print "                 '%s': %s," % (key, wrapClass)
    print "}"

    print "\nif __name__ == '__main__':"
    print "    import win32com.server.register"
    for name in declaredClasses:
        print "    win32com.server.register.UseCommandLine(%s)" % name

#---- end of package

com2corba.py:

--- cut here ---
#----------------------------------------------------------------
# # com2corba support methods and classes (package)
#
# Copyright (c) 2000, 2001 DataChannel, Inc.
#
# This software may be used for any purpose without restriction as long
as the above
# copyright notice is maintained.
#
# $Id: com2corba.py,v 1.1 2001/01/03 20:15:45 eliot Exp $
##
#----------------------------------------------------------------
from win32com.server.util import wrap, Collection  # NOTE: we override
unwrap below
from win32com.server.exception import COMException
from win32com.server import util

from omniORB import CORBA
import sys, string

class InitializeCorbaReferenceError(COMException):
    pass

class CorbaWrapper:

    def __init__(self, corbaObject = None):
        self.corbaObject = corbaObject

    def initializeCorbaReference(self, corbaloc):
        """
        Given a corba location, gets a CORBA object reference to it if
        it doesn't already have one.
        """
        corbaloc = str(corbaloc)

        if not self.corbaObject:
            orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID)
            try:
                self.corbaObject = orb.string_to_object(corbaloc)
            except Exception, exc:
                raise InitializeCorbaReferenceError("Failed to get
CORBA"
				"object reference for location %s: %s" %\
                                                    (corbaloc, exc))
            self.corbaObject =
self.corbaObject._narrow(self.corbaStubClass)
            
    def ClassName(self):
        return self.__class__.__name__

    def narrow(self, stubClassName):
        """
        Implements the CORBA narrow method.

        stubClassName is the class name generated in the Python
        stubs from the IDL, i.e., 'module.module.interface'
        """

        try:
            wrapClass =
self.lookupWrapClassForStubClass(str(stubClassName))
            cObj = self.corbaObject._narrow(wrapClass.corbaStubClass)
            if cObj:
                return wrap(wrapClass(cObj)) 
            else:
                return None
        except KeyError:
            return None

    def lookupWrapClassForStubClass(self, stubClassName):
        """
        Given the name of a python stub class (e.g., full-qualified name
from IDL),
        return the corresponding COM wrapping class.

        Raises KeyError if stubClassName not found.
        """
        
        raise Exception("This is an abstract method. "
			"Must be implemented by subclasses")

def unwrap(comObj):
    "Handle Nothing objects passed in from COM"
    
    if comObj == None:
        return NullCorbaObj()
    else:
        pyObj = util.unwrap(comObj)
        return pyObj

class NullCorbaObj:
    "Provides a None object that has a corbaObject property"
    
    def __init__(self):
        self.corbaObject = None
#---- end of package

-- 
. . . . . . . . . . . . . . . . . . . . . . . .

W. Eliot Kimber | Lead Brain

1016 La Posada Dr. | Suite 240 | Austin TX  78752
    T 512.656.4139 |  F 512.419.1860 | eliot@isogen.com

w w w . d a t a c h a n n e l . c o m