From: <dan...@ya...> - 2001-10-11 01:53:17
|
Please comment on this: I won't submit it to SF patch tracker yet because it will get ignored there. atexit currently does not work when called from a dll. dllcrt1.c includes a dummy atexit to ensure that it doesn't cause serious problems Here is a test case: ================================================================ /* the dll: dll_exit.c */ #include <stdlib.h> #include <stdio.h> void goodbye1( void ); void goodbye2( void ); void goodbye3( void ); void goodbye4( void ); void goodbye5( void ); void hmm( void ); __declspec(dllexport) void dll_says_goodbye ( void ) { int i; atexit( goodbye1 ); atexit( goodbye2 ); atexit( goodbye3 ); atexit( goodbye4 ); atexit( goodbye5 ); /* see if dllonexit can realloc the table beyond 32 */ for (i=1; i<= 40; i++) atexit( hmm ); printf( "In dll\n" ); } void goodbye1() { printf( "atexit.\n" ); } void goodbye2() { printf( "dll's " ); } void goodbye3() { printf( "from " ); } void goodbye4() { printf( "bye " ); } void goodbye5() { printf( "Good" ); } void hmm(){ putchar('.'); } ================================================================ /* the app: test_atexit.c */ #include <stdlib.h> #include <stdio.h> __declspec(dllimport) void dll_says_goodbye( void ); void main_is_exiting(void){ printf("Will the dll say goodbye, please?\n"); } int main() { atexit(main_is_exiting); printf("In main\n"); dll_says_goodbye(); printf("Back in main\n"); return 0; } ================================================================ Build it: gcc -Wall -shared -o dll_exit.dll -Wl,--out-implib,libdll_atext.a dll_exit.c gcc -Wall -o test_atexit.exe test_atexit.c -L. -ldll_atext The atexit functions in the dll are ignored. The attached patch fixes: 1) Only export atexit from libmsvcrt.a as _imp__atexit. Do this by pretending its data. 2) When building *.exe, link atexit aginst this libmsvcrt.a symbol. This is done by defining atexit in crt1.c as a thunk for _imp__atexit 3) When building *.dll, initialise a private atexit table at startup (in dllcrt1.c) and fill it up by making atexit a wrapper for the __dllonexit function exported from msvcrt.dll. Unlike atexit, __dllonexit takes arguments that specify the head and tail of the table. When detaching from process, we run the functions registered in this private table. The table is allocated on the heap to size 32 as per ANSI. I tried putting on stack (as is done in libiberty xatexit.c), but then __dllonexit fails after 32 calls The alternative solution would be to use the xatexit from libiberty, but that would need some thread safety additions. I presume that the MS version of __dllonexit takes care of thread locking. The hint for this was when I grepped my MS Office folder for at/onexit. - it was only found in *.exe files. Looking in the dll files, I found __dllonexit instead. Searching for dllonexit on web, I found only Wine project docs, which had code showing what dllonexit is supposed to do. I haven't tried this with --enable-auto-import. It could disrupt the logic of using atexit/_imp__atexit as a switch controlling which table to use. It shouldn't , but I haven't tested. Here is the diff: diff -u -p ../../mingw-1.1-cvs/runtime/crt1.c ./crt1.c --- ../../mingw-1.1-cvs/runtime/crt1.c Tue Jun 5 12:26:30 2001 +++ ./crt1.c Thu Oct 11 12:46:17 2001 @@ -40,6 +40,7 @@ * crt1.c and dllcrt1.c. This means changes in the code don't have to * be manually synchronized, but it does lead to this not-generally- * a-good-idea use of include. */ + #include "init.c" extern int main (int, char **, char **); @@ -61,6 +62,11 @@ __MINGW_IMPORT void __set_app_type(int); */ extern unsigned int _CRT_fmode; +typedef void (* p_atexit_fn )(void); +/* We force use of library version of atexit, which is only + visible as _imp__atexit. Declare it */ +extern int (*_imp__atexit)( void (*)(void) ) ; + static void _mingw32_init_fmode () { @@ -230,5 +236,12 @@ WinMainCRTStartup () __set_app_type (__GUI_APP); #endif __mingw_CRTStartup (); +} + +/* we use the global atexit table in *.exe apps */ + +int atexit (void (* func )(void) ) +{ + return ( (*_imp__atexit)(func) ); } diff -u -p ../../mingw-1.1-cvs/runtime/dllcrt1.c ./dllcrt1.c --- ../../mingw-1.1-cvs/runtime/dllcrt1.c Tue Jun 5 12:26:30 2001 +++ ./dllcrt1.c Thu Oct 11 13:15:21 2001 @@ -25,7 +25,7 @@ * $Date: 2001/06/05 00:26:30 $ * */ - +#include <stdlib.h> #include <stdio.h> #include <io.h> #include <process.h> @@ -40,6 +40,15 @@ extern void __main (); extern void __do_global_dtors (); #endif +typedef void (* p_atexit_fn )(void); +p_atexit_fn* first_atexit; +p_atexit_fn* last_atexit; + +/* This prototype is based on the function in the Wine project's exit.c */ +extern +__declspec(dllimport) +p_atexit_fn __dllonexit(p_atexit_fn, p_atexit_fn**, p_atexit_fn**); + extern BOOL WINAPI DllMain (HANDLE, DWORD, LPVOID); BOOL WINAPI @@ -49,6 +58,13 @@ DllMainCRTStartup (HANDLE hDll, DWORD dw if (dwReason == DLL_PROCESS_ATTACH) { + /* Initialize private atexit table for this dll + 32 is min size required by ANSI */ + if ( (first_atexit = (p_atexit_fn*) malloc (32 * sizeof (p_atexit_fn))) == NULL ) + return FALSE; /* failed to allocate memory */ + *first_atexit = NULL; + last_atexit = first_atexit; + #ifdef __GNUC__ /* From libgcc.a, calls global class constructors. */ __main (); @@ -66,7 +82,23 @@ DllMainCRTStartup (HANDLE hDll, DWORD dw if (dwReason == DLL_PROCESS_DETACH) { /* From libgcc.a, calls global class destructors. */ - __do_global_dtors (); + /* Not necessary since __do_global_dtors is now registered + * at head of private atexit table by __do_global_ctors */ + /* __do_global_dtors (); */ + /* run (LIFO) terminators registered in private atexit table */ + + if (first_atexit) + { + p_atexit_fn* _last = last_atexit-1; + while ( _last >= first_atexit ) + { + if ( *_last != NULL ) + ( **_last ) (); /* do it */ + _last--; + } + free ( first_atexit ) ; /* done with this process */ + first_atexit = NULL ; + } } #endif @@ -74,16 +106,21 @@ DllMainCRTStartup (HANDLE hDll, DWORD dw } /* - * For the moment a dummy atexit. Atexit causes problems in DLLs, especially - * if they are dynamically loaded. For now atexit inside a DLL does nothing. - * NOTE: We need this even if the DLL author never calls atexit because - * the global constructor function __do_global_ctors called from __main - * will attempt to register __do_global_dtors using atexit. - * Thanks to Andrey A. Smirnov for pointing this one out. + * The atexit exported from msvcrt.dll causes problems in DLLs, especially + * if they are dynamically loaded. Dll's built with MSVC++ do not import + * atexit, but link instead against the msvcrt.dll export __dllonexit. + * Here, we override the exported version of atexit with one that passes the + * private table initialised in DllMainCRTStartup to __dllonexit. */ int -atexit (void (*pfn) ()) +atexit (p_atexit_fn pfn ) { - return 0; + return ( __dllonexit(pfn, &first_atexit, &last_atexit) == NULL ? -1 : 0 ); } +/* + * Now we have a problem with duplicate definitions of atexit, one here and + * one in the runtime lib. The workaround is to pretend that the atexit symbol + * in the dll is DATA and only export _imp__atexit. See crt1.c for how the + * startup code for an .exe app handles this + */ diff -u -p ../../mingw-1.1-cvs/runtime/msvcrt.def ./msvcrt.def --- ../../mingw-1.1-cvs/runtime/msvcrt.def Tue Jun 5 12:26:30 2001 +++ ./msvcrt.def Thu Oct 11 11:30:00 2001 @@ -546,7 +546,7 @@ asctime asin atan atan2 -atexit +atexit DATA atof atoi atol http://briefcase.yahoo.com.au - Yahoo! Briefcase - Manage your files online. |