How can I invoke a method of a private COM interface, defined in a base class, from a derived class?
For example, here is the COM interface, IComInterface
(IDL):
[
uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),
oleautomation
]
interface IComInterface: IUnknown
{
HRESULT ComMethod([in] IUnknown* arg);
}
Here's the C# class BaseClass
from OldLibrary
assembly, which implements IComInterface
like this (note the interface is declared as private):
// Assembly "OldLibrary"
public static class OldLibrary
{
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class BaseClass : IComInterface
{
void IComInterface.ComMethod(object arg)
{
Console.WriteLine("BaseClass.IComInterface.ComMethod");
}
}
}
Finally, here's an improved version, ImprovedClass
, which derives from BaseClass
, but declares and implement its own version of IComInterface
, because the base's OldLibrary.IComInterface
is inaccessible:
// Assembly "NewLibrary"
public static class NewLibrary
{
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class ImprovedClass :
OldLibrary.BaseClass,
IComInterface,
ICustomQueryInterface
{
// IComInterface
void IComInterface.ComMethod(object arg)
{
Console.WriteLine("ImprovedClass.IComInterface.ComMethod");
// How do I call base.ComMethod here,
// otherwise than via reflection?
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
if (iid == typeof(IComInterface).GUID)
{
ppv = Marshal.GetComInterfaceForObject(this, typeof(IComInterface), CustomQueryInterfaceMode.Ignore);
return CustomQueryInterfaceResult.Handled;
}
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
}
}
How do I call BaseClass.ComMethod
from ImprovedClass.ComMethod
without reflection?
I could use reflection, but in the real use case IComInterface
is a complex OLE interface with a number of members of complex signatures.
I thought that because both BaseClass.IComInterface
and ImprovedClass.IComInterface
are both COM interfaces with the same GUID and identical method signatures, and there's COM Type Equivalence in .NET 4.0+, so there has to be a way to do what I'm after without reflection.
Another requirement is that ImprovedClass
has to be derived from BaseClass
, because the C# client code expects an instance of BaseClass
, which it passes to the COM client code. Thus, containment of BaseClass
inside ImprovedClass
is not an option.
[EDITED] A real-life scenario which involves deriving from WebBrowser
and WebBrowserSite
is described here.
Here is my solution. Ok, it uses reflection, but I don't see where is the problem since it's much simpler, and the final usage is really just one line of code, like this:
and the utility method (reusable for any class) is this:
I figured it out, by using a helper contained object (
BaseClassComProxy
) and an aggregated COM proxy object, created withMarshal.CreateAggregatedObject
. This approach gives me an unmanaged object with separate identity, which I can cast (withMarshal.GetTypedObjectForIUnknown
) to my own equivalent version ofBaseClass.IComInterface
interface, which is not otherwise accessible. It works for any other private COM interfaces, implemented byBaseClass
.@EricBrown's points about COM identity rules have helped a lot with this research. Thanks Eric!
Here's a standalone console test app. The code solving the original problem with
WebBrowserSite
is posted here.Output:
You need to use
ICustomMarshaler
. I just worked out this solution and it is much less complex that what you've got, and there's no reflection. As far as I can tell,ICustomMarshaler
is the only way to explicitly control that magical ability of managed objects--such as RCW proxies--where they can be cast, on-the-fly, into managed interface pointers that they don't appear to explicitly implement.For the complete scenario I'll demonstrate, the bold-face items refer to the relevant parts of my example.
Scenario
You are receiving an unmanaged interface pointer (pUnk) into your managed code via a
COM interop
function (e.g. MFCreateMediaSession), perhaps previously using the excellent interop attribute([MarshalAs(UnmanagedType.Interface)] out IMFMediaSession pSess, ...
in order to receive a managed interface (IMFMediaSession). You'd like to "improve" (as you say) upon the backing__COM
object that you get in this situation by providing your own managed class (session) which:The key is to change your marshaling directive on the function that obtains the unmanaged object so that it uses a custom marshaler. If the
p/Invoke
definition is in an external library you don't control, you can make your own local copy. That's what I did here, where I replaced[Out, MarshalAs(UnmanagedType.Interface)]
with the new attribute:To deploy your own class that has the 'magical' interface behavior I mentioned above, you'll need two classes: an abstract base class which must be marked with
[ComImport]
(even if it's not, really) to provide the RCW plumbing, plus the other attributes I show (create your own GUID), and then a derived class, where you can put whatever enhanced functionality you like.The thing to note here is that neither the base class (_session in my example) nor the derived class (session) may explicitly list the interface that you expect it to proxy from an unmanaged
IUnknown
. Any "proper" interface definition that duplicates aQueryInterface
version will take precedence and ruin your ability to effortlessly call the unmanaged "base" methods via casting. You'll be back to COM slots and _vtbl land.This also means that, on instances of the derived class you will only be able to access the imported interface by casting. The derived class can implement the other, "extra" interfaces in the usual way. Those can be imported COM interfaces also, by the way.
Here are the two classes I just described where your app content goes. Notice how uncluttered they are compared to if you had to forward a gigantic interface through one or more member variables (which you'd have to initialize, and clean up, etc.)
Next is the
ICustomMarshaler
implementation. Because the argument we tagged to use this is anout
argument, the managed-to-native functions of this class will never be called. The main function to implement isMarshalNativeToManaged
, where I useGetTypedObjectForIUnknown
specifying the derived class I defined (session). Even though that class doesn't implementIMFMediaSession
, you'll be able to obtain that unmanaged interface via casting.Calling
Release
in theCleanUpNativeData
call is currently my best guess. (If it's wrong, I'll come back to edit this post).Here we see one of the few places in .NET I am aware of that you are allowed to (temporarily) violate type safety. Because notice that ppMediaSession pops out of the marshaler into your code as a full-blown, strongly-typed argument
out IMFMediaSession ppMediaSession
, but it certainly wasn't acting as such (i.e. without casting) immediately beforehand in the custom marshaling code.Now you're ready to go. Here are some examples showing how you can use it, and demonstrating that things work as expected:
I'm used to doing this in C++, so I'm mentally translating from C++ to C# here. (I.e., you may have to do some tweaking.)
COM identity rules require the set of interfaces on an object to be static. So, if you can get some interface that's definitely implemented by
BaseClass
, you can QI off that interface to getBaseClass
'es implementation ofIComInterface
.So, something like this: