1

The QueryInterface() log of an instrumented IDispatch-based COM object accessed in C# via a dynamic variable showed (among others) the unknown IID {B86A98CC-DCC0-3205-8777-7911A07DAAAF}. Google, GitHub and microsoft.com turned up zilch. Does anyone know what this interface could be and, if so, what one might use it for?

FWIW, the object was instantiated like this:

var type = Type.GetTypeFromProgID(THE_PROGID);
var obj = (dynamic)Activator.CreateInstance(type);

Then the interface query log was dumped right away. In LINQPad I got this:

099AC468 {00000000-0000-0000-C000-000000000046} IUnknown
-------- {C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4} IManagedObject
099AC420 {B196B283-BAB4-101A-B69C-00AA00341D07} IProvideClassInfo
-------- {AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90} IInspectable
-------- {ECC8691B-C1DB-4DC0-855E-65F6C551AF49} INoMarshal
-------- {94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90} IAgileObject
-------- {00000003-0000-0000-C000-000000000046} IMarshal
-------- {00000144-0000-0000-C000-000000000046} IRpcOptions
099AC42C {00020400-0000-0000-C000-000000000046} IDispatch
-------- {A6EF9860-C720-11D0-9337-00A0C90DCAA9} IDispatchEx
099AC42C {00020400-0000-0000-C000-000000000046} IDispatch
099AC42C {00020400-0000-0000-C000-000000000046} IDispatch
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
-------- {00000038-0000-0000-C000-000000000046} IWeakReferenceSource
099AC42C {00020400-0000-0000-C000-000000000046} IDispatch

The same code in a C# unit test case (VS2015, Framework 4.5.2) gave a similar result:

0A41BFF0 {00000000-0000-0000-C000-000000000046} IUnknown
-------- {C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4} IManagedObject
0A41BFA8 {B196B283-BAB4-101A-B69C-00AA00341D07} IProvideClassInfo
-------- {AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90} IInspectable
-------- {ECC8691B-C1DB-4DC0-855E-65F6C551AF49} INoMarshal
-------- {94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90} IAgileObject
-------- {00000003-0000-0000-C000-000000000046} IMarshal
-------- {00000144-0000-0000-C000-000000000046} IRpcOptions
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
0A41BFB4 {00020400-0000-0000-C000-000000000046} IDispatch
-------- {A6EF9860-C720-11D0-9337-00A0C90DCAA9} IDispatchEx
0A41BFB4 {00020400-0000-0000-C000-000000000046} IDispatch

There is no appreciable difference between 32-bit and 64-bit modes (apart from the width of the displayed pointers, of course).

The object itself is derived from Delphi's TAutoObject, which explains how it could respond to the IProvideClassInfo query. However, the initial interface query sequence is basically the same for other objects that do not support this interface, regardless of whether they respond 0 or 1 to IDispatch::GetTypeInfoCount().

However, every member access resulted not only in a query for IDispatch but also in two queries for the unknown interface:

099A5068 {00020400-0000-0000-C000-000000000046} IDispatch
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
099A5068 {00020400-0000-0000-C000-000000000046} IDispatch
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
-------- {B86A98CC-DCC0-3205-8777-7911A07DAAAF}
... and so on ad infinitum ...

This was snipped from a long series of lines repeating like this; it is impossible to tell whether the unknown queries precede the corresponding IDispatch call, bracket it or come after it.

Precisation in response to Charlieface's comment: the members are accessed in a long series of printf statements like this:

Console.WriteLine("ApartmentType {0}", obj.ApartmentType);

The double queries for the unknown interface go away completely if I cast the result of each member access to its true datatype before passing it to Console.WriteLine():

Console.WriteLine("ApartmentType {0}", (string)obj.ApartmentType);

The unknown query seems to be a CLR thing, though, not a LINQPad thing, since it shows up in VS2015 as well.

Does anyone know, which interface {B86A98CC-DCC0-3205-8777-7911A07DAAAF} stands for, and what one might use it for?

