Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++/CLI: preventing garbage collection on managed wrapper of unmanaged resource

I have a C++ unmanaged class NativeDog that needs to be used from C#, so I've create a wrapper class ManagedDog.

// unmanaged C++ class
class NativeDog
{
    NativeDog(...); // constructor
    ~NativeDog(); // destructor
    ...
}

// C++/CLI wrapper class
ref class ManagedDog
{
    NativeDog* innerObject; // unmanaged, but private, won't be seen from C#
    ManagedDog(...)
    {
        innerObject = new NativeDog(...);
        ...
    }

    ~ManagedDog() // destructor (like Dispose() in C#)
    {
        // free unmanaged resources
        if (innerObject)
            delete innerObject;
    }

    !ManagedDog() // finalizer (like Finalize() in C#, in case
    {             // the user forgets to dispose)
        ~ManagedDog(); // call destructor
    }
}

All is well, and I use the class like this:

// in C++/CLI
// this function is called from C++ code
void MyLibrary::FeedDogNative(NativeDog* nativedog)
{
    ... // (***)
}
// this function is called from C#, passes on the dog to the native function
void MyLibrary::FeedDogManaged(ManagedDog^ dog)
{
    NativeDog* rawdog = dog->innerObject;
    MyLibrary::FeedDogNative(rawdog);
}

// C# client code
void MyFunc()
{
    ManagedDog dog = new ManagedDog(...);
    MyLibrary.FeedDogManaged(dog);
}

See what's wrong? I didn't either at first, until very strange things started happening from time to time. Basically if after calling MyFunc() the program is paused by the GC while it is somewhere in the native function FeedDogNative (marked (***) above), it will think the managed wrapper can be collected because it will no longer be used, neither in the C# MyFunc (it's a local variable and will not be used after the FeedDogManaged call), neither in FeedDogManaged. And so this has actually happened on occasions. The GC calls the Finalizer, which deletes the native dog object, even though FeedDogNative has not finished using it! So my unmanaged code is now using a deleted pointer.

How can I prevent this? I can think of some ways (e.g. a dummy call pretending to use dog at the end of FeedDogManaged) but what would the recommended way be?

like image 522
Laurent Avatar asked Dec 06 '10 13:12

Laurent


2 Answers

You need a GC::KeepAlive() call in your FeedDogManaged function. Seems like it is an exact use case for that.

like image 134
max Avatar answered Sep 23 '22 15:09

max


In your managed code, add GC.KeepAlive(dog) following the call to FeedDogManaged():

http://msdn.microsoft.com/en-us/library/system.gc.keepalive(VS.71).aspx

like image 24
twon33 Avatar answered Sep 23 '22 15:09

twon33