使用托管C ++时,如何从其他AppDomain返回托管对象?

时间:2010-12-17 10:49:56

标签: .net c++-cli appdomain

我正在使用用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 ->

我遗漏了一些关于库如何允许您覆盖托管包装器2 上方托管代码中的某些功能的细节,这是从一开始就进行非托管到托管转换的重点。

最终,非托管代码2 必须将非托管对象返回到非托管代码1

如果没有委托和AppDomain,托管包装器2 将包装非托管对象并将其返回到托管包装器1 ,然后将状态转移到由之使用的非托管对象非托管代码1

不幸的是,我很难在AppDomain转换中返回托管对象。

我认为我必须使通过AppDomain边界传递的托管对象可序列化。但是,这并不容易。相反,我创建了一个简单的类,我可以存储我想要传输的对象的类型和表示对象状态的字符串。 TypeString都很容易编组,幸运的是我总是可以使用默认构造函数创建对象的实例,然后从字符串初始化它:

// 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 中,然后通过调用{{1}获取原始邮件的副本}。或者至少那是我希望实现的目标。

不幸的是,AppDomain转换失败,SerializedMessage::Create消息无法将“SerializedMessage”类型的对象强制转换为“SerializedMessage”。

我不确定发生了什么,但错误消息可能表明正在从错误的AppDomain访问InvalidCastException对象。但是,使用SerializedMessage的全部意义是能够跨AppDomains调用。

我还尝试从Marshal.GetFunctionPointerForDelegate派生SerializedMessage,但后来我得到MarshalByRefObject消息无法将类型为'System.MarshalByRefObject'的对象转换为'SerializedMessage' ”。

当我通过InvalidCastException返回的指针调用时,我需要做什么才能从另一个AppDomain传回托管对象?

对已接受答案的评论

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

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

最初我有一些Marshal.GetFunctionPointerForDelegate尝试加载我的托管程序集并解决这个问题,我将托管程序集复制到单元测试运行程序的AppBase中。我仍然有点困惑为什么.NET坚持加载正在执行的程序集,但后来决定解决这个问题并简单地将丢失的文件复制为kludge。

不幸的是,它在两个不同的AppDomain中从同一个程序集的两个不同副本加载相同的类型,我认为这是FileNotFoundException的根本原因。

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

1 个答案:

答案 0 :(得分:1)

  

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

我会针对您的问题的核心关注此问题。真正的消息应该是“类型为Foo.SerializedMessage以键入Bar.SerializedMessage”。换句话说,这里涉及两种类型,来自不同的程序集。 .NET类型的标识不仅仅是类名,还包括完全限定的程序集名称。哪个是程序集显示名称和程序集版本和文化。 DLL地狱反措施。

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

你最好远离GetFunctionPointerForDelegate(),非托管指针不会观察AppDomain边界。虽然我不知道你为什么要使用它。