简体   繁体   中英

Get COM interface without QueryInterface

I'm working with a native C++ dll from C#. On the C++ side there's a class that only implements one interface and IUnknown but its QueryInterface returns E_NOINTERFACE for anything other than IUnknown.

On the C# side I'm calling a method in the dll that returns an instance of this class, but even if I declare the parameter as ISomeInterface .NET still calls QueryInterface which results in an exception. Eg:

[ComImport, Guid(" ... ")]
public interface ISomeInterface { ... }

[DllImport("mylib.dll")]
public static extern void GetThing([MarshalAs(UnmanagedType.Interface)] out ISomeInterface thing);

calling GetThing throws InvalidCastException with message "This operation failed because the QueryInterface call on the COM component for the interface with IID '{ ... }' failed due to the following error: No such interface supported (Exception from HRESULT: 0x800040002 (E_NOINTERFACE))."

If I change the type to object

public static extern void GetThing([MarshalAs(UnmanagedType.Interface)] out object thing);

..then calling GetThing succeeds but trying to cast the resulting System.__ComObject to ISomeInterface throws the same exception.

I tried every different InterfaceType attribute on the interface definition but always get the same exception.

I also tried defining the interface as an abstract class:

[ComImport, Guid(" ... "), ClassInterface(ClassInterfaceType.None)]
public abstract class ISomeInterface { ... }

Calling GetThing then succeeds and I get an instance of ISomeInterface but as soon as I call any method it throws a BadImageFormatException with message "Bad IL format."

And I tried every different ClassInterfaceType attribute with the same results.

Is there any way this class can be used from C# without modifying the C++ code to fix its QueryInterface implementation?

With this approach you mentioned:

public static extern void GetThing([MarshalAs(UnmanagedType.Interface)] out object thing);

...assuming you are using .NET 4+ try the following:

object thing;
GetThing (out thing); // prefix me with the class defining the `DllImport`
dynamic d = thing;
d.DoSomething();  //  CHANGE DoSomething() to a known method that ISomeInterface exposes

The behavior of dynamic with COM is that no typelibrary is required ahead of time. You will not see statement completion/Intellisense in Visual Studio as you type your code for the methods on the dynamic object but the code will compile . It will be evaluated at runtime. Hopefully that will bypass QueryInterface() and will make use of the default(ISomeInterface ) that you indicated your COM class is decorated with.

OP:

On the C++ side ISomeInterface is the default interface

OP:

...but I'm still interested in any way to get it working without having to distribute a new/unofficial build of this dll

If this works then no change to the COM DLL is required

According to the rules of COM, an object by definition implements an interface only if calling QueryInterface on any interface pointer to that object with the UUID of the sought interface returns a corresponding new (AddRef'd) interface pointer (by which of course I mean returning S_OK and providing the interface pointer via the second argument). 1

... there's a class that only implements one interface and IUnknown but its QueryInterface returns E_NOINTERFACE for anything other than IUnknown.

No, that's incorrect. That object does not implement such interface. If the object is "pretending" that it implements that interface, but does not follow the rules of COM, then that object has a major bug and is broken. Any functionality you happen to get from such situation is essentially undefined behavior .

Specifically, it is illegal for a method to return an interface pointer of type ISomething if calling QueryInterface on that pointer with ISomething returns E_NOINTERFACE (See corollary on the footnote). You have to fix the C++ implementation of that object.


1 There a couple of subtle points:

First, curiously the object doesn't have to decide whether it implements a given interface until someone asks for it (although most objects of course have a hardcoded list of interfaces).

Second, once the object has made its mind and announced the decision about a given interface (by either returning a new interface pointer, or E_NOINTERFACE), that particular object can never change its mind for the rest of its lifetime and must always provide the same answer. As a corollary, calling QueryInterface from a given interface pointer, asking for the same interface, must succeed . This is where your object is broken.

Third, I intentionally said " any " interface pointer: for example, if an object implements five interfaces beyond IUnknown, when asked about a 6th interface, it must provide the same answer when asked from any pointer to any of the five other interfaces, as well as from an IUnknown pointer. That is, all implemented interfaces must be reachable from all other interfaces.

Finally, notice that I've been talking about objects , not CoClasses . CoClasses, default interfaces, registry entries, type libraries, et al , those are all (somewhat optional) mechanisms that exist for the benefit of the standard "COM factory" infrastructure, and to assist the developers' tooling. Once you get an interface pointer to an object from that infrastructure, all there is is interface pointers and the underlying objects (which means that the phrase "is the default interface" is irrelevant, and does not absolve the object from following the rules of COM interfaces)

i recommend Don Box's "Essential COM", where you can find all this spelled out in great detail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM