TN058: Implementación de MFC módulo Estado

Esta nota técnica describe la implementación de MFC "módulo Estado" construcciones. La comprensión de la aplicación del Estado de módulo es crítica para utilizar la MFC compartido archivos DLL desde un archivo DLL (o servidor OLE en proceso).

Antes de leer esta nota, consulte "Administrar el estado datos de MFC módulos" en crear nuevos documentos, vistas y ventanas, en la Guía del programador de Visual C++. Este artículo contiene importante información del uso y obtener información general sobre este tema.

Visión general

Hay tres tipos de información de estado MFC: estado de módulo, proceso y estado de los subprocesos. A veces se pueden combinar estos tipos de Estado. Por ejemplo, los mapas de identificador de MFC son módulo local y local de subproceso. Esto permite dos módulos diferentes tener mapas diferentes en cada una de sus hebras.

Estado de proceso y subproceso son similares. Estos elementos de datos son cosas que han sido tradicionalmente las variables globales, pero se necesitan ser específica para un determinado proceso o subproceso para Win32s adecuado apoyo o soporte adecuado de subprocesamiento múltiple. Qué categoría encaja un dato determinado depende de ese elemento y su semántica deseada con respecto a límites de proceso y subproceso.

Módulo de Estado es el único que puede contener el estado verdaderamente global o Estado que es local de proceso o subproceso local. También puede conmutarse rápidamente.

Estado del módulo de conmutación

Cada subproceso contiene un puntero para el Estado "actual" o "activo" módulo (no en vano, el puntero es parte del estado local de subproceso de MFC). Este puntero se cambia cuando el subproceso de ejecución pasa un límite de módulo, como una aplicación llamada en un Control OLE o DLL o llamando un Control OLE a volver en una aplicación.

El estado actual del módulo se conmuta llamando a AfxSetModuleState. En su mayor parte, nunca tratar directamente con la API. MFC, en muchos casos, llama para usted (en WinMain, OLE puntos de entrada, AfxWndProc, etc.). Esto se hace en cualquier componente escribir vinculando estáticamente en un especial WndProcy un especial WinMain (o DllMain) que sabe qué estado de módulo debe ser actual. Puede ver este código por echar un vistazo a DLLMODUL.CPP o APPMODUL.CPP en el directorio MFC\SRC.

Es raro que desea establecer el estado del módulo y, a continuación, no establece atrás. Mayoría de las veces que desee para "empujar" a su propio módulo de Estado como el actual y luego, después de haber terminado, "pop" nuevo contexto original. Esto se hace por la macro AFX_MANAGE_STATE y la clase especial AFX_MAINTAIN_STATE.

CCmdTarget tiene características especiales para apoyar el cambio de estado de módulo. En particular, un CCmdTarget es que la clase raíz utilizada para automatización OLE y OLE COM puntos de entrada. Como cualquier otro punto de entrada expuestos al sistema, estos puntos de entrada deben establecer el estado del módulo correcto. ¿Cómo un determinado CCmdTarget saber cuál debe ser el estado del módulo "correcto"? La respuesta es que "recuerda" cuál es el Estado "actual" del módulo cuando se construye, que puede establecer el estado actual del módulo para "recordado" llamado valor cuando es posterior. Como resultado, el estado del módulo que está asociado un determinado objeto de CCmdTarget es el estado de módulo que era actual cuando el objeto se construyó. Tomemos un ejemplo sencillo de cargar un servidor INPROC, creando un objeto y llamar a sus métodos.

  1. La DLL está cargada por OLE mediante LoadLibrary.

  2. En primer lugar se denomina RawDllMain . Establece el estado del módulo en el estado conocido módulo estático para la DLL. Por esta razón RawDllMain es vinculada estáticamente con la DLL.

  3. Se llama al constructor de la fábrica de clase asociado con nuestro objeto. COleObjectFactory se deriva de CCmdTarget y como resultado, recuerda en qué estado de módulo fue crear instancias. Esto es importante: cuando se le pregunta la fábrica de clase para crear objetos, ahora sabe qué estado módulo actual.

  4. DllGetClassObject se llama para obtener la fábrica de clase. MFC busca en la lista de fábrica de clase asociada con este módulo y lo devuelve.

  5. Se llama COleObjectFactory::XClassFactory2::CreateInstance . Antes de crear el objeto y devolverlo, esta función establece el estado del módulo en el estado de módulo que era actual en el paso 3 (uno que era actual cuando se crea una instancia del COleObjectFactory ). Esto se realiza dentro de METHOD_PROLOGUE.

  6. Cuando se crea el objeto, también es un derivado de CCmdTarget y de la misma manera COleObjectFactory recordó que estado de módulo estaba activo, por lo que hace este nuevo objeto. Ahora el objeto sabe qué estado de módulo para cambiar a siempre se llama.

  7. El cliente llama a una función en el objeto OLE COM recibió de su llamamiento CoCreateInstance . Cuando el objeto se llama utiliza METHOD_PROLOGUE para cambiar el estado del módulo al igual que hace COleObjectFactory.

Como puede ver, el estado del módulo se propaga de objeto a objeto como se crearon. Es importante que el estado de módulo definir adecuadamente. Si no se establece, el objeto COM o DLL puede interactuar mal con una aplicación MFC que está llamando, o puede ser incapaz de encontrar sus propios recursos o puede fallar en otras formas miserables.

Nota que ciertos tipos de archivos DLL, específicamente "Extensión de MFC" DLL no cambiar el estado del módulo en sus RawDllMain (en realidad, generalmente no tienen incluso una RawDllMain). Esto es porque están destinados a comportarse "como si" fueran realmente presentes en la aplicación que utiliza. Son parte de la aplicación que se ejecuta y es su intención de modificar el estado global de la aplicación.

OLE controles y otros archivos DLL es muy diferente. No quieren modificar el estado de la aplicación llamada; la aplicación que les llama a no puede ser incluso una aplicación MFC y por lo tanto no puede haber ningún Estado a modificar. Esta es la razón por la que el módulo de conmutación de estado fue inventado.

Para funciones exportadas desde un archivo DLL, como uno que inicia un cuadro de diálogo de archivo DLL, debe agregar el código siguiente al principio de la función:

AFX_MANAGE_STATE (AfxGetStaticModuleState ())

Esto cambia el actual estado de módulo con el estado devuelto desde AfxGetStaticModuleState hasta el final del ámbito actual.

Problemas con los recursos en archivos DLL se producen si no se utiliza la macro AFX_MODULE_STATE . De forma predeterminada, MFC utiliza el identificador de recursos de la aplicación principal para cargar la plantilla de recursos. Esta plantilla está realmente almacenada en el archivo DLL. La causa es que la información de estado del módulo de MFC no ha sido cambiado por la macro AFX_MODULE_STATE . El identificador de recurso se recupera de estado de módulo de MFC. El estado del módulo de conmutación no provoca el identificador de recurso mal a utilizarse.

AFX_MODULE_STATE no necesita poner en cada función en la DLL. Por ejemplo, InitInstance puede ser llamado por el código MFC en la aplicación sin AFX_MODULE_STATE porque MFC cambia automáticamente el estado del módulo antes de InitInstance y, a continuación, switches de nuevo después de InitInstance devuelve. Lo mismo es cierto para todos los controladores de mapa de mensajes. Los archivos DLL estándar tiene un procedimiento de ventana principal especial que cambia automáticamente el estado del módulo antes de enrutar cualquier mensaje.

Datos de proceso locales

Locales proceso de datos no sería de esa gran preocupación si no hubiera sido por la dificultad del modelo Win32s DLL. En Win32s todas las DLL compartan sus datos globales, incluso cuando la carga por varias aplicaciones. Esto es totalmente diferente del modelo de datos "real" de DLL de Win32, donde cada DLL obtiene una copia separada de su espacio de datos en cada proceso que otorga a la DLL. Para agregar a la complejidad, datos asignados en el montón en una DLL Win32s están de hecho proceso específico (al menos en cuanto sale de propiedad). Considere el siguiente código y datos:

strGlobal de CString estático; / / en el ámbito de archivo

dllexport void SetGlobalString(LPCTSTR lpsz)
{
   strGlobal = lpsz;
}

dllexport
void GetGlobalString (LPCTSTR lpsz, int cb)
{
   lstrcpyn (lpsz, strGlobal, cb);
}

Veamos lo que ocurre si el código anterior se encuentra en una DLL y que es cargado DLL por dos procesos a y B (de hecho, podrían ser dos instancias de la misma aplicación). Una de las llamadas SetGlobalString("Hello from A") . Como resultado, se asigna memoria para los datos de CString en el contexto del proceso de A. mantener en mente que el CString sí es global y es visible a ambos, A y B. Ahora se llama b GetGlobalString(sz, sizeof(sz)) . B podrá ver los datos de un conjunto. Esto es porque Win32s no ofrece ninguna protección entre procesos como Win32. Por lo es el primer problema; en muchos casos no es deseable tener una aplicación afecta a datos globales es considerados a ser propiedad de una aplicación diferente.

Pero espera, hay más problemas. Supongamos que sale de un ahora. Cuando sale A, la memoria utilizada por el ' strGlobal ' cadena estará disponible para el sistema, es decir, toda la memoria asignada por el proceso se libera automáticamente por el sistema operativo. No es liberado porque se llama al destructor de CString ; no ha sido llamado todavía. Se libera simplemente porque la aplicación asignada lo ha dejado la escena. Ahora si b llamado GetGlobalString(sz, sizeof(sz)) , no se pueden obtener datos válidos. Otra aplicación puede haber utilizado esa memoria para algo más.

