2
votes

I have two projects in a solution - say, ProjectA and ProjectB. Both projects are registered for COM interop, and ProjectA references ProjectB - when ProjectA builds, it copies ProjectB.dll into its output folder.

Now, the COM client needs to access ProjectB.tlb. I tried using a post-build event to xcopy the type library from ProjectB's output folder into ProjectA's; at a glance, this works - the COM client can "see" the types in the .tlb.

The problem is that the whenever the COM client tries to call a method in the .tlb, an automation error ensues.

My gut-feeling was that the .tlb needed to be created/registered in the location it's being used from, so I unchecked the "register for COM interop" box in ProjectB, removed the xcopy from the post-build events, and added a regasm.exe /tlb call in ProjectA's post-build events, so as to create and register ProjectB.tlb in the output folder of ProjectA... and then I cleaned and rebuilt the solution.

Same thing: the COM client sees the types, but throws an automation error when it tries to actually consume the exposed API.

The two projects were originally one and the same. We decided to split them so that when we ship an update of ProjectA, we could do so without causing compatibility issues with COM clients referencing the code in ProjectB. The idea seemed very good, but ...is it even possible? If so, how?

Or is folding ProjectB back into ProjectA my only hope to get the COM client to work with the COM API exposed by ProjectB?

2
Mat, is this for Excel? Excel automation behaves differently to .NET... if this is Excel based let me know I think I know why you get the automtion error. - user2140173

2 Answers

3
votes

The TLB location doesn't matter; it's just metadata for the compiler's benefit. It's not used at runtime. However, .NET must be able to locate the assembly DLL at runtime, and that can be subtle.

.NET will try to locate the DLLs by using the usual Fusion probing rules, which are based on the location of the client process EXE, not the location of either DLL, or either TLB for that matter. That basically gives you three choices:

  • You can copy both ProjectA.DLL and ProjectB.DLL to the same folder as the EXE of the client.

I don't do this, because I find it's very... Un-COM-like. It surprises people. Also, very often the client lives in an inconvenient location (e.g. IIS host process).

That leaves you with two other options:

  • You can put both DLLs in the GAC. (Note that this is a separate step from running regasm.com to register the assembly for COM interop.)

  • You can add the /Codebase parameter of regasm.exe when registering the assembly in COM, which specifies that .NET should remember where the DLL being registered is, and look for it at runtime in the same location.

Remember that you have to run regasm.exe with both assemblies regardless, to register them for COM interop on the computer that will be running them.

Removing the "Register for COM interop" option simply means that the only classes that will be visible for COM interop are those that explicitly have a [ComVisible] attribute. When the option is checked, every class that is COM-compatible will be visible.

3
votes

but throws an automation error

Error reporting in Automation clients is generally abysmal. It is "this is not my problem, call somebody else" error reporting. A big reason why Java ate Microsoft's lunch. Pretty important that you tackle this first by diagnosing the underlying cause and that needs to start with your .NET projects. Which in this particular case are very likely to cause this error.

Select ProjectA as your startup project and select Project > Properties > Debug tab. Select "Start external program" and put the name of the automation client host or test program in the "Command line arguments" box. Use Debug > Exceptions and tick the Thrown checkbox for CLR exceptions. Press F5 to start the client, the debugger steps in when an exception is thrown and shows you what went wrong.

ProjectA references ProjectB - when ProjectA builds, it copies ProjectB.dll

This is not good and is guaranteed to cause this problem. It will only copy ProjectB if A does in fact have a dependency on B. In other words, you failed in your quest to actually make COM servers that can operate independently. You must remove the reference, you'll now get a compile error that shows how this dependency crept in.

This dependency will trip a FileNotFoundException at runtime, the CLR won't know how to find ProjectB.dll. It is not in the probing path. The fact that it can find ProjectA.dll does not help, it got that hint from the registry.

If you can't break the dependency on B but your quest is to make B independent but not A then use one of the following techniques:

  • Use ILMerge to merge ProjectB into ProjectA.dll so there is only a single file
  • Install ProjectB in the GAC so it can always be found. In general the proper approach for COM servers, a good solution for DLL Hell, albeit that you don't like it on your own machine
  • Copy ProjectB.dll into the same directory as the automation client's EXE. Now it is in the probing path and can always be found. The proper workaround for avoiding the GAC on your dev machine
  • Have ProjectA implement the AppDomain.CurrentDomain.AssemblyResolve event. You need a good trigger in A to subscribe the event, the constructor of a class that's guaranteed to be created by the client.