20

Now, I'm aware that if the library is in .NET, it's a little pointless to access it via COM, however, I am a bit perplexed because if I were to ask someone to write a library and expose it via COM, that person should be free to do so with any language.

It shouldn't matter to me which language that COM library is written in, so why does it matter?

For reference, this is what happens when you use tlbimp on a .tlb file generated from a .NET library:

C:\dev>tlbimp test.tlb
Microsoft (R) .NET Framework Type Library to Assembly Converter 4.0.30319.1
Copyright (C) Microsoft Corporation.  All rights reserved.

TlbImp : error TI1029 : Type library 'test' was exported from
a CLR assembly and cannot be re-imported as a CLR assembly.

Additionally, my test COM library uses IUnknown, supporting only early-bound COM interop.

Arafangion
  • 11,517
  • 1
  • 40
  • 72
  • pst: The tlbimp.exe refuses to let me import the types. – Arafangion Feb 21 '13 at 23:46
  • pst: How else would you use a .NET COM library via COM in .NET? – Arafangion Feb 21 '13 at 23:56
  • tlbimp.exe is just a tool to automatically extract/build bindings. It's not required and the entire process can be done by hand, just as for any other COM interface consumed in .NET. –  Feb 21 '13 at 23:59
  • pst: So, in other words, it should be understood as a "stupid limitation Microsoft has imposed just to be inconvenient"? Surely they have a reason? – Arafangion Feb 22 '13 at 00:00
  • 1
    How about going tlbexp (on assembly) -> tlb -> tlbimp /tlb (to new assembly)? Otherwise, there is the "icky" by hand (from tlb or original assembly types) that ought to work. –  Feb 22 '13 at 00:19
  • 1
    The 'tlbexp -> tlb' bit is fine, however, the '/tlb' flag is not documented unless you mean '/tlbreference', which only specifies how to resolve references. If you provide a .tlb file (as shown), however, then it generates a new assembly. (That's the normal way to do it, afaik). – Arafangion Feb 22 '13 at 00:37
  • I posted a link to a blog on MSDN; you will not find anything that directly references this problem that is more official than that. – j__m Mar 20 '13 at 23:05
  • 1
    Although I've seen it said that it is "pointless" more than once, I personally feel like it is _not_ pointless to want to access a COM-exposed .NET class via COM in .NET. If you want to expose your COM's functionality only once (as COM was intended)-- but you have _both_ VB6 and .NET clients-- you are pretty much forced to register to the GAC for .NET clients... which isn't really a recommended practice in general. See my question, [here](http://stackoverflow.com/q/20410199/864414). – transistor1 Dec 06 '13 at 20:18

2 Answers2

21

tlbimp is only the beginning of the problem.

Every COM-visible .NET object implements IManagedObject. Every time you try to call an object through a COM interface, .NET does a QueryInterface for IManagedObject, and if it succeeds the object is unwrapped and accessed directly instead. Eliminating the interop call in this way is considered a performance optimization.

This is a serious issue in COM-based interop scenarios where multiple components may be implemented in .NET languages. If each .NET component implements its own tlbimp-generated version of the COM interface, they will be unable to communicate with each other using that interface. Even though each tlbimp'ed copy of the interface has the same COM GUID, .NET considers them separate interfaces. The "correct" solution to this problem is to define the COM interface in a primary interop assembly, and have every .NET-implemented component use that same copy of the interface, but if there's no coordination between component developers that's pretty unlikely to happen.

This problem was once highlighted by a Microsoft developer at http://blogs.msdn.com/b/jmstall/archive/2009/07/09/icustomqueryinterface-and-clr-v4.aspx along with a solution for .NET 4, but it was based on a pre-release version and doesn't work in the final. I'm not aware of anywhere else it's been acknowledged by Microsoft.

One solution is to forego the interface in the .NET-to-.NET scenario and use reflection instead. This may work in many cases but of course isn't very performant. Possibly the best solution is to use unmanaged code to aggregate each of your managed components and reject attempts to QueryInterface for IManagedObject. This is similar to what's described in the above blog entry, but doesn't rely on .NET features that no longer work as described in that blog.

The .NET interop behavior is, IMO, pretty terrible, and clearly violates COM rules (same IID == same interface, and shouldn't have to care what language the object is implemented in). But .NET has behaved this way from the beginning and no amount of feedback from developers bitten by this behavior has changed the minds of anyone on the .NET team.

j__m
  • 9,392
  • 1
  • 32
  • 56
7

Tlbimp can see from the type library that it was produced from a .NET assembly. It just balks at what you are trying to do. Which doesn't make sense, if you want to use a .NET assembly then just add a reference to it.

You can fool the machine by using late-bound COM, most easily done with the dynamic keyword in C#. That still doesn't fool the CLR, it can see that it interops with a managed assembly and won't actually create the COM-callable wrapper. You most typically do this to write a test program to test your COM server. The implication is that your test doesn't actually test the way your COM server is going to be used in practice. Very basic things like converting variants to objects and back just won't be tested at all. And may well give you very unpleasant surprises when you use the assembly in production.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • But Late-Bound COM only works with IDispatch, IUnknown requires early-bound COM; Indeed, it is for system testing that I wish to use the COM interfaces. – Arafangion Feb 22 '13 at 02:25
  • Well, you get IDispatch for free so there's little point in avoiding it. But clearly my remarks about testing strongly apply here. – Hans Passant Feb 22 '13 at 02:30
  • The client application will use IUnknown, so avoiding IDispatch means I force my tests to use the IUnknown interface. – Arafangion Feb 22 '13 at 04:26
  • +1 for the good information on how .NET handles managed COM libraries, but I disagree that it doesn't make sense to _want_ to access it via COM. I personally saw some "unpleasant behavior", when trying to get around this problem using late-bound COM. On my dev machine, late-bound calling worked as expected; but when I deployed this to another machine, I got an exception on `Marshal.FinalReleaseComObject()`. It appears that .NET was "seeing" my COM object as a System.__ComObject on my dev machine, and as another type of object on my deployed machine. I didn't go further than that & ditched it. – transistor1 Dec 06 '13 at 20:32