6

I am experimenting with comtypes and ArcObjects under Python 2.6.5 and ArcGIS 10 SP1. I'm using the pure Python method to wrap the ArcObjects OLBs described in this answer, but getting an error in the comtypes.CoCreateInstance method.

Here is the code I am running:

def WrapModules():
    #force wrapping of all ArcObjects libraries (OLBs)
    import os
    import comtypes.client
    # change com_dir to whatever it is for you
    com_dir = r'C:\Program Files\ArcGIS\Desktop10.0\com'
    coms = [os.path.join(com_dir, x) for x in os.listdir(com_dir) if os.path.splitext(x)[1].upper() == '.OLB']
    map(comtypes.client.GetModule, coms)

def GetApp():
    """Get a hook into the current session of ArcMap"""
    from comtypes.gen import esriFramework
    pAppROT = NewObj(esriFramework.AppROT, esriFramework.IAppROT)
    if pAppROT is not None:
        iCount = pAppROT.Count
        if iCount == 0:
            print 'No ArcGIS application currently running.  Terminating ...'
            return None
        for i in range(iCount):
            pApp = pAppROT.Item(i)  #returns IApplication on AppRef
            if pApp.Name == 'ArcMap':
                print "ArcMap found"
                return pApp
        print 'No ArcMap session is running at this time.'
    print "No AppROT found"
    return None

def NewObj(MyClass, MyInterface):
    """Creates a new comtypes POINTER object where\n\
    MyClass is the class to be instantiated,\n\
    MyInterface is the interface to be assigned"""
    from comtypes.client import CreateObject
    import traceback
    try:
        ptr = CreateObject(MyClass, interface=MyInterface)
        return ptr
    except:
        print traceback.format_exc()
        return None

if __name__ == "__main__":
    WrapModules()
    pApp = GetApp()
    if pApp is not None:
        print "HWND: %d" % pApp.hWnd
    else:
        print "No ArcGIS application found!"

And here is the output from the script:

Traceback (most recent call last):
  File "C:\temp\ComHelpers.py", line 35, in NewObj
    ptr = CreateObject(MyClass, interface=MyInterface)
  File "C:\Python26\ArcGIS10.0\lib\site-packages\comtypes\client\__init__.py", line 235, in CreateObject
    obj = comtypes.CoCreateInstance(clsid, clsctx=clsctx, interface=interface)
  File "C:\Python26\ArcGIS10.0\lib\site-packages\comtypes\__init__.py", line 1145, in CoCreateInstance
    _ole32.CoCreateInstance(byref(clsid), punkouter, clsctx, byref(iid), byref(p))
  File "_ctypes/callproc.c", line 925, in GetResult
WindowsError: [Error -2147221231] ClassFactory cannot supply requested class

No AppROT found
No ArcGIS application found!

3 Answers 3

9

This is partly in response to the discussion in the comments regarding wrapping a single module only if necessary, and also just to share my version of the helper module. This combines the importing and the wrapping of the type libraries into one step, e.g.:

esriFramework = GetESRIModule("esriFramework")

This will check to see if the esriFramework module (wrapper) already exists, wrap it if not, and import it as the variable named esriFramework.

Here is the code for my helper module. You can import it as long as it is in your sys.path. You can also run it standalone to test that it appropriately finds the ArcMap application and prints its window handle.

def GetESRIModule(module):
    """Returns the named ESRI module from comtypes.gen,
    creating the wrapper for it if necessary"""
    import sys
    mod = 'comtypes.gen.%s' % module
    try:
        __import__(mod, globals(), locals(), [module], -1)
    except ImportError:
        WrapModule(module)
        __import__(mod, globals(), locals(), [module], -1)
    return sys.modules[mod]

def GetLibPath():
    """Returns the location of the directory containing the ArcGIS object
    library (.olb) files"""
    import _winreg, os.path
    with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,r"SOFTWARE\ESRI") as esrikey:
        # Lets first look for ArcGIS 10.0+ installations, e.g. HKLM\SOFTWARE\ESRI\Desktop10.0\InstallDir
        try:
            i = 0
            while True:
                subkeyname = _winreg.EnumKey(esrikey, i)
                if subkeyname.startswith("Desktop") or subkeyname.startswith("Engine"):
                    # Take the first one we find
                    with _winreg.OpenKey(esrikey, subkeyname) as subkey:
                        return os.path.join(_winreg.QueryValueEx(subkey, "InstallDir")[0], "com")
                i += 1
        except WindowsError:
            pass
        # Try the pre-10.0 key, e.g. HKLM\SOFTWARE\ESRI\ArcGIS\InstallDir
        try:
            with _winreg.OpenKey(esrikey, "ArcGIS") as subkey:
                return os.path.join(_winreg.QueryValueEx(subkey, "InstallDir")[0], "com")
        except WindowsError:
            pass
    raise WindowsError("ArcGIS InstallDir key not found!")

