我正在使用用C ++编写的非托管库。该库具有托管C ++(CLI)包装器,我正在使用托管代码中的库。非托管库(包括CLI包装)是由第三方编写的,但是我可以访问源代码。

不幸的是,托管包装器不能与AppDomains一起很好地使用。非托管库创建线程,并将从这些线程调用托管代码。当托管代码在非默认AppDomain中运行时,这会导致问题。我需要跨AppDomain调用才能使用标准工具进行单元测试。

为了解决这个问题,我在托管包装器中引入了委托,并使用Marshal.GetFunctionPointerForDelegate()获取了一个函数指针,该函数指针允许跨AppDomain调用成功。

这通常可以很好地工作,但是现在我遇到了一个新问题。我有以下事件序列。

Unmanaged thread ->
Unmanaged code 1 ->
Managed wrapper 1 ->
AppDomain transition (via delegate) ->
Managed wrapper 2 ->
Unmanaged code 2 ->

I have left out some details on how the library allows you to override some functionality in managed code above managed wrapper 2 which is the whole point of doing the unmanaged to managed transition in the first place.

Eventually unmanaged code 2 will have to return an unmanaged object to unmanaged code 1.

Without the delegate and AppDomain stuff managed wrapper 2 will wrap the unmanaged object and return it to managed wrapper 1 that will then transfer the state to the unmanaged object used by unmanaged code 1.

Unfortunately I'm having a hard time returning a managed object across the AppDomain transition.

I figured that I had to make the managed object passed across the AppDomain boundary serializable. However, that is not easily done. Instead I've created a simple class where I can store the type of the object I want to transfer and a string representing the state of the object. Both Type and String are easily marshalled and luckily I can always create an instance of the object using the default constructor and then initialize it from a string:

// Message is the base class of large hierarchy of managed classes.

[Serializable]
// Sorry for the "oldsyntax", but that is what the C++ library uses.
__gc class SerializedMessage {
public:
  SerializedMessage(Message* message)
    : _type(message->GetType()), _string(message->ToString()) { }

  Message* Create() {
    Message* message = static_cast<Message*>(Activator::CreateInstance(_type));
    message->InitializeFromString(_string);
    return message;
  }

private:
  Type* _type;
  String* _string;
};


在托管包装器2中,我返回一个SerializedMessage,然后在托管包装器1中,我通过调用SerializedMessage::Create取回原始消息的副本。或者至少那是我希望实现的目标。

不幸的是,AppDomain转换失败,并显示InvalidCastException,消息为无法将类型为“ SerializedMessage”的对象转换为类型为“ SerializedMessage”的消息。

我不确定发生了什么,但是错误消息可能表明正在从错误的AppDomain访问SerializedMessage对象。但是,使用Marshal.GetFunctionPointerForDelegate的全部目的是能够跨AppDomain进行调用。

我也尝试从SerializedMessage派生MarshalByRefObject,但是随后我得到InvalidCastException消息,消息无法将类型为'System.MarshalByRefObject'的对象转换为类型为'SerializedMessage'。

当我通过Marshal.GetFunctionPointerForDelegate()返回的指针进行调用时,我该怎么做才能将托管对象从另一个AppDomain传回?

对已接受答案的评论

关于“如何将程序集加载到第二个AppDomain中”的部分是正确的。

C ++代码在默认的AppDomain中执行,该默认AppDomain由单元测试运行器控制。托管程序集将加载到此单元测试运行器创建的第二个AppDomain中。我正在使用Marshal.GetFunctionPointerForDelegate启用从第一个到第二个AppDomain的托管C ++调用。

最初,我有一些FileNotFoundException试图加载托管程序集并解决此问题,我将托管程序集复制到了单元测试运行器的AppBase中。我仍然有些困惑,为什么.NET坚持要加载正在执行的程序集,但后来决定解决该问题,并简单地将丢失的文件复制为kudge。

不幸的是,这会从两个不同的AppDomain中的同一程序集的两个不同副本中加载同一类型,并且我认为这是InvalidCastException的根本原因。

我的结论是,我无法使用“标准”单元测试运行程序来测试托管C ++库,因为该库中的回调来自我无法控制的错误AppDomain。

最佳答案

无法将类型为“ SerializedMessage”的对象转换为类型为“ SerializedMessage”的对象。


对于您的问题,我将重点放在此问题上。实际消息应为“ Foo.SerializedMessage类型,以Bar.SerializedMessage类型”。换句话说,这里涉及两种类型,分别来自不同的程序集。 .NET类型的标识不仅是类名,还包括完全限定的程序集名。程序集显示名称,程序集版本和区域性。 DLL地狱对策。

检查程序集的构建方式,并验证SerializedMessage在任何程序集中仅出现一次。也可能由程序集加载到第二个AppDomain中的方式引起的,例如,使用LoadFile()会导致它。它会在没有加载上下文的情况下加载程序集,并且从此类程序集加载的任何类型甚至都不与正常加载的完全相同的程序集中的完全相同的类型兼容。

而且您最好远离GetFunctionPointerForDelegate(),非托管指针不要遵守AppDomain边界。尽管我不知道您为什么使用它。

08-03 15:34
查看更多