16
votes

I have a 3rd party component, lets say FIPreviewHandler to handle preview, which implements IPreviewHandler. FIPreviewHandler is implemented as a Managed Component, and uses the IPreviewHandler interface and related interfaces through means of an interop. FIPreviewHandler is registered using regasm.exe as COM.

I have a client application which is also Managed. I want to create an instance of FIPreviewHandler as a COM component in my application.

I have an interop assembly that defines IPreviewHandler and related interfaces.

When I create an instance of FIPreviewHandler, using Activator.CreateInstance(), on a type returned by GetTypeByCLSID(), which uses the correct CLSID for FIPreviewHandler, it returns me a managed instance, as it has the actual assembly available, and skips COM. When I try to QI/cast this instance as any of the interfaces, IPreviewHandler for example, it returns null because, it is loaded as a managed object, and although the IPreviewHandler interface implemented by FIPreviewHandler is the same interface as I have in my interop, but its in a difference namespace/assembly, hence null. If it were to return me a COM instance/RCW (System.__ComObject), it would not take namespace into account, and would cast fine, and return a valid instance.

FIPreviewHandler is a 32 bit component, and on a 64bit Win7 machine, if I compile my client application as "Any CPU", Activator.CreateInstance() returns a COM instance/RCW (System.__ComObject), as it cudnt find a 64bit implementation of FIPreviewHandler, hence returns a proxy. In this scenario, my application works fine. But when I compile it for x86, it gets the 32bit implementation, and returns a managed instance of the actual managed class, and not a COM instance, hence fails.

I cannot use the interfaces defined in FIPreviewHandler's assembly, as I have to write a generic client for IPreviewHandler, and my application will work with any component implementing IPreviewHandler, which would work great for C++ based clients accessing FIPreviewHandler as a COM object, but is failing for Managed clients.

I hope I make sense and I would be really grateful for any help.

7

7 Answers

1
votes

So clearly this a fail on .NET's part as I'm finding there is no way to use a COM wrapper around a managed COM object.

The "solution" (and I use that term very loosely) is the use of a PIA or 'Primary Interop Assembly'. The PIA provides a single strong-named assembly imported with TlbImp.exe that is GAC registered. Basically I guess we then must rely on GAC publisher policies to force clients to the correct interface assembly.

see also

http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/b11a0f90-fcc5-487a-b057-632f5415bfc2

http://www.codeproject.com/KB/COM/BuildCOMServersInDotNet.aspx

1
votes

Man, if I were you, I would just make the wrapper myself, only when the type is NOT a COM type. To know if the type created is a COM type, use the IsCOMObject from the Type of the object:

myObject.GetType().IsCOMObject

If this is FALSE create a wrapper, that uses reflection to call the managed type. Reflection is slow, but you can cache the MethodInfo objects that you get... otherwise you can generate IL code, and create a wrapper that will have no reflection at all.

Of course there are other methods, of doing it... this is up to you.

As I am in love with dynamic run-time IL generation, I can provide you with a code that does this... if you are interested!

1
votes

My experience is that Microsoft did it this way by design. They don't want your two managed code assemblies to communicate via COM. If you try to add an assembly which supports COM as a COM reference in your project, the error says as much.

If the COM interfaces are the only way you can get at the functionality you need, a non-managed wrapper should do the trick. Write a new COM server in C++ or VB6 (or any non-managed language which is COM friendly) which wraps you third party managed COM server. Then add this new wrapper DLL to your managed code project as a COM server.

1
votes

I've tried to do a similar thing with no success. (In my case the existing defined COM interface had a bug that meant it couldn't be implemented in .Net, so I re-defined the COM interface in a separate namespace myself and implemented this interface instead. The resulting object implemented the correct COM interfaces, however I couldn't get a hold of a COM wrapper to cast back to original broken interface).

As far as I am aware there are only 2 solutions:

  1. Declare all COM interfaces in a Primary Interop Assembly, (either using TLBimp or by hand) so that all interfaces are defined in a common namespace. Note that this assembly normally doesn't contain any implementation, and so shouldn't need to reference other assemblies (unless those other assemblies are also interop assemblies that declare dependent COM interfaces).

  2. Create a wrapper (for example in Managed C++) that performs the calls through "traditional" COM, rather than through the .Net COM interop.

Option 1. definitely strikes me as your best option - note that there is no need to register this assembly or place it in the GAC, and the implementation can be in an entirely separate assembly as long as the common interop assembly is referenced.

I can't think of many legitimate situations where a Primary Interop Assembly is not feasible.

0
votes

It sounds like there are two problems:

  1. Access to the Interfaces
  2. Bitness of the component.

First, why reference it as a COM object in the first place if you know it is managed? Why not make a direct reference to the DLL instead of through interop and in that way, you should have access to the interfaces in the same way the the library has access to them. Thus, you wouldn't need to call Activator.CreateInstance. Instead, you would called var foo = new FIPreviewHandler();. However, that does not solve the bitness problem so the point is moot. Instead...

Coming to the second problem, one solution is to put the COM component into a COM+ Server Application. COM+ applications determine their bitness when they are first loaded. If all libraries in the COM+ application are 32-bit, when it is loaded, it will load into a WOW32 and you can call it from a 64-bit application. I've used this trick for COM libraries that only existed in 32-bit form but I needed the use them on a 64-bit server. The downside is that you are loading the library into a separate process and incurring the marshalling costs due to execution being out of process to your application but once instantiated, it should perform well enough.

0
votes

Use PInvoke to call the COM function CoCreateInstance(Ex) and pass it the CLSID defined in your interop assembly and the IID of IPreviewHandler. This way .NET gets no opportunity to interfere.

You say you have an interop assembly. It's best if this is a PIA distributed by the author of the third-party assembly, but it's fine if you have to build it yourself. (PIAs do not have to be registered in the GAC, although they often are.) You can get the CLSID by loading the interop assembly in the IL disassembler and looking up the System.Runtime.InteropServices.GuidAttribute attribute on the class.

0
votes

If I'm reading this right, you need to force it to use COM. Two ideas:

  1. Does the behavior of Activator.CreateInstance() change, if you get the type via GetTypeFromProgID() instead of GetTypeByCLSID()?

  2. How does Microsoft.VisualBasic.Interaction.CreateObject() behave? Yes, I hate to bring VB into this, but I am curious if it returns the COM object instead of the .NET class.