TN058: Implementación de estado del módulo MFCTN058: MFC Module State Implementation

Nota

La nota técnica siguiente no se ha actualizado desde que se incluyó por primera vez en la documentación en línea.The following technical note has not been updated since it was first included in the online documentation. Como resultado, algunos procedimientos y temas podrían estar obsoletos o ser incorrectos.As a result, some procedures and topics might be out of date or incorrect. Para obtener información más reciente, se recomienda buscar el tema de interés en el índice de la documentación en línea.For the latest information, it is recommended that you search for the topic of interest in the online documentation index.

Esta nota técnica describe la implementación de construcciones de "estado de módulo" de MFC.This technical note describes the implementation of MFC "module state" constructs. Una comprensión de la implementación de estado del módulo es fundamental para usar los archivos DLL compartidos de MFC desde un archivo DLL (o un servidor OLE en proceso).An understanding of the module state implementation is critical for using the MFC shared DLLs from a DLL (or OLE in-process server).

Antes de leer esta nota, consulte "Administración de los datos de estado de los módulos MFC" en Creación de nuevos documentos, Ventanas y vistas.Before reading this note, refer to "Managing the State Data of MFC Modules" in Creating New Documents, Windows, and Views. Este artículo contiene información de uso importante e información general sobre este tema.This article contains important usage information and overview information on this subject.

Información generalOverview

Hay tres tipos de información de estado MFC: estado de módulo, estado de proceso y estado de subproceso.There are three kinds of MFC state information: Module State, Process State, and Thread State. A veces, estos tipos de estado se pueden combinar.Sometimes these state types can be combined. Por ejemplo, las asignaciones de identificador de MFC son locales de módulo y locales de subprocesos.For example, MFC's handle maps are both module local and thread local. Esto permite que dos módulos diferentes tengan mapas diferentes en cada uno de sus subprocesos.This allows two different modules to have different maps in each of their threads.

El estado del proceso y el estado del subproceso son similares.Process State and Thread State are similar. Estos elementos de datos son cosas que tradicionalmente han sido variables globales, pero tienen que ser específicos de un proceso o subproceso determinado para la compatibilidad adecuada con Win32s o para la compatibilidad adecuada con multithreading.These data items are things that have traditionally been global variables, but have need to be specific to a given process or thread for proper Win32s support or for proper multithreading support. La categoría en la que cabe un elemento de datos determinado depende de ese elemento y de su semántica deseada con respecto a los límites de proceso y subproceso.Which category a given data item fits in depends on that item and its desired semantics with regard to process and thread boundaries.

El estado del módulo es único en el que puede contener un estado o estado verdaderamente global que sea local de proceso o local de subprocesos.Module State is unique in that it can contain either truly global state or state that is process local or thread local. Además, se puede cambiar rápidamente.In addition, it can be switched quickly.

Cambio de estado del móduloModule State Switching

Cada subproceso contiene un puntero al estado del módulo "actual" o "activo" (no es de extrañar que el puntero forme parte del estado local del subproceso de MFC).Each thread contains a pointer to the "current" or "active" module state (not surprisingly, the pointer is part of MFC's thread local state). Este puntero se cambia cuando el subproceso de ejecución pasa un límite de módulo, como una aplicación que llama a un control OLE o DLL, o un control OLE que vuelve a llamar a una aplicación.This pointer is changed when the thread of execution passes a module boundary, such as an application calling into an OLE Control or DLL, or an OLE Control calling back into an application.

El estado actual del módulo AfxSetModuleStatese cambia llamando a .The current module state is switched by calling AfxSetModuleState. En su mayor parte, nunca tratará directamente con la API.For the most part, you will never deal directly with the API. MFC, en muchos casos, lo llamará por usted (en AfxWndProcWinMain, puntos de entrada OLE, , etc.).MFC, in many cases, will call it for you (at WinMain, OLE entry-points, AfxWndProc, etc.). Esto se hace en cualquier componente que escriba WndProcvinculando estáticamente en un especial, y un especial WinMain (o DllMain) que sepa qué estado del módulo debe ser actual.This is done in any component you write by statically linking in a special WndProc, and a special WinMain (or DllMain) that knows which module state should be current. Puede ver este código mirando DLLMODUL. CPP o APPMODUL. CPP en el directorio MFC-SRC.You can see this code by looking at DLLMODUL.CPP or APPMODUL.CPP in the MFC\SRC directory.

Es raro que desee establecer el estado del módulo y, a continuación, no volver a establecerlo.It is rare that you want to set the module state and then not set it back. La mayoría de las veces desea "empujar" su propio estado de módulo como el actual y luego, después de que haya terminado, "pop" el contexto original de nuevo.Most of the time you want to "push" your own module state as the current one and then, after you are done, "pop" the original context back. Esto lo hace el AFX_MANAGE_STATE macro AFX_MAINTAIN_STATEy la clase especial .This is done by the macro AFX_MANAGE_STATE and the special class AFX_MAINTAIN_STATE.

CCmdTargettiene características especiales para soportar la conmutación de estado del módulo.CCmdTarget has special features for supporting module state switching. En particular, CCmdTarget a es la clase raíz utilizada para la automatización OLE y los puntos de entrada OLE COM.In particular, a CCmdTarget is the root class used for OLE automation and OLE COM entry points. Al igual que cualquier otro punto de entrada expuesto al sistema, estos puntos de entrada deben establecer el estado correcto del módulo.Like any other entry point exposed to the system, these entry points must set the correct module state. ¿Cómo un CCmdTarget determinado sabe cuál debe ser el estado del módulo "correcto" La respuesta es que "recuerda" cuál es el estado del módulo "actual" cuando se construye, de modo que puede establecer el estado del módulo actual en ese valor "recordado" cuando se llama más tarde.How does a given CCmdTarget know what the "correct" module state should be The answer is that it "remembers" what the "current" module state is when it is constructed, such that it can set the current module state to that "remembered" value when it is later called. Como resultado, el estado del CCmdTarget módulo al que está asociado un objeto determinado es el estado del módulo que era actual cuando se construyó el objeto.As a result, the module state that a given CCmdTarget object is associated with is the module state that was current when the object was constructed. Tome un ejemplo sencillo de cargar un servidor INPROC, crear un objeto y llamar a sus métodos.Take a simple example of loading an INPROC server, creating an object, and calling its methods.

  1. OLE carga el archivo LoadLibraryDLL mediante .The DLL is loaded by OLE using LoadLibrary.

  2. RawDllMainse llama primero.RawDllMain is called first. Establece el estado del módulo en el estado de módulo estático conocido para el archivo DLL.It sets the module state to the known static module state for the DLL. Por esta RawDllMain razón está vinculado estáticamente a la DLL.For this reason RawDllMain is statically linked to the DLL.

  3. Se llama al constructor del generador de clases asociado a nuestro objeto.The constructor for the class factory associated with our object is called. COleObjectFactoryse deriva CCmdTarget de y como resultado, recuerda en qué estado de módulo se crea una instancia.COleObjectFactory is derived from CCmdTarget and as a result, it remembers in which module state it was instantiated. Esto es importante: cuando se pide al generador de clases que cree objetos, ahora sabe qué estado de módulo se va a hacer actual.This is important — when the class factory is asked to create objects, it knows now what module state to make current.

  4. DllGetClassObjectestá llamado a obtener la fábrica de clases.DllGetClassObject is called to obtain the class factory. MFC busca la lista de generador de clases asociada a este módulo y la devuelve.MFC searches the class factory list associated with this module and returns it.

  5. Se llama a COleObjectFactory::XClassFactory2::CreateInstance.COleObjectFactory::XClassFactory2::CreateInstance is called. Antes de crear el objeto y devolverlo, esta función establece el estado del módulo en COleObjectFactory el estado del módulo que estaba actual en el paso 3 (el que estaba actualizado cuando se creó una instancia de la instancia).Before creating the object and returning it, this function sets the module state to the module state that was current in step 3 (the one that was current when the COleObjectFactory was instantiated). Esto se hace dentro de METHOD_PROLOGUE.This is done inside of METHOD_PROLOGUE.

  6. Cuando se crea el objeto, CCmdTarget también es una COleObjectFactory derivada y de la misma manera se recuerda qué estado de módulo estaba activo, también lo hace este nuevo objeto.When the object is created, it too is a CCmdTarget derivative and in the same way COleObjectFactory remembered which module state was active, so does this new object. Ahora el objeto sabe a qué estado de módulo cambiar cada vez que se llama.Now the object knows which module state to switch to whenever it is called.

  7. El cliente llama a una función en CoCreateInstance el objeto OLE COM que recibió de su llamada.The client calls a function on the OLE COM object it received from its CoCreateInstance call. Cuando se llama al METHOD_PROLOGUE objeto se utiliza COleObjectFactory para cambiar el estado del módulo al igual que lo hace.When the object is called it uses METHOD_PROLOGUE to switch the module state just like COleObjectFactory does.

Como puede ver, el estado del módulo se propaga de objeto a objeto a medida que se crean.As you can see, the module state is propagated from object to object as they are created. Es importante que el estado del módulo se establezca correctamente.It is important to have the module state set appropriately. Si no se establece, el objeto DLL o COM puede interactuar mal con una aplicación MFC que lo está llamando, o puede no ser capaz de encontrar sus propios recursos, o puede producir un error de otras maneras miserables.If it is not set, your DLL or COM object may interact poorly with an MFC application that is calling it, or may be unable to find its own resources, or may fail in other miserable ways.

Tenga en cuenta que ciertos tipos de archivos DLL, específicamente los archivos RawDllMain DLL de "extensión MFC" RawDllMainno cambian el estado del módulo en su (en realidad, por lo general ni siquiera tienen un ).Note that certain kinds of DLLs, specifically "MFC Extension" DLLs do not switch the module state in their RawDllMain (actually, they usually don't even have a RawDllMain). Esto se debe a que están destinados a comportarse "como si" estuvieran realmente presentes en la aplicación que los utiliza.This is because they are intended to behave "as if" they were actually present in the application that uses them. Son una parte de la aplicación que se está ejecutando y es su intención modificar el estado global de esa aplicación.They are very much a part of the application that is running and it is their intention to modify that application's global state.

Los controles OLE y otros archivos DLL son muy diferentes.OLE Controls and other DLLs are very different. No quieren modificar el estado de la aplicación que realiza la llamada; la aplicación que les está llamando puede que ni siquiera sea una aplicación MFC, por lo que puede no haber ningún estado para modificar.They do not want to modify the calling application's state; the application that is calling them may not even be an MFC application and so there may be no state to modify. Esta es la razón por la que se inventó la conmutación de estado del módulo.This is the reason that module state switching was invented.

Para las funciones exportadas desde un archivo DLL, como una que inicia un cuadro de diálogo en el archivo DLL, debe agregar el código siguiente al principio de la función:For exported functions from a DLL, such as one that launches a dialog box in your DLL, you need to add the following code to the beginning of the function:

AFX_MANAGE_STATE(AfxGetStaticModuleState())

Esto intercambia el estado del módulo actual con el estado devuelto desde AfxGetStaticModuleState hasta el final del ámbito actual.This swaps the current module state with the state returned from AfxGetStaticModuleState until the end of the current scope.

Se producirán problemas con los recursos de los archivos DLL si no se utiliza la macro AFX_MODULE_STATE.Problems with resources in DLLs will occur if the AFX_MODULE_STATE macro is not used. De forma predeterminada, MFC usa el identificador de recursos de la aplicación principal para cargar la plantilla de recursos.By default, MFC uses the resource handle of the main application to load the resource template. Esta plantilla se almacena realmente en el archivo DLL.This template is actually stored in the DLL. La causa raíz es que la macro de AFX_MODULE_STATE no ha cambiado la información de estado del módulo de MFC.The root cause is that MFC's module state information has not been switched by the AFX_MODULE_STATE macro. El identificador de recursos se recupera del estado del módulo de MFC.The resource handle is recovered from MFC's module state. Si no se cambia el estado del módulo, se utiliza el identificador de recursos incorrecto.Not switching the module state causes the wrong resource handle to be used.

AFX_MODULE_STATE no es necesario poner en todas las funciones del archivo DLL.AFX_MODULE_STATE does not need to be put in every function in the DLL. Por ejemplo, InitInstance el código MFC de la aplicación puede llamarlo a InitInstance él sin AFX_MODULE_STATE InitInstance porque MFC cambia automáticamente el estado del módulo antes y, a continuación, lo vuelve a cambiar después de las devoluciones.For example, InitInstance can be called by the MFC code in the application without AFX_MODULE_STATE because MFC automatically shifts the module state before InitInstance and then switches it back after InitInstance returns. Lo mismo es cierto para todos los controladores de mapa de mensajes.The same is true for all message map handlers. Los archivos DLL de MFC normales tienen realmente un procedimiento de ventana maestra especial que cambia automáticamente el estado del módulo antes de enrutar cualquier mensaje.Regular MFC DLLs actually have a special master window procedure that automatically switches the module state before routing any message.

Procesar datos localesProcess Local Data

Procesar datos locales no sería de gran preocupación si no hubiera sido por la dificultad del modelo DLL Win32s.Process local data would not be of such great concern had it not been for the difficulty of the Win32s DLL model. En Win32s, todos los archivos DLL comparten sus datos globales, incluso cuando se cargan en varias aplicaciones.In Win32s all DLLs share their global data, even when loaded by multiple applications. Esto es muy diferente del modelo de datos DLL de Win32 "real", donde cada DLL obtiene una copia independiente de su espacio de datos en cada proceso que se adjunta al archivo DLL.This is very different from the "real" Win32 DLL data model, where each DLL gets a separate copy of its data space in each process that attaches to the DLL. Para agregar a la complejidad, los datos asignados en el montón en un archivo DLL de Win32s son de hecho específicos del proceso (al menos en lo que respecta a la propiedad).To add to the complexity, data allocated on the heap in a Win32s DLL is in fact process specific (at least as far as ownership goes). Tenga en cuenta los siguientes datos y código:Consider the following data and code:

static CString strGlobal; // at file scope

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

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
    StringCbCopy(lpsz, cb, strGlobal);
}

Considere lo que sucede si el código anterior está ubicado en un archivo DLL y ese archivo DLL se carga mediante dos procesos A y B (de hecho, podría ser dos instancias de la misma aplicación).Consider what happens if the above code is in located in a DLL and that DLL is loaded by two processes A and B (it could, in fact, be two instances of the same application). Una SetGlobalString("Hello from A")llamada .A calls SetGlobalString("Hello from A"). Como resultado, la memoria CString se asigna para los datos en el CString contexto del proceso A. Tenga en cuenta que el sí mismo es global y es visible para A y B. Ahora B GetGlobalString(sz, sizeof(sz))llama .As a result, memory is allocated for the CString data in the context of process A. Keep in mind that the CString itself is global and is visible to both A and B. Now B calls GetGlobalString(sz, sizeof(sz)). B podrá ver los datos que A establece.B will be able to see the data that A set. Esto se debe a que Win32s no ofrece protección entre procesos como Win32.This is because Win32s offers no protection between processes like Win32 does. Ese es el primer problema; en muchos casos no es deseable que una aplicación afecte a los datos globales que se considera propiedad de una aplicación diferente.That is the first problem; in many cases it is not desirable to have one application affect global data that is considered to be owned by a different application.

Hay problemas adicionales también.There are additional problems as well. Digamos que la A ahora sale.Let's say that A now exits. Cuando A sale, la memoriastrGlobalutilizada por la cadena ' ' está disponible para el sistema, es decir, toda la memoria asignada por el proceso A se libera automáticamente por el sistema operativo.When A exits, the memory used by the 'strGlobal' string is made available for the system — that is, all memory allocated by process A is freed automatically by the operating system. No se libera porque CString se llama al destructor; aún no se ha llamado.It is not freed because the CString destructor is being called; it hasn't been called yet. Se libera simplemente porque la aplicación que lo asignó ha salido de la escena.It is freed simply because the application which allocated it has left the scene. Ahora, si GetGlobalString(sz, sizeof(sz))B llamó , puede que no obtenga datos válidos.Now if B called GetGlobalString(sz, sizeof(sz)), it may not get valid data. Alguna otra aplicación puede haber utilizado esa memoria para otra cosa.Some other application may have used that memory for something else.

Claramente existe un problema.Clearly a problem exists. MFC 3.x utiliza una técnica denominada almacenamiento local de subprocesos (TLS).MFC 3.x used a technique called thread-local storage (TLS). MFC 3.x asignaría un índice TLS que en Win32s realmente actúa como un índice de almacenamiento local de proceso, aunque no se llama a eso y, a continuación, haría referencia a todos los datos basados en ese índice TLS.MFC 3.x would allocate a TLS index that under Win32s really acts as a process-local storage index, even though it is not called that and then would reference all data based on that TLS index. Esto es similar al índice TLS que se usó para almacenar datos locales de subprocesos en Win32 (consulte a continuación para obtener más información sobre ese tema).This is similar to the TLS index that was used to store thread-local data on Win32 (see below for more information on that subject). Esto provocó que cada DLL de MFC utilizara al menos dos índices TLS por proceso.This caused every MFC DLL to utilize at least two TLS indices per process. Cuando se contabiliza para cargar muchos archivos DLL de control OLE (OXP), se agota rápidamente los índices TLS (solo hay 64 disponibles).When you account for loading many OLE Control DLLs (OCXs), you quickly run out of TLS indices (there are only 64 available). Además, MFC tenía que colocar todos estos datos en un solo lugar, en una sola estructura.In addition, MFC had to place all this data in one place, in a single structure. No era muy extensible y no era ideal con respecto a su uso de índices TLS.It was not very extensible and was not ideal with regard to its use of TLS indices.

MFC 4.x soluciona esto con un conjunto de plantillas de clase que puede "envolver" alrededor de los datos que deben ser locales de proceso.MFC 4.x addresses this with a set of class templates you can "wrap" around the data that should be process local. Por ejemplo, el problema mencionado anteriormente podría solucionarse escribiendo:For example, the problem mentioned above could be fixed by writing:

struct CMyGlobalData : public CNoTrackObject
{
    CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;

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

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
    StringCbCopy(lpsz, cb, globalData->strGlobal);
}

MFC implementa esto en dos pasos.MFC implements this in two steps. En primer lugar, hay una capa encima de las API de* Tls de Win32 (TlsAlloc, TlsSetValue, TlsGetValue, etc.) que utilizan solo dos índices TLS por proceso, independientemente de cuántos archivos DLL tenga.First, there is a layer on top of the Win32 Tls* APIs (TlsAlloc, TlsSetValue, TlsGetValue, etc.) which use only two TLS indexes per process, no matter how many DLLs you have. En segundo CProcessLocal lugar, se proporciona la plantilla para acceder a estos datos.Second, the CProcessLocal template is provided to access this data. Anula la > operador, que es lo que permite la sintaxis intuitiva que se ve arriba.It overrides operator-> which is what allows the intuitive syntax you see above. Todos los objetos CProcessLocal que se ajustan CNoTrackObjectpor deben derivarse de .All objects that are wrapped by CProcessLocal must be derived from CNoTrackObject. CNoTrackObjectproporciona un asignador de nivel inferior (LocalAlloc/LocalFree) y un destructor virtual de modo que MFC puede destruir automáticamente los objetos locales del proceso cuando finaliza el proceso.CNoTrackObject provides a lower-level allocator (LocalAlloc/LocalFree) and a virtual destructor such that MFC can automatically destroy the process local objects when the process is terminated. Estos objetos pueden tener un destructor personalizado si se requiere una limpieza adicional.Such objects can have a custom destructor if additional cleanup is required. El ejemplo anterior no requiere uno, ya que el compilador CString generará un destructor predeterminado para destruir el objeto incrustado.The above example doesn't require one, since the compiler will generate a default destructor to destroy the embedded CString object.

Hay otras ventajas interesantes para este enfoque.There are other interesting advantages to this approach. No sólo CProcessLocal todos los objetos se destruyen automáticamente, no se construyen hasta que se necesitan.Not only are all CProcessLocal objects destroyed automatically, they are not constructed until they are needed. CProcessLocal::operator->creará una instancia del objeto asociado la primera vez que se llama, y no antes.CProcessLocal::operator-> will instantiate the associated object the first time it is called, and no sooner. En el ejemplo anterior, esostrGlobalsignifica que la cadena ' SetGlobalString ' GetGlobalString no se construirá hasta la primera vez o se llama.In the example above, that means that the 'strGlobal' string will not be constructed until the first time SetGlobalString or GetGlobalString is called. En algunos casos, esto puede ayudar a reducir el tiempo de inicio de DLL.In some instances, this can help decrease DLL startup time.

Datos locales de subprocesosThread Local Data

De forma similar al procesamiento de datos locales, los datos locales de subprocesos se usan cuando los datos deben ser locales para un subproceso determinado.Similar to process local data, thread local data is used when the data must be local to a given thread. Es decir, necesita una instancia independiente de los datos para cada subproceso que tiene acceso a esos datos.That is, you need a separate instance of the data for each thread that accesses that data. Esto se puede utilizar muchas veces en lugar de mecanismos de sincronización extensos.This can many times be used in lieu of extensive synchronization mechanisms. Si no es necesario compartir los datos por varios subprocesos, estos mecanismos pueden ser costosos e innecesarios.If the data does not need to be shared by multiple threads, such mechanisms can be expensive and unnecessary. Supongamos que CString teníamos un objeto (muy parecido a la muestra anterior).Suppose we had a CString object (much like the sample above). Podemos hacerlo enhebrado local CThreadLocal envolviéndolo con una plantilla:We can make it thread local by wrapping it with a CThreadLocal template:

struct CMyThreadData : public CNoTrackObject
{
    CString strThread;
};
CThreadLocal<CMyThreadData> threadData;

void MakeRandomString()
{
    // a kind of card shuffle (not a great one)
    CString& str = threadData->strThread;
    str.Empty();
    while (str.GetLength() != 52)
    {
        unsigned int randomNumber;
        errno_t randErr;
        randErr = rand_s(&randomNumber);

        if (randErr == 0)
        {
            TCHAR ch = randomNumber % 52 + 1;
            if (str.Find(ch) <0)
            str += ch; // not found, add it
        }
    }
}

Si MakeRandomString se llama desde dos subprocesos diferentes, cada uno "barajaría" la cadena de diferentes maneras sin interferir con el otro.If MakeRandomString was called from two different threads, each would "shuffle" the string in different ways without interfering with the other. Esto se debe a strThread que en realidad hay una instancia por subproceso en lugar de una sola instancia global.This is because there is actually a strThread instance per thread instead of just one global instance.

Observe cómo se utiliza una CString referencia para capturar la dirección una vez en lugar de una vez por iteración de bucle.Note how a reference is used to capture the CString address once instead of once per loop iteration. El código de bucle threadData->strThread podría haberstrsido escrito con todas partes ' ' se utiliza, pero el código sería mucho más lento en la ejecución.The loop code could have been written with threadData->strThread everywhere 'str' is used, but the code would be much slower in execution. Es mejor almacenar en caché una referencia a los datos cuando estas referencias se producen en bucles.It is best to cache a reference to the data when such references occur in loops.

La CThreadLocal plantilla de clase utiliza CProcessLocal los mismos mecanismos que y las mismas técnicas de implementación.The CThreadLocal class template uses the same mechanisms that CProcessLocal does and the same implementation techniques.

Consulte tambiénSee also

Notas técnicas por númeroTechnical Notes by Number
Notas técnicas por categoríaTechnical Notes by Category