Exception 0xc0020001 in C++/CLI assembly

After reorganising some code in a C++/CLI assembly, I started getting exception “0xc0020001: The string binding is invalid” when shutting down the C# application that loaded that assembly.

When the program was run under the debugger, it would throw the exception from a function in crtdll.c that was processing the DLL_PROCESS_DETACH notification sent to DllMain. The error occurred when attempting to call the function pointer function_to_call (on line 444).

      437 /* cache the function to call. */
      438 function_to_call = (_PVFV)_decode_pointer(*onexitend);
      439
      440 /* mark the function pointer as visited. */
      441 *onexitend = (_PVFV)_encoded_null();
      442
      443 /* call the function, which can eventually change __onexitbegin and __onexitend */
      444 (*function_to_call)();
      445
      446 onexitbegin_new = (_PVFV *)_decode_pointer(__onexitbegin);
      447 onexitend_new = (_PVFV *)_decode_pointer(__onexitend);

Here’s where a feature of the Visual Studio debugger that I hadn’t seen before came in very handy. If I set a breakpoint on line 444 and simply hovered my mouse over function_to_call, the debugger tooltip showed the full decorated name of the function, in this case, “_t2m@???__FstaticNativeObject@?1??NativeM ethod@@YAHH@Z@YAXXZ@?A0x754dd9c9@@YAXXZ”.

Chris Brumme explains error C0020001 and identifies one of the causes as “trying to call into managed code … after the runtime has started shutting down”. According to a forum post (about this same error), “t2m” stands for “transition to managed”. The information in the decorated function name (“staticNativeObject” and “NativeMethod”) was enough to piece together the rest of the puzzle. I had written code much like the following:

    #pragma unmanaged
    class NativeClass
    {
    public:
        NativeClass() { }
        ~NativeClass() { }
    };
    bool NativeMethod()
    {
        static NativeClass staticNativeObject;
        return true;
    }

Even though NativeMethod is emitted as native code, the disassembly showed that it registers a managed entry point for the NativeClass destructor (for staticNativeObject) with the atexit function. But by the time atexit ran this destructor (from DllMain when the C++/CLI assembly was unloaded), the CLR had already started shutting down, and the function call failed.

This problem can be solved by removing the static variable. Either make it non-static, or move it to class or file (or global!) scope. (Slightly more complex workarounds may, of course, be necessary depending on the expense or difficulty of initialising the object.)

It seems like the compiler is emitting incorrect code here–it should register a native entry point for the destructor (or call the managed version from AppDomain.DomainUnloaded) instead–so I filed a bug report with Microsoft Connect on this problem.

Posted by Bradley Grainger on April 09, 2008