3

Consider the following C++/CLI code fragment:

public ref class MyData
{

private:
  UnmanagedObject* pobj;

public:
  MyData()
  {
    pobj = ... // acquire unmanaged pointer
  }

  !MyData()
  {
    delete pobj;  // finalizer
    pobj = NULL;
  }

  void DoSomething()
  {
    // next line calls a native method that has no knowledge of .NET
    CallToNativeMethod(data->pobj);
  }

  static void Test()
  {
    MyData^ data = gcnew MyData();
    data->DoSomething()
  }
}

This class represents a .NET-wrapper for some unmanaged API. In my project, I am currently dealing with a hard-to-trace bug - it seems instances of MyData are getting garbage-collected while the method DoSomething() is still executing. Here is what I believe happens:

  1. The static method Test() is called, which creates an instance of MyData
  2. The instance method DoSomething() is called
  3. DoSomething() calls a native method
  4. While (!) the native method is being executed, the Garbage Collector collects the instance of MyData and calls the finalizer
  5. The finalizer deletes pobj, which is used by the native method which is still executing
  6. Well... the program crashes

My two questions:

  1. Is the scenario as described above actually possible? I can't quite get over the fact that an object is able to get finalized while an instance method is still executing...
  2. What is a good way to fix this problem (i.e. prevent the object from being collected until it is "safe" to do so)? Modifying any instance variable after the native method has executed does the trick, but it feels kind of ad-hoc.

EDIT (some additional information): the code crashes with an AccessViolationException which bubbles up from the native method, and can be caught by try..catch in Test(). For debugging, I inserted a couple of logging calls, which reveal the following execution order:

  1. Native method is entered (but never exited)
  2. The finalizer is executed
  3. The exception is caught
Claudio P.
  • 31
  • 2
  • Do you know for sure that the object has been garbage collected? – Matt Smith Feb 16 '12 at 21:21
  • It doesn't sound plausible to me that the instance of MyData would be collected. After all it is rooted in the Test method. What does the exception say? – Brian Rasmussen Feb 16 '12 at 21:55
  • @Matt: I edited the post to add some information – Claudio P. Feb 16 '12 at 23:50
  • 1
    @Brian: It seems the garbage collector is allowed to collect an object if no execution path will address it again - even if it is not yet "out of scope". FWIW, the problem only occurs with optimizations enabled – Claudio P. Feb 16 '12 at 23:53
  • 1
    Yes, the garbage collector certainly can be this aggressive. But no, the *this* reference in the DoSomething() method should be found by the garbage collector. This hinges somewhere between the snippet not being an accurate rendition of the real code and the native code somehow screwing up the managed stack frame. – Hans Passant Feb 17 '12 at 00:19
  • @Hans: could you elaborate? Can you positively assure that an object an instance method of which is currently executing will under no circumstances be collected? – Claudio P. Feb 17 '12 at 08:33
  • 1
    I can't assure anything with a broken code snippet that can't compile. Adding GC::KeepAlive(data) to the bottom of the method would be a workaround. – Hans Passant Feb 17 '12 at 09:38
  • Same question/answer: http://stackoverflow.com/q/4366779/321013 – Martin Ba Apr 11 '14 at 21:28

1 Answers1

0

You should add GC::KeepAlive(data) to the bottom of the Test() method.

Be aware that if CallToNativeMethod invokes the call onto another thread and returns immediately, then the managed data object and GC reference would fall out of scope pretty quickly and is then at risk of being garbage collected again. In that case you would need to create some kind of notification code that calls KeepAlive and until it gets a signal.

Gareth
  • 439
  • 5
  • 12