在使用C++与c#使用delegate时,会出现跨域错误,
详细错误:Cannot pass a GCHandle across AppDomains
现在摘取两篇文章帮助大家理解
1:http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html
I'm currently working on adding easy to use Real Time Data server support to my Managed XLL Excel Addin system. This lets you use the =RTD()
functionality of Excel to push real time data into your spreadsheets without needing to understand COM and without needing to register any COM objects on your machine. You simply add some attributes to your managed code, compile your assembly and drop it in the same directory as the Managed XLL and it does the rest. This is working well and makes Excel Real Time Data servers VERY easy to write.
Anyway, during this I came across a slight problem. The architecture is such that I create a custom AppDomain for the managed code that the XLL hosts inside of Excel. The XLL then causes unmanaged callbacks into this managed code when the various Excel RTD COM object functions are called. The bridge between the unmanaged code and the managed code is a gcroot<>
that holds onto the managed code inside the unmanaged wrapper via a GCHandle
. The COM call eventually calls through this gcroot<>
and into the managed code. Unfortunately unmanaged code knows nothing of AppDomains and (it seems from reading this) that an AppDomain is chosen arbitrarily when the unmanaged call into the managed code occurs. This causes the code to fail with a "Cannot pass a GCHandle across AppDomains" exception.
Luckily for me, Miral, over at Thoughts from Mirality has already solved this issue and covers the problem and a solution here. The trick is that you need to use a delegate, which knows about the AppDomain that it relates to, and then call through the delegate by converting it to a function pointer. This effectively marshals the unmanaged call into the correct AppDomain before executing the managed code.
Miral's example code was enough to help me along my way but I stumbled a couple of times before I solved my particular version of the problem so I thought it would be worth documenting what I needed to do to get this to work for me.
Lets say we have the following unmanaged callback. Some code that knows nothing about managed code wants to call the following function:
void
Connect(
const
long
topicId,
const
JetByteTools::Win32::StringVector &topics,
const
bool
getNewValue,
VARIANT &result);
|
The first thing we need to do is create a typedef for the function signature and a managed class that can provide us with the delegate that we need to be able to call this correctly. Something like this, perhaps:
typedef
void
(__stdcall ConnectFnc)(
const
long
topicId,
const
JetByteTools::Win32::StringVector &topics,
const
bool
getNewValue,
VARIANT &result);
ref
class
ConnectDelegate
{
public
:
ConnectDelegate();
: m_delegate(gcnew Delegate(
this
, &ConnectDelegate::Connect))
{
}
ConnectFnc *GetDelegateFunctionPointer();
{
return
(ConnectFnc*)(Marshal::GetFunctionPointerForDelegate(m_delegate).ToPointer());
}
private
:
void
Connect(
long
topicId,
JetByteTools::Win32::StringVector &topics,
bool
getNewValue,
VARIANT &result)
{
// YOUR CODE GOES HERE and is executed in the correct AppDomain...
}
delegate
void
Delegate(
long
topicId,
JetByteTools::Win32::StringVector &topics,
bool
getNewValue,
VARIANT &result);
Delegate ^m_delegate;
};
|
The managed code that you want to execute inside the correct AppDomain lives in ConnectDelegate::Connect()
. The constructor of our class simply creates the delegate to call the function and GetDelegateFunctionPointer()
does exactly what it says on the tin. An instance of the ConnectDelegate
class needs to be created by code that is running inside the AppDomain that you want your unmanaged callback to call into.
It might look like you can then simply call ConnectDelegate::GetDelegateFunctionPointer()
from your unmanaged code to obtain a function pointer and make your callback, but unfortunately you need one further level of indirection before that's possible. As I said at the start, the problem is that calling through a GCHandle
via a gcroot<>
from unmanaged code gives a "Cannot pass a GCHandle across AppDomains" exception. Well, right now you still need a gcroot<>
to hold onto your ConnectDelegate
so that you can call it from unmanaged code and calling ConnectDelegate::GetDelegateFunctionPointer()
calls through that GCHandle
and raises the exact same exception... The trick is that you also need to call ConnectDelegate::GetDelegateFunctionPointer()
from within your target AppDomain and then store away the function pointer for later use. Since this leaves you with a gcroot<ConnectDelegate^>
and a ConnectFnc*
to hold onto in unmanaged code I put together a simple unmanaged class which manages both of these and provides an overloaded function call operator to make it easy to call the ConnectFnc*
. The result is something like this:
class
ConnectFunction
{
public
:
ConnectFunction()
: m_pDelegate(0),
m_pFunction(0)
{
}
~ConnectFunction()
{
delete
m_pDelegate;
}
void
operator()(
const
long
topicId,
const
JetByteTools::Win32::StringVector &topics,
const
bool
getNewValue,
VARIANT &result)
const
{
m_pFunction(topicId, topics, getNewValue, result);
}
void
Setup()
{
m_pDelegate =
new
gcroot<connectdelegate^>(gcnew ConnectDelegate());
m_pFunction = (*m_pDelegate)->GetDelegateFunctionPointer();
}
private
:
gcroot<ConnectDelegate ^> *m_pDelegate;
ConnectFnc *m_pFunction;
// No copies do not implement
ConnectFunction(
const
ConnectFunction &rhs);
ConnectFunction &operator=(
const
ConnectFunction &rhs);
};
|
You can then create an instance of this class and then call Setup()
from within your target AppDomain. You can then use the function call operator on your instance from within your unmanaged code to make the callback. You can move the body of Setup()
into the constructor if you like, my particular situation requires two stage construction.
2:https://resnikb.wordpress.com/2009/05/18/passing-ccli-delegate-to-native-code/
Recently I had to interface a C++/CLI assembly with a native DLL written in C. This is mostly straightforward, but the C DLL could raise an internal event and provided a way to have the application notified of this event. In order to be informed, the application has to register a callback function that will be invoked by the DLL when the event is raised. The registration function is declared like this:
1
2
|
typedef
void
(__stdcall* EventCallback)();
void
RegisterCallback(EventCallback callback);
|
Using an ordinary function for the callback would be easy, but I wanted to use a .NET delegate so that I could convert the native event into a .NET event. This scenario also turns out to be supported by .NET. All you need to take care of is to prevent the delegate from being moved or collected by the garbage collector.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public
delegate
void
EventDelegate();
ref
class
NativeInterface
{
public
:
NativeInterface()
{
// Create the delegate from a member function
nativeCallback_ = gcnew EventDelegate(
this
, &NativeInterface::Callback);
// As long as this handle is alive, the GC will not move or collect the delegate
// This is important, because moving or collecting invalidate the pointer
// that is passed to the native function below
delegateHandle_ = GCHandle::Alloc(nativeCallback_);
// This line will actually get the pointer that can be passed to
// native code
IntPtr ptr = Marshal::GetFunctionPointerForDelegate(nativeCallback_);
// Convert the pointer to the type required by the native code
RegisterCallback(
static_cast
<EventCallback>(ptr.ToPointer()) );
}
!NativeInterface()
{
// Free the handle to the delegate, allowing GC to collect
// the delegate
if
(delegateHandle_.IsAllocated)
delegateHandle_.Free();
}
private
:
GCHandle delegateHandle_;
EventDelegate^ nativeCallback_;
void
Callback()
{
Console::WriteLine(
"Native event raised"
);
}
};
|