sistemas segun peso para organizacion mercancias materiales logistica ligeras definicion cargas carga capacidad calculo almacenes almacenamiento .net windows c++-cli clr mixed-mode

.net - segun - Destrucción de objetos nativos con duración de almacenamiento estático



organizacion de almacenes (2)

2012-12-09 Resumen:

  • En una aplicación de modo mixto normal, los destructores nativos globales de C ++ se ejecutan como finalizadores. No es posible cambiar ese comportamiento o el tiempo de espera asociado.
  • Una DLL de ensamblaje de modo mixto ejecuta constructores / destructores de C ++ durante la carga / descarga de DLL, exactamente como una DLL nativa.
  • Alojar el CLR en un ejecutable nativo utilizando la interfaz COM permite que los deconstructores se comporten como en una DLL nativa (el comportamiento que deseo) y establecer el tiempo de espera para los finalizadores (una ventaja adicional).
  • Por lo que sé, lo anterior se aplica al menos a Visual Studio 2008, 2010 y 2012. (Solo probado con .NET 4)

El ejecutable de alojamiento CLR real que planeo usar es muy similar al descrito en esta pregunta, excepto por algunos cambios menores:

  • Configuración de OPR_FinalizerRun en algún valor (60 segundos actualmente, pero sujeto a cambios) según lo sugerido por Hans Passant.
  • Uso de las clases de puntero inteligente COM de ATL (no están disponibles en las ediciones Express de Visual Studio, por lo que las omití en esta publicación).
  • Lodaing CLRCreateInstance desde mscoree.dll dinámicamente (para permitir mejores mensajes de error cuando no se instala un CLR compatible).
  • Pasando la línea de comando desde el host a la función Main designada en la DLL de ensamblaje.

Gracias a todos los que se tomaron el tiempo de leer la pregunta y / o comentar.

2012-12-02 Actualización al final del post.

Estoy trabajando en una aplicación de modo mixto C ++ / CLI usando Visual Studio 2012 con .NET 4 y me sorprendió descubrir que no se llamaba a los destructores para algunos de los objetos globales nativos. Al investigar el problema, resulta que se comportan como objetos gestionados como se explica en esta publicación .

Me sorprendió bastante este comportamiento (lo comprendo para los objetos administrados) y no lo encontré documentado en ninguna parte, ni en el estándar C ++ / CLI ni en la descripción de los destructores y finalizadores .

Siguiendo la sugerencia de un comentario de Hans Passant , compilé los programas como una DLL de ensamblaje y los alojé en un pequeño archivo ejecutable nativo, lo que me da el comportamiento deseado (los destructores tienen suficiente tiempo para terminar y ejecutarse en el mismo hilo que tenían). construido)!

Mis preguntas:

  1. ¿Puedo obtener el mismo comportamiento en un ejecutable independiente?
  2. Si (1) no es factible, ¿es posible configurar la política de tiempo de espera del proceso (es decir, básicamente llamar a ICLRPolicyManager->SetTimeout(OPR_ProcessExit, INFINITE) ) para el ejecutable? Esta sería una solución aceptable.
  3. ¿Dónde está documentado / cómo puedo informarme más sobre el tema? Prefiero no confiar en un comportamiento que pueda cambiar.

Para reproducir compilar los siguientes archivos de la siguiente manera:

cl /EHa /MDd CLRHost.cpp cl /EHa /MDd /c Native.cpp cl /EHa /MDd /c /clr CLR.cpp link /out:CLR.exe Native.obj CLR.obj link /out:CLR.dll /DLL Native.obj CLR.obj

Comportamiento no deseado:

C:/Temp/clrhost>clr.exe [1210] Global::Global() [d10] Global::~Global() C:/Temp/clrhost>

Ejecución alojada:

C:/Temp/clrhost>CLRHost.exe clr.dll [1298] Global::Global() 2a returned. [1298] Global::~Global() [1298] Global::~Global() - Done! C:/Temp/clrhost>

Archivos utilizados:

// CLR.cpp public ref class T { static int M(System::String^ arg) { return 42; } }; int main() {} // Native.cpp #include <windows.h> #include <iostream> #include <iomanip> using namespace std; struct Global { Global() { wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::Global()" << endl; } ~Global() { wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::~Global()" << endl; Sleep(3000); wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::~Global() - Done!" << endl; } } g; // CLRHost.cpp #include <windows.h> #include <metahost.h> #pragma comment(lib, "mscoree.lib") #include <iostream> #include <iomanip> using namespace std; int wmain(int argc, const wchar_t* argv[]) { HRESULT hr = S_OK; ICLRMetaHost* pMetaHost = 0; ICLRRuntimeInfo* pRuntimeInfo = 0; ICLRRuntimeHost* pRuntimeHost = 0; wchar_t version[MAX_PATH]; DWORD versionSize = _countof(version); if (argc < 2) { wcout << L"Usage: " << argv[0] << L" <assembly.dll>" << endl; return 0; } if (FAILED(hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost)))) { goto out; } if (FAILED(hr = pMetaHost->GetVersionFromFile(argv[1], version, &versionSize))) { goto out; } if (FAILED(hr = pMetaHost->GetRuntime(version, IID_PPV_ARGS(&pRuntimeInfo)))) { goto out; } if (FAILED(hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost)))) { goto out; } if (FAILED(hr = pRuntimeHost->Start())) { goto out; } DWORD dwRetVal = E_NOTIMPL; if (FAILED(hr = pRuntimeHost->ExecuteInDefaultAppDomain(argv[1], L"T", L"M", L"", &dwRetVal))) { wcerr << hex << hr << endl; goto out; } wcout << dwRetVal << " returned." << endl; if (FAILED(hr = pRuntimeHost->Stop())) { goto out; } out: if (pRuntimeHost) pRuntimeHost->Release(); if (pRuntimeInfo) pRuntimeInfo->Release(); if (pMetaHost) pMetaHost->Release(); return hr; }

2012-12-02 :
Por lo que puedo decir, el comportamiento parece ser el siguiente:

  • En un archivo EXE de modo mixto, los destructores globales se ejecutan como finalizadores durante la descarga de dominio, independientemente de si se colocan en código nativo o código CLR . Este es el caso en Visual Studio 2008, 2010 y 2012.
  • En una DLL de modo mixto alojada por un destructor de aplicaciones nativas, los objetos nativos globales se ejecutan durante DLL_PROCESS_DETACH una vez que el método administrado se haya ejecutado y se haya producido el resto de la limpieza . Se ejecutan en el mismo subproceso que el constructor y no hay tiempo de espera asociado con ellos (el comportamiento deseado). Como era de esperar, los destructores de tiempo de los objetos administrados globales (clases no-ref ubicadas en archivos compilados con /clr ) se pueden controlar usando ICLRPolicyManager->SetTimeout(OPR_ProcessExit, <timeout>) .

Haciendo una conjetura, creo que la razón por la cual los constructores / destructores nativos globales funcionan "normalmente" (definido como comportarse como yo esperaría) en el escenario DLL es permitir el uso de LoadLibrary y GetProcAddress en funciones nativas. Por lo tanto, espero que sea relativamente seguro confiar en que no cambie en el futuro previsible, pero agradecería tener algún tipo de confirmación / negación de fuentes o documentación oficial de cualquier manera.

Actualización 2 :

En Visual Studio 2012 (probado con las versiones Express y Premium, desafortunadamente no tengo acceso a versiones anteriores en esta máquina). Debería funcionar de la misma manera en la línea de comandos (compilación como se describe anteriormente), pero aquí se explica cómo reproducir desde el IDE.

Construyendo CLRHost.exe:

  1. Archivo -> Nuevo Proyecto
  2. Visual C ++ -> Win32 -> Aplicación de consola Win32 (Nombre del proyecto "CLRHost")
  3. Configuración de la aplicación -> Opciones adicionales -> Proyecto vacío
  4. Presiona "Terminar"
  5. Haga clic derecho en Archivos de origen en el explorador de soluciones. Añadir -> Nuevo elemento -> Visual C ++ -> Archivo C ++. Nombra CLRHost.cpp y pega el contenido de CLRHost.cpp de la publicación.
  6. Proyecto -> Propiedades. Propiedades de configuración -> C / C ++ -> Generación de código -> Cambiar "Habilitar excepciones de C ++" a "Sí con excepciones SEH (/ EHa)" y "Verificaciones básicas de tiempo de ejecución" a "Predeterminado"
  7. Construir.