DarthGizka
  • 4,347
  • 1
  • 24
  • 36
  • 1
    Are you attempting a cast anywhere? – Charlieface Mar 06 '21 at 20:31
  • @Charlieface: the results of member access are passed to `Console.WriteLine()`; who knows what kinds of casts the CLR formatting logic is attempting ... Good catch, though! Casting all member access results to their true data types (all scalars, like `string`, `int` and `DateTime`) before passing them to `Console.WriteLine()` made the double queries for the unknown interface connected to member access go away completely. The plot thickens ... – DarthGizka Mar 06 '21 at 20:49
  • Might be `IFormattable` see https://referencesource.microsoft.com/mscorlib/R/aedbc5e502007cf7.html – Charlieface Mar 06 '21 at 20:53
  • Please [edit] and post your code for `Console.WriteLine` – Charlieface Mar 06 '21 at 21:06
  • I tried casting to `IFormattable` (which is `{9a604ee7-e630-3ded-9444-baae247075ab}`) but the IID never showed up in COM. Makes sense, though, since `IFormattable` is a .NET interface and deeply rooted in the .NET world, not part of the alien COM world which .NET sees only through the layers of CCW and RCW code (COM-callable wrappers and runtime-callable wrappers, respectively). – DarthGizka Mar 06 '21 at 21:06

1 Answers1

3

This happens because you're using the dynamic keyword which will trigger all sorts of calls from .NET. The corresponding code is this (found in .NET reference source):

/// <summary>
/// Creates a meta-object for the specified object. 
/// </summary>
/// <param name="value">The object to get a meta-object for.</param>
/// <param name="expression">The expression representing this <see cref="DynamicMetaObject"/> during the dynamic binding process.</param>
/// <returns>
/// If the given object implements <see cref="IDynamicMetaObjectProvider"/> and is not a remote object from outside the current AppDomain,
/// returns the object's specific meta-object returned by <see cref="IDynamicMetaObjectProvider.GetMetaObject"/>. Otherwise a plain new meta-object 
/// with no restrictions is created and returned.
/// </returns>
public static DynamicMetaObject Create(object value, Expression expression) {
    ContractUtils.RequiresNotNull(expression, "expression");

    IDynamicMetaObjectProvider ido = value as IDynamicMetaObjectProvider;
    if (ido != null && !RemotingServices.IsObjectOutOfAppDomain(value)) {
        var idoMetaObject = ido.GetMetaObject(expression);

        if (idoMetaObject == null ||
            !idoMetaObject.HasValue ||
            idoMetaObject.Value == null ||
            (object)idoMetaObject.Expression != (object)expression) {
            throw Error.InvalidMetaObjectCreated(ido.GetType());
        }

        return idoMetaObject;
    } else {
        return new DynamicMetaObject(expression, BindingRestrictions.Empty, value);
    }
}

{B86A98CC-DCC0-3205-8777-7911A07DAAAF} is the (auto generated) IID of IDynamicMetaObjectProvider, i.e: typeof(IDynamicMetaObjectProvider).GUID. It's a pure managed interface so it's not useful from native code.

To find that, just look at the call stack, but make sure your thread apartement type is compatible with your object otherwise you'll get RPC remoting "useless" stack.

In my case I had to add the STAThread attribute on the .NET method:

enter image description here

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Thank you, your answer is amazingly helpful! Identifying the interface has not only opened up new information sources but also exciting new avenues of research. Note: I'm using `IDispatch` and `dynamic` because the structures in question are generated at runtime by reading type information from an XSD that the user specifies. Users of dynamic languages like Visual Basic or FoxPro/VFP can get debugger support and code completion (member picking) by referencing a type library generated on the fly, but C# users have so far been left out in the cold. `IDynamicMetaObject` may allow partial remedy. – DarthGizka Mar 07 '21 at 10:36
  • For one thing, `IDynamicMetaObject` might allow me to tunnel type information from the COM side to .NET, so that things like LINQPad's `.Dump()` work like they do with native objects instead of printing only `System.__ComObject`. – DarthGizka Mar 07 '21 at 10:44