.NET 6 IDispatch client implementation crash - c#

.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?

Here's what is going on. The .NET object supports two dispinterfaces - one corresponding to IHello, another corresponding to Hello itself, and the latter is treated as the default one. I've confirmed that by adding an extra method Bar() to Hello, and checking the type in a native C++ client:
IUnknownPtr pUnk;
ROTGet(L"HelloTest", &pUnk);
IDispatchPtr pDisp(pUnk);
LPOLESTR Name = (LPOLESTR)L"Bar";
DISPID dispid;
hr = pDisp->GetIDsOfNames(IID_NULL, &Name, 1, 0, &dispid);
And I got S_OK and a valid, if bogus, DISPID. I then requested a DISPID for Foo in the same way, and got another bogus DISPID.
I've found two ways to fix that.
One, tell the framework that it should not implicitly generate a dispinterface for Hello. Annotate the class with [ClassInterface(ClassInterfaceType.None)], and it won't. The explicit check for the DISPID of Foo confirms that. The unmarshaled dispinterface in the client corresponds to IHello, and the methods work as expected.
Another approach, give IHello an identity (an IID) and make it marshalable. If I slap a custom GUID on that interface, and add a key with that GUID to HKCR\Interfaces, providing ProxyStubClsid32 equals to {00020420-0000-0000-C000-000000000046} (that's PSDispatch, the proxy/stub CLSID for IDispatch), the fragment also works.
One other way to tell COM that an IID corresponds to a dispinterface and should be marshaled as one is via implementing IStdMarshalInfo. I didn't try this.
One more note: the Hello class has to be declared public for this to work. If it's not, for some reason the as IHello returns null.

Related

C# Constructors Ambiguity with explicit call - Error CS0012

There is an unexplained ambiguity in C#, where I explicitly try to call a constructor but the compiler thinks it is a different constructor. I will start with showing a short C# architecture we use. Then show a small "working" example I created, and the possible solution to this, but still I like to understand why this happens.
The Architecture:
CLR DLL which bridges the C++ API.
C# API which uses the bridge level.
C# Client applications that use the C# API.
Note that the C# Clients are not allowed to use the CLR level.
Example I created
A class in the CLR DLL:
#pragma once
#include <string>
using namespace System;
namespace Inner {
public ref class AInner
{
public:
AInner() : _data(new std::wstring(L"")) {}
~AInner() {
delete _data;
}
property String^ Val
{
String^ get()
{
return gcnew String((*_data).data());
}
void set(String^ value) {
System::IntPtr pVal = System::Runtime::InteropServices::Marshal::StringToHGlobalUni(value);
*_data = (const wchar_t*)pVal.ToPointer();
System::Runtime::InteropServices::Marshal::FreeHGlobal(pVal);
}
}
private:
std::wstring* _data;
};
}
Class wrapping the CLR level, in a DLL:
using System;
using Inner;
namespace Outer
{
public class A
{
public A()
{
_inner.Val = String.Empty;
}
public A(string val)
{
init(val);
}
public string Val
{
get
{
return _inner.Val;
}
set
{
_inner.Val = value;
}
}
internal A(AInner inner)
{
_inner = inner;
}
private void init(string Val)
{
_inner = new AInner();
_inner.Val = String.Empty;
}
private AInner _inner;
}
}
Note that there is an internal constructor and a public constructor.
Executable Client using the C# API DLL:
using Outer;
namespace OneClient
{
class Program
{
static void Main(string[] args)
{
string myString = "Some String";
A testA = new A(myString);
}
}
}
Twist in the story:
In the DLL wrapping the CLR level, not ALL API should be used by external clients, but can be used by internal clients, thus the internals are exposed to the internal clients by adding [assembly: InternalsVisibleTo("OneClient")] to the 'AssemblyInfo.cs' of the DLL wrapping the CLR level.
The issue
When compiling the Client code I get the following error:
error CS0012: The type 'AInner' is defined in an assembly that is not referenced. You must add a reference to assembly 'InnerOne, Version=1.0.7600.28169, Culture=neutral, PublicKeyToken=null'.
I cannot use InnerOne because clients are not allowed to use this level.
The client is exposed to both A(string val) and A(AInner inner) constructors.
Possible Workarounds:
Remove the [assembly: InternalsVisibleTo("OneClient")] - This is unacceptable due to other classes internals that the specific client needs to use.
Change the A(string val) constructor to A(string val, bool unique=true) and use it A testA = new A(myString, true) - Not a nice solution.
Use default constructor A() and call testA.Val = myString; - This is actually OK but to much code.
Change the client code from A testA = new A(myString) to A testA = new A(val:myString); - This is actually the chosen solution.
Question
Why does this ambiguity happen?
I call the A(string val) with the myString which is actually a string value
This is very strange.
Is this a bug in Microsoft compiler?
Example Sources:
Source Code One.zip
Why does this ambiguity happen?
Because to satisfy the constructor overload resolution, the compiler needs to know what all the argument types are, and it doesn't know what an AInner is.
Why not expose the AInner version as a factory method:
static internal A Create(AInner inner)
{
return new A { _inner = inner };
}
I don't see any issue in this, the problem is we are used to do the things in a wrong/briefly way.
The correct answer fot this is:
A testA = new A(val:myString);
Furthermore, all your calls (in this way is a call to a constructor/initializer but it's a call anyway) should be with the parameter name. No one (even me) writes them, but...

COM interop and marshaling of VARIANT(VT_PTR)

We use a 3rd party COM object, one of which methods under certain conditions returns a VARIANT of VT_PTR type. That upsets the default .NET marshaler, which throws the following error:
Managed Debugging Assistant 'InvalidVariant' : 'An invalid VARIANT was
detected during a conversion from an unmanaged VARIANT to a managed
object. Passing invalid VARIANTs to the CLR can cause unexpected
exceptions, corruption or data loss.
Method signatures:
// (Unmanaged) IDL:
HRESULT getAttribute([in] BSTR strAttributeName, [retval, out] VARIANT* AttributeValue);
// C#:
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
Is there an elegant way to bypass such marshaler's behavior and obtain the underlying unmanaged pointer on the managed side?
What I've considered/tried so far:
A custom marshaler:
[return: MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof(IntPtrMarshaler))]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
I did implement IntPtrMarshaler, just to find the interop layer crashing the process even before any of my ICustomMarshaler methods gets called. Perhaps, the VARIANT* argument type is not compatible with custom marshalers.
Rewrite (or clone) the C# interface definition with getAttribute method redefined (like below) and do all the marshaling for output VARIANT manually:
void getAttribute(
[In, MarshalAs(UnmanagedType.BStr)],
string strAttributeName,
IntPtr result);
This doesn't seem nice (the interface itself has 30+ other methods). It'd also break existing, unrelated pieces of code which already make use of getAttribute without issues.
Obtain an unmanaged method address of getAttribute from vtable (using Marshal.GetComSlotForMethodInfo etc), then do the manual invocation and marshaling against my own custom delegate type (using Marshal.GetDelegateForFunctionPointer etc).
So far, I've taken this approach and it seem to work fine, but it feels as such an overkill for what should be a simple thing.
Am I missing some other feasible interop options for this scenario? Or, maybe there is a way to make CustomMarshaler work here?
What I would do is define a simple VARIANT structure like this:
[StructLayout(LayoutKind.Sequential)]
public struct VARIANT
{
public ushort vt;
public ushort r0;
public ushort r1;
public ushort r2;
public IntPtr ptr0;
public IntPtr ptr1;
}
And the interface like this;
[Guid("39c16a44-d28a-4153-a2f9-08d70daa0e22"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface MyInterface
{
VARIANT getAttributeAsVARIANT([MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}
Then, add an extension method somewhere in a static class like this, so the caller can have the same coding experience using MyInterface:
public static object getAttribute(this MyInterface o, string strAttributeName)
{
return VariantSanitize(o.getAttributeAsVARIANT(strAttributeName));
}
private static object VariantSanitize(VARIANT variant)
{
const int VT_PTR = 26;
const int VT_I8 = 20;
if (variant.vt == VT_PTR)
{
variant.vt = VT_I8;
}
var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf<VARIANT>());
try
{
Marshal.StructureToPtr(variant, ptr, false);
return Marshal.GetObjectForNativeVariant(ptr);
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
This will do nothing for normal variants, but will just patch it for VT_PTR cases.
Note this only works if the caller and the callee are in the same COM apartement.
If they are not, you will get the DISP_E_BADVARTYPE error back because marshaling must be done, and by default, it will be done by the COM universal marshaler (OLEAUT) which only support Automation compatible data types (just like .NET).
In this case, theoratically, you could replace this marshaler by another one (at COM level, not at NET level), but that would mean to add some code on C++ side and possibly in the registry (proxy/stub, IMarshal, etc.).
For my own future reference, here's how I ended up doing it, using the 3rd option mentioned in the question:
[ComImport, Guid("75A67021-058A-4E2A-8686-52181AAF600A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInterface
{
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}
private delegate int IInterface_getAttribute(
IntPtr pInterface,
[MarshalAs(UnmanagedType.BStr)] string name,
IntPtr result);
public static object getAttribute(this IInterface obj, string name)
{
var ifaceType = typeof(IInterface);
var ifaceMethodInfo = ((Func<string, object>)obj.getAttribute).Method;
var slot = Marshal.GetComSlotForMethodInfo(ifaceMethodInfo);
var ifacePtr = Marshal.GetComInterfaceForObject(obj, ifaceType);
try
{
var vtablePtr = Marshal.ReadIntPtr(ifacePtr);
var methodPtr = Marshal.ReadIntPtr(vtablePtr, IntPtr.Size * slot);
var methodWrapper = Marshal.GetDelegateForFunctionPointer<IInterface_getAttribute>(methodPtr);
var resultVar = new VariantClass();
var resultHandle = GCHandle.Alloc(resultVar, GCHandleType.Pinned);
try
{
var pResultVar = resultHandle.AddrOfPinnedObject();
VariantInit(pResultVar);
var hr = methodWrapper(ifacePtr, name, pResultVar);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
if (resultVar.vt == VT_PTR)
{
return resultVar.ptr;
}
try
{
return Marshal.GetObjectForNativeVariant(pResultVar);
}
finally
{
VariantClear(pResultVar);
}
}
finally
{
resultHandle.Free();
}
}
finally
{
Marshal.Release(ifacePtr);
}
}

Passing an object in C# that inherits from a generic causes a cast exception with IDispatch

I have a COM call I would like to make
[DispId(163)]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType =
MethodCodeType.Runtime)]
[return: MarshalAs(UnmanagedType.IDispatch)]
object CreatePropertyManagerPage
([MarshalAs(UnmanagedType.BStr), In] string Title,
[In] int Options,
[MarshalAs(UnmanagedType.IDispatch), In] object Handler,
[In, Out] ref int Errors);
The Handler property is the one causing me problems. If I pass in an object that inherits from
public abstract class PmpBase<TMacroFeature,TData> : IPropertyManagerPage2Handler9
then I get a InvalidCastException. However if I remove the generics and pass a class that inherits from
public abstract class PmpBase : IPropertyManagerPage2Handler9
by hard coding the types in the file then the object passes into the call. I can replicate the same findings by doing
new DispatchWrapper(foo)
where foo is an instance of the class that derives from either the generic ( it fails ) and the non generic ( it passes )version.
I have zero knowledge of COM and am just trying to use the automation interface to solidworks. Am I trying to do something impossible. I haven't found any obvious articles that associate generics with errors with COM.
I can't explain why the problem occurs but the solution is quite easy. I created a proxy class for the interface I was trying to pass to COM.
public class PropertyManagerPage2Handler9Wrapper : IPropertyManagerPage2Handler9
{
readonly IPropertyManagerPage2Handler9 _Implementation;
public PropertyManagerPage2Handler9Wrapper(IPropertyManagerPage2Handler9 implementation)
{
_Implementation = implementation;
}
public void AfterActivation()
{
_Implementation.AfterActivation();
}
public void OnClose(int Reason)
{
_Implementation.OnClose(Reason);
}
public void AfterClose()
{
_Implementation.AfterClose();
}
<snip>
}
and passed the wrapper to the COM function instead of the original instance. Then there was no class cast exception.
var propertyManagerPage = SwApp.CreatePropertyManagerPage
(_Name, options, new PropertyManagerPage2Handler9Wrapper(this), ref errors);
instead of this
var propertyManagerPage = SwApp.CreatePropertyManagerPage
(_Name, options, this, ref errors);

.Net COM object needs to be a STA Thread

I'm implementing a .NET COM Server for a 3rd party system which is the COM client (C++). I had a IDL provided which I used to generate the .dll with tlbimp. In the API its also recommended to implement the interfaces dual and Apartment. In this COM server I have to instantiate a WPF component, which currently fails because it has to be called from a STA Thread. I did a lot of googeling already on how to enforce my COM Server to be of Threading model Apartment. I changed the registry entry which didn't help and then I found following article on stackoverflow: How to make make a .NET COM object apartment-threaded?
I followed the instructions to implement ICustomQueryInterface but it fails; Here my IDL:
[
object,
uuid(ABB09FED-F7BE-4654-88BC-5F4D2941111A),
dual,
nonextensible,
helpstring("IDVRServer Interface"),
pointer_default(unique)
]
interface IDVRServer : IDispatch{
[id(1), helpstring("Creates a DVR Unit Connection based on connection information
such as IP address, user name...")]
HRESULT CreateConnection(
[in] DVRConnectionInfo* pConnInfo, [out, retval] IDVRUnitConnection** ppConn);
};
Here the implementation in C# (including the ICustomQueryInterface):
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)
public class DVRServer : IDVRServer, ICustomQueryInterface
{
private IntPtr _dvrPointer;
public DVRServer() {
NativeMethods.CoGetStdMarshalEx(this, NativeMethods.SMEXF_SERVER, out _dvrPointer);
}
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv) {
if (iid == NativeMethods.IID_IMarshal_DVRServerDispatch)
{
res = 0;
res = Marshal.QueryInterface(_dvrPointer, ref NativeMethods.IID_IMarshal_DVRServerDispatch, out ppv);
string hex = res.ToString("X");
if (res != 0)
{
return CustomQueryInterfaceResult.Failed;
}
return CustomQueryInterfaceResult.Handled;
}
return CustomQueryInterfaceResult.Failed;
}
static class NativeMethods
{
public static Guid IID_IMarshal_DVRServerDispatch = new Guid("ABB09FED-F7BE-4654-88BC-5F4D2941111A");
public const UInt32 SMEXF_SERVER = 1;
[DllImport("ole32.dll", PreserveSig = true)]
public static extern void CoGetStdMarshalEx([MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, UInt32 smexflags, out IntPtr ppUnkInner);
}
The result of Marshal.QueryInterface is always 0x80004002 E_NOINTERFACE. I tried a lot of different things already, also deriving from StandardOleMarshalObject and ServicedComponent but nothing worked. One question is also if the registry does count at all or if I can just ignore the ThreadingModel in there, I didn't really get that from Noseratio's post. Thanks in advance for any help on that topic.
I use VisualStudio 2012 and .Net Framework 4.

Returning Managed C# List to Unmanaged C++ code

I have a C# dll ( for which register for COM interop option is set).
This C# dll has the below interface and class
interface IMyInterface
{
bool IsNameExists(string name);
List<string> GetNameList();
}
public class MyClass : IMyInterface
{
public bool IsNameExists(string name)
{
//DO Something
}
public List<string> GetNameList()
{
// DO something
}
}
I need to call the methods IsNameExists and GetNameList from unmanaged C++.
#import "..\..\ProdCon\bin\ProdCon.tlb" raw_interfaces_only
void main()
{
HRESULT hr =::CoInitialize(NULL);
IMyInterface pIMyInterface(__uuidof(MyClass));
VARIANT_BOOL bRet = FALSE;
BSTR bstrName = ::SysAllocString(_T("RAJESH"));
hr = pIMyInterface->IsNameExists(bstrName,&bRet);
}
So I created COM object as above and called the IsNameExists mehtod without any issue.
Since GetNameList method returns list, I am getting the below warning
'MyDll.IMyInterface.GetNameList(#0), MyDll'. Warning: Type library
exporter encountered a generic type instance in a signature. Generic
code may not be exported to COM.
Please help me how to return C# list to unmanaged C++ code. So that unmanaged C++ code can use this list.
'Generic code may not be exported to COM.' => hence its the type parameter string in public List GetNameList(). Thus essentially you need access to a non-generic c# method to get the data.
If you have control of the MyClass codebase you could (for example) add a:
public string[] GetNameArray()
{
return GetNameList.ToArray();
}
If not then you'll need to write a proxy/wrapper class to do something similar to the above and expose that via COM, either as a one off or a 'general' methodology using reflection say.
See for example http://weblog.west-wind.com/posts/2007/Jul/10/Generics-and-COM-Interop-dont-mix

Categories

Resources