Mixed-mode C++/CLI DLL throws exception on exit
Asked Answered
T

3

10

I am having a problem with a C++/CLI mixed mode DLL that I created. It is throwing an exception when unloading as the .NET application that uses it exits. After DLL_PROCESS_DETACH is executed, the DLL does runtime clean-up using automatically registered atexit() / __onexit() functions and throws the following exception:

Unhandled exception at 0x752bb9bc (KernelBase.dll) in psstestm.exe: 
0xC0020001: The string binding is invalid.

I've traced the problem to an atexit() call which is registered by a static boost exception object get_static_exception_object().

    function_to_call    0x0f560410 _t2m@???__Fep@?1???$get_static_exception_object@Ubad_exception_@exception_detail@boost@@@exception_detail@boost@@YA?AVexception_ptr@1@XZ@YAXXZ@?A0x0a546e27@@YAXXZ   void (void)*

I am using boost_1_47 statically linked for the most part except boost::thread which is dynamically linked to avoid loaderlock. I've also tried dynamically linking all of boost which didn't help. Also all of the boost includes are surrounded by #pragma unmanaged blocks.

I'm hoping someone has had a similar problem or knows of a solution?

Thanks, Mark

Here is the call stack just before the exception occurs:

psscorem.dll!_CRT_INIT(void * hDllHandle=0x0f4b0000, unsigned long dwReason=0, void * lpreserved=0x00000001)  Line 413  C
psscorem.dll!__DllMainCRTStartup(void * hDllHandle=0x0f4b0000, unsigned long dwReason=0, void * lpreserved=0x00000001)  Line 526 + 0x11 bytes   C
psscorem.dll!_DllMainCRTStartup(void * hDllHandle=0x0f4b0000, unsigned long dwReason=0, void * lpreserved=0x00000001)  Line 476 + 0x11 bytes    C
mscoreei.dll!__CorDllMain@12()  + 0xde bytes    
mscoree.dll!_ShellShim__CorDllMain@12()  + 0xad bytes   
ntdll.dll!_LdrpCallInitRoutine@16()  + 0x14 bytes   
ntdll.dll!_LdrShutdownProcess@0()  + 0x141 bytes    
ntdll.dll!_RtlExitUserProcess@4()  + 0x74 bytes 
kernel32.dll!749479f5()     
mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes()  + 0xc8 bytes 
mscoreei.dll!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit()  + 0x15 bytes    
clr.dll!EEPolicy::ExitProcessViaShim()  + 0x66 bytes    
clr.dll!SafeExitProcess()  + 0x99 bytes 
clr.dll!DisableRuntime()  - 0x1146bb bytes  
clr.dll!EEPolicy::HandleExitProcess()  + 0x57 bytes 
clr.dll!__CorExeMainInternal@0()  + 0x11c bytes 
clr.dll!__CorExeMain@0()  + 0x1c bytes  
mscoreei.dll!__CorExeMain@0()  + 0x38 bytes 
mscoree.dll!_ShellShim__CorExeMain@0()  + 0x227 bytes   
mscoree.dll!__CorExeMain_Exported@0()  + 0x8 bytes  
kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes    
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes   
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes    
Tangier answered 15/11, 2011 at 23:28 Comment(0)
S
10

I have faced the same problem and managed to track it down to the following function in exception_ptr.hpp:

    template <class Exception>
    exception_ptr
    get_static_exception_object()
        {
        Exception ba;
        exception_detail::clone_impl<Exception> c(ba);
        c <<
            throw_function(BOOST_CURRENT_FUNCTION) <<
            throw_file(__FILE__) <<
            throw_line(__LINE__);
        static exception_ptr ep(shared_ptr<exception_detail::clone_base const>(new exception_detail::clone_impl<Exception>(c)));
        return ep;
        }

The problematic part here is: static exception_ptr ep(...

You can just remove static and it should work:

    template <class Exception>
    exception_ptr
    get_static_exception_object()
        {
        Exception ba;
        exception_detail::clone_impl<Exception> c(ba);
        c <<
            throw_function(BOOST_CURRENT_FUNCTION) <<
            throw_file(__FILE__) <<
            throw_line(__LINE__);
        exception_ptr ep(shared_ptr<exception_detail::clone_base const>(new exception_detail::clone_impl<Exception>(c)));
        return ep;
        }

Note how this function is used, it assigns returned static variable to another static variable. The whole implementation of this function looks suspicious, probably I will raise a question on boost support about this.

There are could be other workarounds to fix this issue. More analysis on static variables in mixed assemblies might be found here: http://derevyanko.blogspot.com/2009/01/clic.html however only in russian.

Sheaff answered 22/2, 2012 at 12:26 Comment(1)
THANK YOU!!! I've been tracking this down forever. I don't know C++ well enough to understand how that causes a call to atexit.Celluloid
A
0

As described in a post on the Boost mailing list, one approach is to separate the managed and unmanaged code into separate translations units (.cpp files and the headers they #include). Only reference Boost from the unmanaged translations units. Only turn on /clr for the managed translation units.

Aircondition answered 14/1, 2013 at 15:26 Comment(0)
U
0

You can add the lines :

#if _MANAGED
#error "Don't include that file in CLI compilation units. It will cause failure when cleaning the static objects of the managed dll"
#endif

before get_static_exception_object declaration and not include that file only (or a boost header which include that file) in your cli files.

For me replacing one boost/thread.hpp by boost/thread/thread.hpp fixed the issue.

Underling answered 5/4, 2016 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.