def WrapModules():
    """Force wrapping of all ArcObjects libraries (OLBs)"""
    import os.path
    import comtypes.client
    from glob import iglob
    comdir = GetLibPath()
    for olb in iglob(os.path.join(comdir, "*.olb")):
        comtypes.client.GetModule(olb)

def WrapModule(module):
    """Wrap a single ArcObjects library (OLB)"""
    import os
    import comtypes.client
    comdir = GetLibPath()
    olb = os.path.join(comdir, module + ".olb")
    comtypes.client.GetModule(olb)

def GetCurrentApp():
    """Returns the Application if the script is being run from
    within the application boundary of an ArcGIS application"""
    esriFramework = GetESRIModule("esriFramework")
    return NewObj(esriFramework.AppRef, esriFramework.IApplication)

def GetApp(app="ArcMap"):
    """Returns the Application if the script is being run from
    outside the application boundary of an ArcGIS application.
    "app" must be 'ArcMap' (default) or 'ArcCatalog'"""
    if not app in ("ArcMap", "ArcCatalog"):
        raise ValueError("app must be 'ArcMap' or 'ArcCatalog'")
    esriFramework = GetESRIModule("esriFramework")
    pAppROT = NewObj(esriFramework.AppROT, esriFramework.IAppROT)
    if pAppROT:
        pApp = None
        appcount = pAppROT.Count
        if appcount > 0:
            for i in range(appcount):
                pApp = pAppROT.Item(i)
                if pApp.Name == app:
                    break
        return pApp
    else:
        raise RuntimeError("AppROT object could not be created!")

def NewObj(MyClass, MyInterface):
    """Creates a new comtypes POINTER object where
    MyClass is the class to be instantiated,
    MyInterface is the interface to be assigned"""
    from comtypes.client import CreateObject
    try:
        ptr = CreateObject(MyClass, interface=MyInterface)
        return ptr
    except:
        return None

def CType(obj, interface):
    """Casts obj to interface and returns comtypes POINTER or None"""
    try:
        newobj = obj.QueryInterface(interface)
        return newobj
    except:
        return None

def CLSID(MyClass):
    """Return CLSID of MyClass as string"""
    return str(MyClass._reg_clsid_)

if __name__ == "__main__":
    import arcpy # Required to initialize ArcObjects (for ArcGIS 10)
    ##import arcgisscripting # Required to initialize ArcObjects (for either ArcGIS 10 or 9)
    pApp = GetCurrentApp() # try to get IApplication from AppRef first, in case we are in-process
    if not pApp:
        print "AppRef not available (script is not running from within an ArcGIS application), attempting AppROT..."
        pApp = GetApp() # defaults to look for ArcMap
    if pApp:
        print "%s HWND: %x" % (pApp.Name, pApp.hWnd)
    else:
        print "No ArcGIS application found!"
5
  • Very slick! If running out of process, though, you will need to use IObjectFactory to create subsequent objects. Also in this case, you may have a problem with screen refresh or other drawing operations. You should read the blog post at palladiumconsulting.com/blog/sebastian/2008/02/…. It's quite something. IObjectFactory cleared up the object problems for me, but the screen refresh still isn't working. Commented Jan 13, 2011 at 17:07
  • Thanks, I did see that discussion as well, that will be a challenge indeed. I did see that comtypes has a PumpEvents function but I have not spent much time looking into it. starship.python.net/crew/theller/comtypes
    – blah238
    Commented Jan 13, 2011 at 17:34
  • Also, have you tried running your script in process to see if the refreshing works?
    – blah238
    Commented Jan 13, 2011 at 17:38
  • 1
    See my answer to another question: gis.stackexchange.com/questions/5039/…
    – Petr Krebs
    Commented Jan 14, 2011 at 0:26
  • No, actually, I haven't run the script in process. There are reasons why I wouldn't be running it in process in its intended use environment, but I really should just try it. I suspect the refresh would work just fine. Commented Jan 14, 2011 at 4:22
5

Import arcpy first, you aren't doing any license checkout or setting up the ArcObjects 10.0 runtime as-is so it won't find the CoClass.

