How to return a managed entity from another AppDomain when using managed C ++?

I am using an unmanaged library written in C ++. The library has a C ++ managed shell (CLI), and I use the library from managed code. The unmanaged library (including the CLI wrapper) is written by a third party, but I have access to the source code.

Unfortunately, managed wrappers do not work well with AppDomains. An unmanaged library creates threads and is called into managed code from these threads. This leads to problems when managed code runs in a non-standard AppDomain. And I need cross-application calls in order to be able to use unit tests to use standard tools.

To solve this problem, I introduced delegates to a managed shell and used Marshal.GetFunctionPointerForDelegate() to get a pointer to a function that allows cross-calls to AppDomain.

This generally works well, but now I have a new problem. I have the following sequence of events.

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

I did not talk about how the library allows you to override some functions in managed code above managed wrapper 2, which generally means performing an unmanaged transition.

Ultimately, unmanaged code 2 should return the unmanaged object to unmanaged code 1.

Without delegating the delegate and AppDomain, managed wrapper 2 will wrap the unmanaged object and return it to managed shell 1, which will then transfer state to the unmanaged object used by unmanaged code 1.

Unfortunately, it's hard for me to return a managed entity through the AppDomain transition.

I figured that I had to make the managed entity passing through the border of the ApplicationDomain interface serializable. However, this is not easy to do. Instead, I created a simple class where I can save the type of object I want to pass, and a string representing the state of the object. Both Type and String easily sorted and, fortunately, I can always instantiate the object using the default constructor and then initialize it from the 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; }; 

In managed shell 2, I return SerializedMessage and in managed wrapper 1, then I return a copy of the original message by calling SerializedMessage::Create . Or at least that's what I was hoping to achieve.

Unfortunately, the AppDomain transition fails with an InvalidCastException error with the message. Cannot pass an object of type "SerializedMessage" to enter "SerializedMessage".

I'm not sure what is happening, but an error message may indicate that the SerializedMessage object is accessing from the wrong AppDomain. However, the whole point of using Marshal.GetFunctionPointerForDelegate is the ability to call through AppDomains.

I also tried to get SerializedMessage from MarshalByRefObject , but then I get an InvalidCastException with the message Unable to cast object of type "System.MarshalByRefObject" on type "SerializedMessage".

What do I need to do to pass the managed object back from another AppDomain when I call the pointer returned by Marshal.GetFunctionPointerForDelegate() ?

Comments on the accepted answer

The part about how the assembly loaded in the second AppDomain is correct.

C ++ code is executed in the standard AppDomain, which is controlled by the unit test runner. The managed assembly is loaded into the second AppDomain created by this unit test runner. I use Marshal.GetFunctionPointerForDelegate to enable C ++ managed calls from the first to the second AppDomain.

I initially got a few FileNotFoundException trying to load my managed assembly and get around this. I copied my managed assembly into the AppBase unit test runner. I'm still a little confused why .NET insists on loading the assembly itself, which is running, but decided to work on this issue later and just copy the missing file as kludge.

Unfortunately, it loads the same type from two different copies of the same assembly into two different AppDomains, and I assume this is the main reason for InvalidCastException .

My conclusion is that I cannot use the “standard” unit test runner to test the C ++ managed library, because the callbacks from this library come from the wrong AppDomain, with which I have no control.

+4
source share
1 answer

Cannot pass an object of type "SerializedMessage" to enter "SerializedMessage".

I would focus on this problem at the core of your problem. The actual message must be "type Foo.SerializedMessage to print Bar.SerializedMessage". In other words, two types are involved here: from different assemblies. The .NET type identifier is not just a class name, but also the fully qualified name of the assembly. What will be the display name of the assembly, as well as the assembly version and culture. Hell DLL counter.

Check how your assemblies are built, and make sure that SerializedMessage is displayed only once in any of your assemblies. It can also be caused by how the assembly was loaded into the second AppDomain, using LoadFile (), it will call it, for example. It loads assemblies without a loading context, and any types loaded from such an assembly are not even compatible with the same type from the same assembly that was loaded normally.

And you better stay away from GetFunctionPointerForDelegate (), unmanaged pointers don't respect the boundaries of the AppDomain. Although I do not know why you use it.

+1
source

Source: https://habr.com/ru/post/1332481/


All Articles