bobbyalex
bobbyalex

Reputation: 2751

Passing a managed class member to a c++ method

I have a C++ dll with the following method:

//C++ dll method (external)
GetServerInterface(ServerInterface* ppIF /*[OUT]*/)
{
//The method will set ppIF
}

//ServerInterface is defined as:
typedef void *  ServerInterface;

To access the dll from a C# project, I created a C++/CLI project and declared a managed class as follows:

public ref class ComWrapperManager
{
//
//
ServerInterface _serverInterface;
void Connect();
//
//
}

I use the Connect() method to call GetServerInterface as shown below. The first call works, the second doesn't. Can someone explain why? I need to persist that pointer as a member variable in the managed class. Any better way to do this?

    void Connect()
    {
    ServerInterface localServerInterface;
    GetServerInterface(&localServerInterface); //THIS WORKS

    GetServerInterface(&_serverInterface); //THIS DOESNT

    //Error 1   error C2664: 'ServerInterface ' : 
    //cannot convert parameter 1 from //'cli::interior_ptr<Type>' 
    //to 'ServerInterface *'


    }

Upvotes: 0

Views: 4670

Answers (2)

Hans Passant
Hans Passant

Reputation: 941327

You are passing a pointer to a member of a managed object. Such pointers are special, known as interior pointers. They are tracked by the garbage collector, it will modify the pointer value when the managed object is moved when the GC compacts the heap.

Problem is, you are passing that pointer to unmanaged code. The GC is not capable of modifying the copy of the pointer value that the native code is using. Now disaster strikes when another thread triggers a garbage collection, just when the native code is executing and dereferences the pointer. The object no longer exists at the original address. Very, very bad. And extremely hard to diagnose since it is so unlikely to happen.

The compiler can see you making this mistake. And complains with C2664.

The workaround is to pass a pointer that's stored in a memory location that's not going to get moved by the GC. Such a location is very easy to come by, a local variable qualifies. It is stored on the stack, it isn't going to be moved. So make it look like this instead:

void Connect()
{
    ServerInterface temp;
    GetServerInterface(&temp);
    this->_serverInterface = temp;
    // etc..
}

Which you already discovered yourself, just don't forget to assign the class member.

Upvotes: 7

David Yaw
David Yaw

Reputation: 27864

Here's why you can't do the second one: _serverInterface is a void pointer that is part of a managed class. Think about what the garbage collector does... It's allowed to move the managed objects around in memory however it wants, so the address of the void pointer can change from moment to moment. Therefore, it's not valid to use that address.

There are two solutions to this:

  1. As you noted, where you can pass the address of a stack variable to the unmanaged method. Unlike managed objects, the stack doesn't move when the garbage collector does its thing, so the address doesn't change. You can then take the data stored in the stack variable and copy it to the class field, and that works fine, because you're not dealing with the address of it.
  2. As the other answerer noted, you can lock your managed object in memory. Once it can't move, you can take the address of the void pointer field without issue. (He's showing C# syntax where you're looking for C++/CLI syntax. I'm not at a compiler to check, but I believe that the C++/CLI syntax is not the same.)

Of the two solutions, I prefer #1, the one you already have implemented: Solution #2 introduces a block of unmovable memory in the middle of the space that the garbage collector wants to rearrange. Given a choice, I prefer not to hamstring the garbage collector.

Upvotes: 1

Related Questions