5

This is a kind of complex question, hitting on somewhat arcane areas relating to COM, the CLR, and reg-free COM.

First, my main application is written in python. As such, it's codebase (in development) is at, say, C:\mainapp\main.py.

Of course, on windows, it is C:\Python27\python.exe that executes the program.

I now want to use reg-free COM from python (using win32com) to talk (using IDispatch) to a COM object written in C# that I control, that is located at C:\mainapp\ManagedCOMObject.dll


Note: If you don't speak python / pythoncom, note that the call to Dispatch() eventually boils down to CoCreateInstance().


Attempt 1

#main.py:

import win32com.client
CLSID_ManagedComObject_MyClass = "{zzzzz....}" #This is correct
myclass = win32com.client.Dispatch(CLSID_ManagedComObject_MyClass)

Result

Failure, because the object is not in the registry (as expected), and I didn't mention anything about manifest files, and python.exe's manifest obviously doesn't know about my object.


Attempt 2

#main.py:

ac = ActivationContext("C:\mainapp\myapp.manifest", asm_dir="C:\mainapp")
with ac.activate():
    #The above two lines fill in a ACTCTX structure, call CreateActCtx, and call ActivateActCtx 
    import win32com.client
    CLSID_ManagedComObject_MyClass = "{zzzzz....}" #This is correct
    myclass = win32com.client.Dispatch(CLSID_ManagedComObject_MyClass)

-

#myapp.manifest:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity type="win32" 
                name="My.Main.App" 
                version="1.0.0.0" 
                processorArchitecture="x86" 
    />
    <dependency>
        <dependentAssembly>
            <assemblyIdentity
                        name="ManagedComObject" 
                        version="1.0.0.0" 
                        processorArchitecture="msil" 
            />
        </dependentAssembly>
    </dependency>
</assembly>

Note that ManagedCOMObject has an embedded manifest, which declares the COM object using the clrClass tag.

Result

The ActicateActCtx call succeeds and correctly parses the manifests of myapp.manifest and ManagedComObject.dll - I verified this using sxstrace.exe.

The call to CoCreateInstance fails with FileNotFound, after probing for C:\Python27\ManagedComObject.dll. The fusion log claims that PrivatePath is not set (presumable because python.exe.config doesn't exist), and simply does not look for the C# object in C:\mainapp.

Questions

  1. Why is this failing? I believe it is because the CLR COM loader stub is unable to import my C# assembly. If it failed before this step, the CLR would not even load, so the very fact that it is probing and creating fusion logs leads me to belive it is because the CLR can't find ManagedCOMObject.dll.

  2. Note that the CLR is loaded - I believe this means that COM has successfully looked at the current activation context to find the registration. I don't know precisely what clrClass does in the manifest, but presumably it got the CLR loaded successfully.

  3. I assume now that the issue is the CLR is not paying attention to the ActCtx when loading assemblies. If I was writing managed code, I could hook into the AppDomain.CurrentDomain.AssemblyResolve event and find the DLL myself. Since instead i'm writing unmanaged code, and hosting the CLR only implicitly, can I somehow change my applications PrivatePath and/or how assemblies are probed?

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
EB.
  • 3,383
  • 5
  • 31
  • 43
  • Nicely written question. Clearly describes the problem, shows all prior research, has a good searchable title. – Robert Harvey Nov 27 '12 at 19:01
  • Thanks. This took a few days of learning way more than I ever wanted about COM, the CLR, ACTCTX structures, assemblies, and manifest files. Especially manifest files o_O. – EB. Nov 27 '12 at 19:07

1 Answers1

3

I assume now that the issue is the CLR is not paying attention to the ActCtx when loading assemblies

Yes, that's correct. The CLR has its own strategy to locate assemblies that isn't otherwise affected by the Windows activation context. By default it only looks in the GAC and then in the private bin path of the EXE. You can see this at work with the Fuslogvw.exe utility.

You don't have many great options here. Installing the assembly in the GAC is the obvious solution and in general appropriate for [ComVisible] assemblies since it helps solve the COM DLL Hell problem. Only other thing you can do is copy it to C:\Python27 directory or write a python.exe.config file in that same directory that changes the probing path to a subdirectory of C:\Python27. Neither scales well. Hosting the CLR yourself or writing an AppDomain.AssemblyResolve event handler are off the table.

Using the GAC is the appropriate solution if you want to avoid Regasm.exe /codebase

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • You mention creating a python.exe.config file - I'm guessing I can't point the CLR to a different one before loading it? You say that hosting the CLR myself is off the table - is that because it is more complicated than it is worth, or are there further reasons? I also saw another interesting suggestion - after activating the ACTCTX, I could theoretically LoadLibrary() my DLL directly into my process. DLLMain would then invoke the CLR, and (perhaps) already have the assembly it was loaded from cached. Do you think that is possible? – EB. Nov 27 '12 at 19:51
  • No, the default CLR host always uses the name of the EXE. Maybe you can custom-host the CLR, it is a COM interface, no idea how far Python gets you. No, DllMain() can't invoke the CLR. That's the infamous LoaderLock problem. The GAC is the 20 seconds solution. – Hans Passant Nov 27 '12 at 19:58
  • Thanks for your help. I could theoretically host the CLR in python, as I can get dirty with ctypes and win32com if need be. However, I think i'm just going to write a 'proxy' C# exe to live next to my program and use stdin / stdout to communicate with the original dll. Thanks for your help. – EB. Nov 28 '12 at 15:45
  • It beats dealing with registering anything anywhere. If I had more time i'd just host the CLR. Personally I think the CLR not looking at the ACTCTX app path is behavior that should be changed, but oh well. Alternately, setting an environment variable / other local means for the CLR application home would be nice too. – EB. Nov 28 '12 at 21:15