Reputation: 1033
I am trying to work on calling a clr 4.0 contorl in clr 2.0 I have three classes. My problem is the line in class 2, c.Add(x).
This line throws the error
Unable to cast object of type 'System.__ComObject' to type 'System.Windows.Forms.Control'.
Stacktrace
at System.StubHelpers.InterfaceMarshaler.ConvertToManaged(IntPtr pUnk, IntPtr itfMT, IntPtr classMT, Int32 flags)
at Net4ToNet2Adapter.IClassAdapter.LoadRyderControl(Int32 atacode, Int32 eventid, Control c)
at Net2Assembly.RyderQuestion..ctor() in C:\Users\casmith\Desktop\C#\Net2Assembly\RyderQuestion.cs:line 28
at Net2Assembly.Program.Main() in C:\Users\casmith\Desktop\C#\Net2Assembly\Program.cs:line 17
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
It seems to me that it cannot access the control from the object.
Class 1: Net 2 CLR
namespace Net2Assembly
{
public partial class RyQuestion : Form
{
private IClassAdapter _ryderControl;
public RyQuestion()
{
InitializeComponent();
var classAdapterType = Type.GetTypeFromProgID("Net4ToNet2Adapter.MyClassAdapter");
var classAdapterInstance = Activator.CreateInstance(classAdapterType);
var myClassAdapter = (IClassAdapter)classAdapterInstance;
_ryControl = myClassAdapter;
myClassAdapter.LoadRyControl(17, 291457,this.Panel1);
}
public void LoadQuestionsTC()
{
_ryControl.LoadQuestionsTC();
}
public void LoadQuestionsCloseout()
{
_ryControl.LoadQuestionsCloseout();
}
}
}
Class 2: My CLR 4 assembly
namespace Net4Assembly
{
public class RyderControlWrapper
{
private WindowsFormsApplication3.RyCriticalPath _ryControl;
public void LoadRyControl(int atacode, int eventid,Control c)
{
WindowsFormsApplication3.RyderCriticalPath x = new WindowsFormsApplication3.RyCriticalPath(atacode, 2945784);
_ryControl = x;
c.Add(x); //Bad line :(
}
public void LoadQuestionsTC()
{
_ryControl.LoadQuestionsTC();
}
public void LoadQuestionsCloseout()
{
_ryControl.LoadQuestionsCloseout();
}
}
}
Class 3: Net 4 to net 2 adapter
namespace Net4ToNet2Adapter
{
public class MyClassAdapter : IClassAdapter
{
private RyControlWrapper _rcWrapper = new RyControlWrapper();
public void LoadRyControl(int atacode, int eventid,Control c)
{
_rcWrapper.LoadRyControl(atacode, eventid,c);
}
public void LoadQuestionsTC()
{
_rcWrapper.LoadQuestionsTC();
}
public void LoadQuestionsCloseout()
{
_rcWrapper.LoadQuestionsCloseout();
}
}
}
namespace Net4ToNet2Adapter
{
[ComVisible(true)]
public interface IClassAdapter
{
void LoadRyderControl(int atacode, int eventid, Control c);
void LoadQuestionsTC();
void LoadQuestionsCloseout();
}
}
Upvotes: 1
Views: 1309
Reputation: 13177
The problem is that you are mixing two Control
types, one from .NET 2 and the other one from .NET 4. This simply cannot be done. You can't pass managed object between two different CLRs, as if they were of the same type, that's why you had to use COM in the first place. The managed Control
object gets "wrapped" in ComObject, but it can't be converted to the new Control
type, that's why you see this exception. (It is possible to treat this object as Control
, with some heavy proxying, but it would bring more problems that it would solve - but I will research this possibility later.)
So what to do? You could put RyderCriticalPath
into the .NET 2 assembly and make it implement an interface IRyderCriticalPath
placed in the adapter assembly. Create the instance of it in the .NET 2 assembly and pass it to LoadRyControl
as the interface. Don't pass the Control
. Move the c.Add(x);
to the calling method (.NET 2).
Of course, this is what I would do with the code you provided, but the point is that you have to pass managed objects only as interfaces, exposing only methods that are needed to control it.
Edit:
As promised, I've delved into the possibility of proxying the Control
object. Yes, it is possible, but not fully. Only "remotable" types can be proxied. Therefore, you can't access any properties of non-remotable types, except serializable types. So you should still use the original solution, because this won't work for your problem (but it might be useful for others):
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Windows.Forms;
namespace Net4ToNet2Adapter
{
[ComVisible(true)]
[Guid("E36BBF07-591E-4959-97AE-D439CBA392FB")]
public interface IMyClassAdapter
{
void DoNet4Action( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ProxyMarshaler))] Control c);
}
[ComVisible(true)]
[Guid("9F973534-E089-4C22-A481-54403B97DED9")]
public interface IProxyProvider
{
Type Type{get;}
object Instance{get;}
object Invoke(string method, Type[] signature, object[] args);
}
public class ProxyMarshaler : ICustomMarshaler
{
private static readonly ProxyMarshaler instance = new ProxyMarshaler();
public static ICustomMarshaler GetInstance(string cookie)
{
return instance;
}
public IntPtr MarshalManagedToNative(object ManagedObj)
{
return Marshal.GetIUnknownForObject(new ProxyProvider(ManagedObj));
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
IProxyProvider prov = (IProxyProvider)Marshal.GetObjectForIUnknown(pNativeData);
return new ComProxy(prov).GetTransparentProxy();
}
public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.Release(pNativeData);
}
public void CleanUpManagedData(object ManagedObj)
{
ComProxy proxy = (ComProxy)RemotingServices.GetRealProxy(ManagedObj);
proxy.Dispose();
}
public int GetNativeDataSize()
{
return -1;
}
private class ProxyProvider : IProxyProvider
{
public Type Type{get; private set;}
public object Instance{get; private set;}
public ProxyProvider(object instance)
{
Instance = instance;
Type = instance.GetType();
}
public object Invoke(string method, Type[] signature, object[] args)
{
MethodInfo mi = Type.GetMethod(method, signature);
if(mi == null || mi.IsStatic) throw new NotSupportedException();
DeproxyArgs(args);
object ret = mi.Invoke(Instance, args);
ProxyArgs(args);
return ProxyValue(ret);
}
public static bool IsProxyable(Type t)
{
return t.IsInterface || typeof(MarshalByRefObject).IsAssignableFrom(t) || t == typeof(object);
}
public static void DeproxyArgs(object[] args)
{
for(int i = 0; i < args.Length; i++)
{
args[i] = DeproxyValue(args[i]);
}
}
public static void ProxyArgs(object[] args)
{
for(int i = 0; i < args.Length; i++)
{
args[i] = ProxyValue(args[i]);
}
}
public static object DeproxyValue(object val)
{
var pp = val as IProxyProvider;
if(pp != null)
{
if(val is ProxyProvider) return pp.Instance;
else return ComProxy.GetProxy(pp);
}
return val;
}
public static object ProxyValue(object val)
{
ComProxy proxy = ComProxy.GetProxy(val);
if(proxy != null)
{
return proxy.Provider;
}else if(val != null && ProxyProvider.IsProxyable(val.GetType()))
{
return new ProxyProvider(val);
}
return val;
}
}
private sealed class ComProxy : RealProxy, IDisposable
{
public IProxyProvider Provider{get; private set;}
public ComProxy(IProxyProvider provider) : base(provider.Type == typeof(object) ? typeof(MarshalByRefObject) : provider.Type)
{
Provider = provider;
}
public static object GetProxy(IProxyProvider provider)
{
return new ComProxy(provider).GetTransparentProxy();
}
public static ComProxy GetProxy(object proxy)
{
if(proxy == null) return null;
return RemotingServices.GetRealProxy(proxy) as ComProxy;
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage msgCall = msg as IMethodCallMessage;
if(msgCall != null)
{
object[] args = msgCall.Args;
try{
ProxyProvider.ProxyArgs(args);
object ret = Provider.Invoke(msgCall.MethodName, (Type[])msgCall.MethodSignature, args);
ProxyProvider.DeproxyArgs(args);
ret = ProxyProvider.DeproxyValue(ret);
return new ReturnMessage(ret, args, args.Length, msgCall.LogicalCallContext, msgCall);
}catch(TargetInvocationException e)
{
return new ReturnMessage(e.InnerException, msgCall);
}catch(Exception e)
{
return new ReturnMessage(e, msgCall);
}
}
return null;
}
~ComProxy()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if(disposing)
{
Marshal.FinalReleaseComObject(Provider);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
}
It uses an interface IProxyProvider
that supports remote invoking of methods on an object, transparently proxying all arguments and return values. It adds a custom marshaler that handles all this, just add it to the MarshalAs
attribute and it will process all remotable objects passed or received.
Upvotes: 4