Edificio CLR.DLL:

  1. Archivo -> Nuevo Proyecto
  2. Visual C ++ -> CLR -> Class Library (Nombre del proyecto "CLR")
  3. Eliminar todos los archivos autogenerados.
  4. Proyecto -> Propiedades. Propiedades de configuración -> C / C ++ -> Encabezados precompilados -> Encabezados compilados previamente. Cambie a "No usar encabezados precompilados".
  5. Haga clic derecho en Archivos de origen en el explorador de soluciones. Añadir -> Nuevo elemento -> Visual C ++ -> Archivo C ++. Póngale un nombre CLR.cpp y pegue el contenido de CLR.cpp de la publicación.
  6. Agregue un nuevo archivo C ++ llamado Native.cpp y pegue el código de la publicación.
  7. Haga clic derecho en "Native.cpp" en el explorador de soluciones y seleccione las propiedades. Cambie C / C ++ -> General -> Soporte de Common Language RunTime a "Sin soporte de Common Language RunTime"
  8. Proyecto -> Propiedades -> Depuración. Cambie "Comando" para que apunte a CLRhost.exe, "Argumentos del comando" a "$ (TargetPath)" incluyendo las comillas, "Tipo de depurador" a "Mezclado"
  9. Construir y depurar.

La colocación de un punto de interrupción en el destructor de Global proporciona el siguiente seguimiento de pila:

> clr.dll!Global::~Global() Line 11 C++ clr.dll!`dynamic atexit destructor for ''g''''() + 0xd bytes C++ clr.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 416 C clr.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 522 + 0x11 bytes C clr.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 472 + 0x11 bytes C mscoreei.dll!__CorDllMain@12() + 0x136 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!74e37a0d() mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes() + 0x10e bytes mscoreei.dll!_CorExitProcess@4() + 0x27 bytes mscoree.dll!_ShellShim_CorExitProcess@4() + 0x94 bytes msvcr110d.dll!___crtCorExitProcess() + 0x3a bytes msvcr110d.dll!___crtExitProcess() + 0xc bytes msvcr110d.dll!__unlockexit() + 0x27b bytes msvcr110d.dll!_exit() + 0x10 bytes CLRHost.exe!__tmainCRTStartup() Line 549 C CLRHost.exe!wmainCRTStartup() Line 377 C kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes

Al ejecutarse como un ejecutable independiente, obtengo un seguimiento de pila que es muy similar al observado por Hans Passant (aunque no está usando la versión administrada del CRT):

> clrexe.exe!Global::~Global() Line 10 C++ clrexe.exe!`dynamic atexit destructor for ''g''''() + 0xd bytes C++ msvcr110d.dll!__unlockexit() + 0x1d3 bytes msvcr110d.dll!__cexit() + 0xe bytes [Managed to Native Transition] clrexe.exe!<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void* cookie) Line 577 C++ clrexe.exe!<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain() Line 594 + 0x8 bytes C++ clrexe.exe!<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^ source, System::EventArgs^ arguments) Line 628 C++ clrexe.exe!<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^ source, System::EventArgs^ arguments) Line 273 + 0x6e bytes C++ kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes


Conseguir las preguntas fáciles fuera del camino primero:

Un buen recurso para la personalización de CLR es el libro de Steven Pratschner "Personalización del Common Language Runtime de Microsoft .NET Framework". Tenga en cuenta que está desactualizado, las interfaces de alojamiento han cambiado en .NET 4.0. MSDN no dice mucho al respecto, pero las interfaces de alojamiento están bien documentadas.

Puede simplificar la depuración cambiando la configuración de un depurador, cambie el Tipo de "Automático" a "Administrado" o "Mezclado".

Tenga en cuenta que su sueño de 3000 ms está justo en el borde, debe probar con 5000 ms. Si la clase de C ++ aparece en el código que está compilado con / clr en efecto, incluso con #pragma no administrado en efecto , tendrá que anular el tiempo de espera del subproceso finalizador. Probado en la versión CLR de .NET 3.5 SP1, el siguiente código funcionó bien para darle al destructor el tiempo suficiente para que se ejecute hasta su finalización:

ICLRControl* pControl; if (FAILED(hr = pRuntimeHost->GetCLRControl(&pControl))) { goto out; } ICLRPolicyManager* pPolicy; if (FAILED(hr = pControl->GetCLRManager(__uuidof(ICLRPolicyManager), (void**)&pPolicy))) { goto out; } hr = pPolicy->SetTimeout(OPR_FinalizerRun, 60000); pPolicy->Release(); pControl->Release();

Elegí un minuto como un tiempo razonable, modificándolo según sea necesario. Tenga en cuenta que la documentación de MSDN tiene un error, no muestra OPR_FinalizerRun como un valor permitido pero, de hecho, funciona correctamente. Establecer el tiempo de espera del subproceso del finalizador también garantiza que un finalizador administrado no se agote cuando se destruye indirectamente una clase de C ++ no administrada, un escenario muy común.

Una cosa que verá cuando ejecute este código con CLRHost compilado con / clr es que la llamada a GetCLRManager () fallará con un código de retorno HOST_E_INVALIDOPERATION. El host CLR predeterminado que se cargó para ejecutar su CLRHost.exe no le permitirá anular la política. Así que estás bastante atascado con tener un EXE dedicado para alojar el CLR.

Cuando probé esto haciendo que CLRHost cargara un ensamblaje de modo mixto, la pila de llamadas tenía este aspecto al establecer un punto de interrupción en el destructor:

CLRClient.dll!Global::~Global() Line 24 C++ [Managed to Native Transition] CLRClient.dll!<Module>.?A0x789967ab.??__Fg@@YMXXZ() + 0x1b bytes CLRClient.dll!_exit_callback() Line 449 C++ CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void* cookie = <undefined value>) Line 753 C++ CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain() Line 775 + 0x8 bytes C++ CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^ source = 0x027e1274, System::EventArgs^ arguments = <undefined value>) Line 808 C++ msvcm90d.dll!<CrtImplementationDetails>.ModuleUninitializer.SingletonDomainUnload(object source = {System.AppDomain}, System.EventArgs arguments = null) + 0xa1 bytes // Rest omitted

Tenga en cuenta que esto es diferente a sus observaciones en su pregunta. El código es activado por la versión administrada del CRT (msvcm90.dll). Y este código se ejecuta en un hilo dedicado, iniciado por el CLR para descargar un dominio de aplicación. Puede ver el código fuente para esto en el archivo de código fuente vc / crt / src / mstartup.cpp.

El segundo escenario se produce cuando la clase C ++ forma parte de un archivo de código fuente que se compila sin / clr en efecto y se vincula al ensamblaje en modo mixto. El compilador luego usa el controlador atexit () normal para llamar al destructor, como lo hace normalmente en un ejecutable no administrado. En este caso, cuando Windows descarga la DLL al finalizar el programa y se apaga la versión administrada del CRT.

Notable es que esto sucede después de que el CLR se apaga y que el destructor se ejecuta en el hilo de inicio del programa. En consecuencia, los tiempos de espera de CLR están fuera de la imagen y el destructor puede tomar todo el tiempo que quiera. La esencia de la traza de pila es ahora:

CLRClient.dll!Global::~Global() Line 12 C++ CLRClient.dll!`dynamic atexit destructor for ''g''''() + 0xd bytes C++ // Confusingly named functions elided //... CLRHost.exe!__crtExitProcess(int status=0x00000000) Line 732 C CLRHost.exe!doexit(int code=0x00000000, int quick=0x00000000, int retcaller=0x00000000) Line 644 + 0x9 bytes C CLRHost.exe!exit(int code=0x00000000) Line 412 + 0xd bytes C // etc..

Sin embargo, este es un caso de esquina que solo ocurrirá cuando el EXE de inicio no esté administrado. Tan pronto como se administre el EXE, ejecutará los destructores en AppDomain.Unload, incluso si aparecen en el código que se compiló sin / clr. Así que todavía tienes el problema del tiempo de espera. Tener un EXE no administrado no es muy inusual, esto ocurrirá, por ejemplo, cuando cargue el código administrado [ComVisible]. Pero eso no suena como tu escenario, estás atrapado con CLRHost.


Para responder a la pregunta "¿Dónde está documentado / cómo puedo informarme más sobre el tema?" pregunta: puede entender cómo funciona esto (o se usa para trabajar al menos para el marco 2) si descarga y verifica la Infraestructura de lenguaje común de fuente compartida (también conocida como SSCLI) desde aquí http://www.microsoft.com/en-us/download/details.aspx?id=4917 .

Una vez que haya extraído los archivos, encontrará en gcEE.ccp ("motor de ejecución de recolección de basura") esto:

#define FINALIZER_TOTAL_WAIT 2000

que define este famoso valor predeterminado de 2 segundos También en el mismo archivo verás esto:

BOOL GCHeap::FinalizerThreadWatchDogHelper() { // code removed for brevity ... DWORD totalWaitTimeout; totalWaitTimeout = GetEEPolicy()->GetTimeout(OPR_FinalizerRun); if (totalWaitTimeout == (DWORD)-1) { totalWaitTimeout = FINALIZER_TOTAL_WAIT; }

Eso le indicará que el motor de ejecución obedecerá la política OPR_FinalizerRun , si está definida, que corresponde al valor en la enumeración EClrOperation . GetEEPolicy se define en eePolicy.h & eePolicy.cpp .