.NET 6 has trouble calling its own IDispatch
objects, if marshaled.
To reproduce:
if (Array.IndexOf(Environment.GetCommandLineArgs(), "/s")>=0) //Server
{
Thread t = new Thread(new ThreadStart(() =>
{
Hello h = new Hello();
new RunningObjectTable().Register("HelloTest", h);
Thread.Sleep(1000 * 3600);
}));
t.SetApartmentState(ApartmentState.MTA);
t.Start();
Thread.Sleep(1000 * 3600);
}
else //Client
{
object o = new RunningObjectTable().Get("HelloTest");
IHello h = o as IHello;
int f = h.Foo();
Console.WriteLine(h);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")] //IID_IDispatch
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
public interface IHello
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[DispId(1)]
public int Foo();
}
public class Hello: IHello
{
public int Foo()
{
Debug.WriteLine("Hello from server");
return 19;
}
}
Run the program with /s, that's the object server. Then run another copy to become the client.
The method invocation line in the client crashes with "Member not found" - HRESULT 0x80020003, DISP_E_MEMBERNOTFOUND. Normally it would mean a bogus DISPID, but where would the discrepancy possibly come from?
One minor infraction in that code is that the interface is decorated with the IID of IDispatch. With a custom IID, as COM prescribes, it doesn't even unmarshal as the dispinterface (the as IHello
line returns null); internally, there's a QueryInterface
call across processes with said custom IID, the dispinterface is not registered under HKCR\Interfaces, so the interprocess COM machinery doesn't know how to marshal it. At least that's my theory.
A similar piece of logic works fine if the server is a native (C++) one. If the same C# piece is recompiled against .NET framework 4.72, it doesn't even get that far, the o as IHello;
line returns null.
RunningObjectTable
is a helper class around the ROT. For completeness' sake, here:
internal class RunningObjectTable
{
#region API
[DllImport("ole32.dll")]
private static extern int CreateItemMoniker([MarshalAs(UnmanagedType.LPWStr)] string
lpszDelim, [MarshalAs(UnmanagedType.LPWStr)] string lpszItem,
out IMoniker ppmk);
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
#endregion
private IRunningObjectTable m_rot;
public RunningObjectTable()
{
GetRunningObjectTable(0, out m_rot);
}
private IMoniker CreateItemMoniker(string s)
{
IMoniker mon;
CreateItemMoniker("", s, out mon);
return mon;
}
public int Register(string ItemName, object o)
{
return m_rot.Register(0, o, CreateItemMoniker(ItemName));
}
public void Unregister(int ROTCookie)
{
m_rot.Revoke(ROTCookie);
}
public object Get(string ItemName)
{
object o;
m_rot.GetObject(CreateItemMoniker(ItemName), out o);
return o;
}
}
Can someone with access to .NET Core internals please tell me what's going on?