MFC interface packaging class (when multithreading, the member function call asserted failure)

Source: Internet
Author: User

MFC interface Packaging

-- The asserted call of the member function fails when multiple threads are used.

I often see the following problems on the Forum:
DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport)
{
// Data acquisition cycle
// Put the obtained data in variable I
Cabcdialog * pdialog = reinterpret_cast <cabcdialog *> (pdata );
Assert (pdialog); // here assert_valid (pdialog) will assert failure
Pdialog-> m_data = I;
Pdialog-> updatedata (false); // updatedata internal assert_valid (this) asserted failure
...
}
Bool cabcdialog: oninitdialog ()
{
Cdialog: oninitdialog ();
// Other initialization code
Createthread (null, 0, threadproc, this, 0, null); // create a thread
Return true;
}
Note that the assertion in the above comments fails. This article explains why the assertion fails from the underlying implementation of MFC, and explains why the implementation of MFC and the corresponding solutions.
Before explaining the underlying implementation of the MFC interface packaging class, because it is related to the window, we should first explain the basic knowledge of the window class to pave the way for the future.

Window Type

A window class is a structure, and an instance of it represents a window type. It is similar to the class concept in C ++ (although its representation is completely different, the C ++ class is only the type of memory layout and Its Operation Concept. Therefore, it is called a window class.
A window is a logical concept with device operation capabilities, that is, something that can operate a device (usually a display. A window is an instance of a window class, just like an instance of a class in C ++, it can have a member function (although in different forms ), however, you must specify the purpose of the window-to operate the device (this can also be seen from Microsoft's API functions for the window, mainly for the convenience of device operations ). Therefore, the window should not be used to create function objects because it has the function of member functions. Although this is good, it seriously violates the semantic requirements (about semantics, I can refer to another article, "semantic needs"), which is not recommended. However, due to the addition of the MFC interface packaging class, most programmers often mix the logic into the interface.
The window class is a structure, and most of the Members have little significance, but Microsoft is willing to work out it. If you do not want to use the interface API (Windows User Interface API), you can ignore those members. Only one member is important-lpfnwndproc, message processing function.
The outside world (using the window code) can only use the message operation window, this is like an example of a class written in C ++ with a good object-oriented style. It can only be operated through its Public member functions. Therefore, the message processing function represents everything in a window (ignoring the role of other Members in the window class ). It is easy to find that the window instance only has member functions (message processing functions), and does not have member variables, that is, no specific memory is associated with a specific window, the window cannot be in the state (Windows still provides the window properties API to ease this situation ). This is exactly the root cause of the above problem.
In order to solve the problem that Windows cannot be stateful (this is actually a flexible performance of Windows), there are many methods, while MFC can easily expand existing window classes, select a ing to bind a window handle (Unique Identifier of the window) to a memory block, this memory block is an instance of the well-known MFC interface packaging class (derived from cwnd.

MFC status

Status means that the instance can reproduce information across time periods through some means, A c ++ class instance is a type of class that changes the value of the member variable of an instance through the Public member function to achieve stateful effect. In MFC, there are three statuses: module status, Process status, and thread status. The status of the three instances: module, process, and thread. Because the code is run by a thread and closely related to the other two, it is also called local data.
Module local data
Variables With module locality. A module is a PE file loaded into the virtual memory space of the process, that is, the EXE file itself and the DLL file it loads. The module locality is the same pointer. Different Memory Spaces are accessed by code execution from different modules. In fact, only one global variable is declared for each module, and the previous "code" is in the MFC library file, then, you can implement the module locality by assigning the address of the global variable of the module you want to use to the aforementioned pointer. In MFC, this process is switched by calling afxsetmodulestate. Generally, afx_manage_state macro is used for processing. Therefore, the following common statements are used for switching the module status:
Afx_manage_state (afxgetstaticmodulestate ());
A structure (afx_module_state) is defined in MFC. Its instance is module-local and records global variables at the module level, such as global application object pointers and resource handles of this module. One of the member variables is the local data of the thread and the type is afx_module_thread_state, which is the key to this article.
Process local data
Variables with local processes. Similar to the module locality, that is, the same Pointer Points to different Memory Spaces in different processes. This mechanism has been implemented for Windows virtual memory space. However, if the DLL supports win32s, it shares its global variables, that is, different processes load the same DLL to access the same memory. Win32s is designed for Win32-based applications to run on Windows 3.1, because Windows 3.1 is a 16-bit operating system and has long been eliminated, the current DLL model has already achieved the local nature of the process (but it can still achieve the DLL effect in win32s through the Sharing Section). Therefore, the Process status is actually a global variable.
There are many structures in MFC as local data, such as _ afx_win_state, _ afx_debug_state, and _ afx_db_state, which are global variables with local processes used by MFC.
Thread Local Data
Variables with local thread. As shown above, different threads access different memory spaces for the same pointer. This MFC is implemented through local thread storage (TLS -- Thread Local storage, which is not used here because it is irrelevant to this Article.
MFC defines a structure (_ afx_thread_state) to record some global variables at the Thread level, such as the last module status pointer and the last message.
Module thread status
A structure (afx_module_thread_state) defined in MFC. Its instance is thread-local and module-local. That is to say, different threads accessing the MFC library functions from the same module and the same thread from different modules will lead to different operations on the memory space. It is used in afx_module_state to record thread-related but module-level data. For example, the focus of this article is window handle ing.

Packaging Class Object and handle ing

Handle ing-chandlemap, an underlying helper class provided by MFC, which should not be directly used by programmers. It has two important member variables: cmapptrtoptr m_permanentmap, m_temporarymap ;. Record the permanent handle binding and temporary handle binding respectively. As mentioned above, MFC uses a ing to bind the window handle to the instance of its packaging class. m_permanentmap and m_temporarymap are the mappings, respectively ing permanent packaging class objects and temporary packaging class objects, in the afx_module_thread_state mentioned above, there is a member variable: chandlemap * m_pmaphwnd; (the reason is that chandlemap * uses the lazy Programming Method to save resources as much as possible) in addition, member variables such as m_pmaphdc and m_pmaphmenu are used to realize the top-bound ing of HDC and hmenu respectively. Why do these mappings need to be placed in the module thread state rather than in the thread state or module state? The handle of these packaging classes is related to the thread (for example, hwnd only has the thread to create it ). to receive messages) in addition, the packaging class object in this module may be different from that in another module (for example, the packaging class is a class derived from a DLL, such as. the cabutton instance and B. if the cbbutton instance defined in DLL is in one thread at the same time. At this time, the thread uninstalls A. dll, and then the cabutton instance receives a message and processes it. A serious error will occur-the class code has been uninstalled ).
The existing packaging class has two meanings: packaging the hwnd operation to accelerate code writing and provide the effect of window subclass (not superclass) to derive the window class. There are two types of packaging objects for threads: Permanent packaging objects (permanent objects) and temporary packaging objects (temporary objects ). The meaning of a temporary object is only to wrap the operation on hwnd to accelerate code writing. It does not have the function of deriving window classes. Permanent objects have two meanings of the preceding packaging class.
When creating a window (that is, in cwnd: createex), MFC processes the notification in advance (before wm_create and wm_nccreate) through the hook, afxwndproc is used to subclass the created window and add the corresponding cwnd * to the ing of the permanent object of the current thread. In afxwndproc, always by cwnd :: fromhandlepermanent (obtain the permanent object corresponding to hwnd) gets the permanent object corresponding to the window handle of the current message in the current thread, then, the windowproc member function of cwnd * is called to process messages to implement the effect of the derived window class. This means that the permanent object has the significance of window subclass, rather than simply encapsulating the hwnd operation.
To associate an hwnd with an existing packaging Class Object, call cwnd :: attach maps the packaging Class Object and hwnd to a permanent object (however, the permanent object obtained by this method does not necessarily have the subclass function, and is likely to be the same as the temporary object, for encapsulation purposes only ). If you want to obtain a temporary object, you can use the static member function cwnd: fromhandle to obtain it. The temporary object is called temporary, that is, it is generated by the MFC internal (chandlemap: fromhandle), and its internal (chandlemap: deletetemp) is destroyed (usually through cwinthread :: onidle calls afxunlocktempmaps ). Therefore, programmers should never try to destroy the temporary object (even if the thread to which the temporary object belongs has no message loop, they cannot call cwinthread: onidle. At the end of the thread, the structure of the chandlemap still destroys the temporary object ).

Cause

Why are there two packaging objects? Is it fun? Note that the window model mentioned above-the message mechanism can only be used to interact with the window. Note that the window is a thread-safe instance. When writing a window, you do not need to consider the state of the window accessed by multiple threads at the same time. If two types of packaging objects are not used, call setprop In the hook created by the window to bind the created window handle to the corresponding cwnd, can I bind a window handle to a memory block?
The derived class CA of cwnd has a member variable m_bgcolor to determine the color used to fill the background. Thread 1 creates instance a of Ca and transmits its pointer to thread 2. Thread 2 sets a. m_bgcolor to red. Obviously, CA: m_bgcolor is NOT thread-safe. If there are more than one thread 2, A. m_bgcolor will cause thread access conflicts. This seriously violates this requirement that the window is thread-safe. Because the non-message mechanism is used to interact with the window, it fails.
Continue. If you give CA a public member function setbgcolor and use atomic operations to protect m_bgcolor, isn't everything normal? In Ca: onpaint, m_bgcolor is used twice for plotting. If CA: setbgcolor is called by another thread between the two plotting, CA: m_bgcolor is changed, the problem is serious. That is to say, not only do CA: m_bgcolor write operations require protection, but read operations also require protection. This is just a member variable.
Then proceed. According to the definition of the window itself, only the message is used to interact with it, that is, a custom message, such as am_setbgcolor, and then sendmessage in CA: setbgcolor, modify CA: m_bgcolor in the response function. Perfect. This is a good design that conforms to the window concept, but it requires every programmer to note this when writing every packaging class, and most importantly, the concept of C ++ class does not play a role in this design, causing serious resource waste.
Therefore, MFC decides to take advantage of the concept of C ++ class, so that the packaging class object looks equivalent to the window itself, so the above two packaging class objects are used. Let the packaging class object be thread-protected with different threads. That is to say, a thread cannot or should not access the packaging Class Object in another thread (because the packaging class object is equivalent to a window, this is the objective of MFC, not that the packaging class itself cannot be accessed across threads). "No" means that macro implementation is implemented through assertions in the packaging class member function (in cwnd: assertvalid ), however, we have already explained "no" clearly. Therefore, the root cause of assertion failure at the beginning of this article is that it violates "no" and "no ".
Although packaging objects cannot be accessed across threads, window handles can be accessed across threads. Because the packaging object is not only equivalent to the window, but also changes the window interaction mode (this is also the application of the C ++ class concept), so that you do not have to use the message mechanism to interact with the window. Note that if you access a packaging Class Object across threads and use the C ++ class concept to operate on it, thread protection is required, this problem is eliminated when cross-thread access is not allowed. Therefore, the generation of temporary objects is just as mentioned above. It facilitates code writing and does not provide subclass effect, because the window handle can be accessed across threads.

Solution

You have understood the cause of the failure, so make the following changes:
DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport)
{
// Data acquisition cycle
// Put the obtained data in variable I
Cabcdialog * pdialog = static_cast <cabcdialog *> (
Cwnd: fromhandle (reinterpret_cast Assert_valid (pdialog); // The assert_valid may fail.
Pdialog-> m_data = I; // This is a poor design. For details, refer to my other article: semantic needs.
Pdialog-> updatedata (false); // The assert_valid (this) in updatedata may fail to be asserted.
...
}
Bool cabcdialog: oninitdialog ()
{
Cdialog: oninitdialog ();
// Other initialization code
Createthread (null, 0, threadproc, m_hwnd, 0, null); // create a thread
Return true;
}
The reason is "possible", because the temporary object is encapsulated by the hwnd operation, not the encapsulation of the window class. Therefore, all temporary hwnd objects are cwnd instances. Even if the above forced conversion is cabcdialog *, it is still cwnd *. Therefore, when cabcdialog: assertvalid is called in assert_valid, it defines some additional checks and may find that this is a cwnd instance rather than a cabcdialog instance, leading to assertion failure. Therefore, we should replace all cabcdialog with cwnd. Although this failed, it is still wrong (not to mention pdialog-> m_data) because the temporary object is the encapsulation of the hwnd operation, unfortunately, updatedata is only a dialog box data exchange mechanism (DDX) provided by MFC. It is not implemented by sending messages to hwnd, but by the virtual function mechanism. Therefore, in updatedata, calling the instance's dodataexchange cannot call cabcdialog: dodataexchange, but cwnd: dodataexchange, so nothing will happen.
Therefore, a reasonable (not necessarily the best) solution is to send a message to the cabcdialog instance, and transmit data through an intermediate variable (such as a global variable) instead of using cabcdialog: m_data. Of course, if there is little data, for example, in this example, data should be transmitted as message parameters to reduce Code complexity. If there are many data, it should be passed through global variables, reduces the buffer management fee. The modification is as follows:
# Define am_datanotify (wm_user + 1)
Static DWORD g_data = 0;
DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport)
{
// Data acquisition cycle
// Put the obtained data in variable I
G_data = I;
Cwnd * pwnd = cwnd: fromhandle (reinterpret_cast Assert_valid (pwnd); // in this example, you should directly call the platform sendmessage without calling the packaging class.
Pwnd-> sendmessage (am_datanotify, 0, 0 );
...
}
Begin_message_map (cabcdialog, cdialog)
...
On_message (am_datanotify, ondatanotify)
...
End_message_map ()
Bool cabcdialog: oninitdialog ()
{
Cdialog: oninitdialog ();
// Other initialization code
Createthread (null, 0, threadproc, m_hwnd, 0, null); // create a thread
Return true;
}
Lresult cabcdialog: ondatanotify (wparam/* wparam */, lparam/* lparam */)
{
Updatedata (false );
Return 0;
}
Void cabcdialog: dodataexchange (cdataexchange * PDX)
{
Cdialog: dodataexchange (PDX );
Ddx_text (PDX, idc_edit1, g_data );
}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.