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

2

11

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?

Abulia answered 6/12, 2010 at 13:20 Comment(1)
I highly recommend using CAutoNativePtr (codeproject.com/KB/mcpp/CAutoNativePtr.aspx) to handle lifetime management of the native object, then have your ref class focus on exposing functionality to managed clients. (This doesn't replace GC::KeepAlive; it replaces manual memory management.)Putupon
A
7

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

Amsterdam answered 6/12, 2010 at 13:40 Comment(0)
D
4

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

Dougherty answered 6/12, 2010 at 13:39 Comment(2)
That's what I need, although as max said, it's better to put the call inside FeedDogManaged() because it's not the responsibility of the caller of FeedDogManaged() to handle this.Abulia
Yup, agree completely. Didn't read it carefully enough the first time.Dougherty

© 2022 - 2024 — McMap. All rights reserved.