6
  • If full ArcObjects access is what you are after, then arcpy won't do it for you; arcpy does not expose most of the ArcObjects model. Do remember, though, that at 10.0 Python is aware of arcpy, which operates inside the application boundary (... I think). Anyway, you can use some of its really convenient functions, like cursor iterations, in your Python scripts. It sure beats the pure ArcObjects version! Commented Jan 11, 2011 at 5:13
  • 1
    I understand that, but the act of importing arcpy will set up AoInitialize and the other stuff you need to get ArcObjects up and running in process. It's a shortcut to get comtypes working. Commented Jan 11, 2011 at 8:05
  • You are correct. That is what you have to do to run in process, and that might do the trick. I haven't done this myself and haven't had problems, but I will do it from now on. Commented Jan 11, 2011 at 14:36
  • Thanks Jason, importing arcpy in my "main" statement was the fix for me. I am unsure as to what exactly it is doing though, as I am running the script from PyScripter or the command line, not as a script tool within the application. Is that what is meant by "in process" or is there another meaning?
    – blah238
    Commented Jan 11, 2011 at 17:14
  • 1
    In that currently executing python.exe process. Each running program that consumes ArcObjects needs to bootstrap by checking out a license and doing some other initialization. Commented Jan 11, 2011 at 17:30
1

Well, I don't see much that is different from what I posted in the answer you refer to. You put the wrapping procedure in a function that you then call from the main loop. I run the wrapping first thing at the top level of the main module. You might try moving the wrapping code to the top level. It's not obvious why putting the wrapping in a function would cause a problem, but Python can be rather convoluted when it comes to scoping of namespaces. That said, I doubt this is the problem. Your GetApp() and NewObj() functions are essentially identical to those I posted, and they work fine for me. So, my guess is that at least one of the generated .pyc modules in the comtypes/gen directory is bad, and my suspicion is the esriFramework module

Go to the C:\Python26\ArcGIS10.0\Lib\site-packages\comtypes\gen directory, find the esriFramework.py file, and open it in a text editor. In the first line will be a long string following the from-import statement, probably starting with an underscore. This is the filename of the byte-compiled (.pyc) module that actually holds all the wrapped code. Look for this file in the same directory. Open it in a text editor and see if it is empty or has very little content. I have seen these .pyc modules be corrupted like this before. You can write a bunch of fussy code like I did at one time to go through all the pyc wrapper files in the directory looking for corrupt ones and then rewrap only those OLBs; however, for all that effort it really doesn't save much processing time and isn't really worth it. So, I would recommend just deleting everything in the comtypes/gen directory and rerunning your script. I've had to do that a couple of times. The py and pyc files are all rebuilt when you run the script. If this doesn't work, then I would try uninstalling and reinstalling comtypes. I know one other person who was having object non-recognition problems and reinstalled comtypes. Everything was fine thereafter.

Oh, one other thing I thought of. Not at all meaning to be insulting, I would caution that you make sure you have an ArcMap session running before running the script. Obvious, I know, but I will admit to being stumped by this more than once.

That's what I have to offer at this time.

7
  • Thanks for the detailed suggestions. The structure of the script did not seem to play a role, and this structure lets me use it as a helper module for use in other scripts. Fortunately I didn't have to mess with the comtypes\gen files! Another question for you: Would wrapping only the OLBs actually needed by your script significantly reduce that initial wrapping time? That might be a handy function to have in a helper module like this. Alternatively, can the wrappings be made permanent in the filesystem so you do it once and then never have to again?
    – blah238
    Commented Jan 11, 2011 at 17:21
  • Never mind that last part, looks like the wrappings are permanent by default (in the comtypes\gen files), and by calling my WrapModules function I am forcibly recreating them even if they already exist. I think you did mention this in your original answer I linked in my question.
    – blah238
    Commented Jan 11, 2011 at 18:05
  • >> Would wrapping only the OLBs actually needed by your script significantly reduce that initial wrapping time? Commented Jan 12, 2011 at 3:29
  • >> Would wrapping only the OLBs actually needed by your script significantly reduce that initial wrapping time? Yes, but, in my case at least, initial wrapping is only about 20 secs. tops. I am running production scripts that make feature-based graphics for feature classes having 10,000+ features. The processing time is already huge, so an initial 20 secs (or even a little longer) hasn't seemed a problem to me. Commented Jan 12, 2011 at 3:39
  • 1
    A thought I had (but have never implemented) is that you could create a text file that contains a flag that gets set after OLBs are initially wrapped. A wrapping function could check that flag to see if it should wrap modules again. Might work. (ini files anyone?) Commented Jan 12, 2011 at 3:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.