Claramente hay un problema aquí. MFC 3.x utilizaba una técnica llamada almacenamiento local de subprocesos (TLS). MFC 3.x asignaría un índice TLS que bajo Win32s realmente actúa como un índice de almacenamiento local de proceso, a pesar de lo no se llama y luego sería hacer referencia todos los datos basados en ese índice TLS. Esto es similar al índice TLS que fue utilizado para almacenar datos de local de subproceso de Win32 (véase a continuación para obtener más información sobre el tema). Esto causó cada DLL de MFC utilizar al menos dos índices TLS por proceso. Cuando cuenta para cargar muchos OLE Control dll (OCX), rápidamente ejecutar fuera de índices TLS (existen sólo 64). Además, MFC tuvo que colocar todos estos datos en un solo lugar, en una sola estructura. No fue muy extensible y no ideal con respecto a su uso de índices TLS.

MFC 4.x resuelve esto con un conjunto de plantillas de clase puede "ajustar" alrededor de los datos que deben ser el proceso local. Por ejemplo, podría fijarse el problema mencionado por escrito:

struct CMyGlobalData: CNoTrackObject pública
{
   CString strGlobal;
};
CProcessLocallt;CMyGlobalData > globalData;

dllexport void SetGlobalString(LPCTSTR lpsz)
{
   globalData - > strGlobal = lpsz;
}

dllexport
void GetGlobalString (LPCTSTR lpsz, int cb)
{
   lstrcpyn (lpsz, globalData - > strGlobal, cb);
}

MFC implementa esto en dos pasos. En primer lugar, es una capa encima de la Win32 Tls * API (TlsAlloc, TlsSetValue, Tls&GetValue, etc.) que utilizan sólo dos índices TLS por proceso, no importa cuántos archivos DLL que tienes. Segundo, el CProcessLocal se proporciona una plantilla para tener acceso a estos datos. Reemplaza operador-gt; que es lo que permite la sintaxis intuitiva que véase supra. Todos los objetos que están envueltos por CProcessLocal debe derivarse de CNoTrackObject . CNoTrackObject proporciona un asignador de nivel inferior (LocalAlloc/LocalFree) y un destructor virtual que MFC puede destruir automáticamente los objetos locales de proceso cuando se termina el proceso. Tales objetos pueden tener un destructor personalizado si es necesaria la limpieza adicional. El ejemplo anterior no requiere uno, ya que el compilador generará un destructor predeterminado para destruir el objeto incrustado de CString.

Hay otras interesantes ventajas a este enfoque. No sólo son todos CProcessLocal objetos destruidos automáticamente, no están construidos hasta que se necesiten. CProcessLocal::operator-gt; se crea una instancia del objeto asociado la primera vez que se llama y no antes. En el ejemplo anterior, esto significa que la ' str&Global ' cadena no se construirán hasta la primera vez que se llama SetGlobalString o GetGlobalString . En algunos casos, esto puede ayudar a reducir el tiempo de inicio DLL.

Datos de subproceso locales

Similar al proceso de datos locales, datos locales de hilo se utilizan cuando los datos deben ser locales en un subproceso determinado. Es decir, se necesita una instancia independiente de los datos para cada subproceso que tiene acceso a esos datos. Esto puede muchas veces ser usado en lugar de mecanismos de sincronización extensa. Si los datos no tiene que ser compartida por varios subprocesos, esos mecanismos pueden ser costoso e innecesario. Supongamos que tuvimos un objeto CString (igual que el ejemplo anterior). Podemos hacerlo de subprocesos locales envolviendo con un CThreadLocal plantilla:

struct CMyThreadData: CNoTrackObject pública
{
   CString strThread;
};
CThreadLocallt;CMyThreadData > threadData;

void MakeRandomString()
{
   y un tipo de shuffle de tarjeta (no una gran)
   CString & str = threadData - > strThread;
   STR.Empty();
   mientras (str.GetLength()! = 52)
   {
      TCHAR ch = rand() % 52 + 1;
      Si (str.Find(CH) < 0)
         STR += ch; / / no encontrado, agregar
   }
}

Si MakeRandomString fue llamado desde dos subprocesos diferentes, cada uno sería "shuffle" la cadena de diferentes maneras sin interferir con el otro. Esto es porque hay realmente un strThread instancia por hilo en lugar de una sola instancia global.

Ten&ga en cuenta cómo se utiliza una referencia para capturar la dirección de CString una vez en lugar de una vez por iteración del bucle. El código del bucle se podría haber escrito con threadData-gt;strThread en todo el mundo ' str ' se utiliza, pero sería mucho más lento en la ejecución del código. Lo mejor es que una referencia a los datos en caché cuando se producen tales referencias en bucles.

La CThreadLocal clase plantilla utiliza los mismos mecanismos que CProcessLocal hace y las mismas técnicas de aplicación.

&Notas técnicas por número |nbsp; Notas técnicas por categoría

Index