Multi-threaded MFC object pointers created in MFC cannot be transmitted between threads

Source: Internet
Author: User

For most mfc objects, do not pass them between threads, whether on the stack or on the stack! The reason is as follows:

1. Most of the mfc classes are not thread-safe. Calling the member functions of the input object may not report errors, but it may not be able to implement the predefined functions of the program!

2. Most of mfc interface-related classes are implemented through sendmessage. If the message processing function is not thread-safe, calling these methods from the work thread will sooner or later conflict with the user message response of your interface thread;

3. For CWnd-related classes, an operation may cause an exception (ASSERT exception) even if a window handle is passed in: get the window object through the handle and call its member functions or member variables! Because this object is a temporary object, it makes no sense to access its member variables. Accessing its member functions may throw an exception!

It is difficult to pass detailed analysis of mfc objects between threads. It involves three states in the MFC program: module status, Process status, and thread status!

The following reproduced the relevant articles, the original: http://blog.ednchina.com/hotpower/174745/message.aspx
MFC multi-thread programming
1. Performance-error example
About the window object (pointer? Handle ?) Problems:

After selecting the start thread in the menu:
Void cmainframe: onmenu_start ()
{
...
Afxbeginthread (mythread, this );
...
}

The thread functions are as follows:
Uint mythread (lpvoid pparam)
{
Cmainframe * pmainfrm = (cmainframe *) pparam;
...
}

Question 1:
Is there a problem with such code?
(The document says that the pointer to the mfc object cannot be directly transmitted between threads. It should be implemented through the transfer handle)

Question 2:
In this way, the view in the pmainfrm access window is normal.
However, when an access status bar is found:
Pmainfrm-> m_wndstatusbar.setpanetext (2, "test );
Debug assertion failed! (No problem in the window thread)
The location is in wincore. cpp.
Assert (p = pmap-> lookuppermanent (m_hwnd ))! = Null |
(P = pmap-> lookuptemporary (m_hwnd ))! = Null );
Why is access to view normal?

Question 3:
What should I do if it is implemented through a transfer handle?
I encountered a problem when using the following code:
Void cmainframe: onmenu_start ()
{
...
Hwnd = getsafehwnd ();
Afxbeginthread (mythread, hwnd );
...
}

Uint mythread (lpvoid pparam)
{
Cmainframe * pmainfrm = (cmainframe *) (cwnd: fromhandle (hwnd) pparam ));
...
}
Pmainfrm is obtained through the thread during execution, and access to its members is abnormal.
 
Netizen: hewwatt
The general reasons are as follows:
1. Most of the mfc classes are not thread-safe, and cwnd and its message routing are among the most
2. Most methods of the mfc interface class are implemented through sendmessage, while the Message Processing
Other messages are sent and processed. If the message processing function is not thread-safe
Calling these methods from the work thread will sooner or later conflict with the user message response of your interface thread.
3. cxxxx: fromhandle checks the query results of the caller's thread. If the user-created cxxxx is not found
Corresponding object, which will create a temporary object. Because you call this method in the work thread, of course
It is impossible to find the object you created in the main interface thread. In this case, you will create a temporary
Object and return it to you. You cannot expect its member variables to be meaningful. So use
You can only use cwnd: fromhandle because it only contains one m_hwnd member. But remember
Cross-thread direct or indirect call: sendmessage, usually unpredictable behavior.
 
2. Cause Analysis
MFC interface packaging class (when multithreading, the member function call asserted failure)
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 <HWND> (pData )));
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 <HWND> (pData ));
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 );
}
 
3. Notes
What is "thread security?
In the past, I often heard that the MFC object should not be used across threads, because MFC is NOT thread-safe. For example, the CWnd object should not be used across threads and can be replaced by a window handle (HWND. The CSocket/CAsyncSocket object should not be used across threads and should be replaced by a SOCKET handle. So what is thread security? When should I consider it? If the program involves multiple threads, thread security should be considered. For example, if an interface is designed and needs to be used in a multi-threaded environment in the future, or an object needs to be used across threads, this must be considered. There is no authoritative definition of thread security. Here I will only talk about my understanding: the provided interfaces for threads are atomic operations or switching between multiple threads will not lead to ambiguity in the execution results of this interface, that is to say, we do not need to consider synchronization issues.
Generally, "thread security" is caused by multi-thread access to shared resources. If we need to take synchronization Measures to Protect the shared resources accessed by an interface, this interface is NOT thread-safe. both MFC and stl are not thread-safe. how can we design a thread-safe class or interface? If all the data accessed in the interface belongs to private data, such an interface is thread-safe. or several interfaces are read-only for shared data, so such interfaces are thread-safe. if data is shared between multiple interfaces, and data is read and written, the caller does not need to consider data synchronization if the designer takes the synchronization measures, this interface is thread-safe, otherwise it is not thread-safe.
 
 
What should I pay attention to in multi-thread programming?
1. Use global variables and static variables as few as possible to share data, and use parameters to pass objects as much as possible. The object passed by parameters should include only required member variables. The required member variables must be operated by multiple threads. Many people think that this pointer (which may be an object pointer) is passed as a thread parameter, so that the thread has too many operation permissions, and the parameters in this are arbitrary. The whole program is completed by one person, which may be very careful and will not cause errors. However, as long as you turn around, the program will be completely invisible. When two threads operate on a member variable at the same time, the program crashes. Worse, this error is hard to reproduce. (I am depressed about this problem. We are a few people who compiled the program into a debug version. After several days of use, they found the error. Finding an error is just the beginning, because it is also very difficult to prove that the bug has been modified successfully .) In fact, data interaction between threads is mostly unidirectional. at the entrance of the thread callback function, we try to back up the incoming data to a local variable (of course, variables used for inter-thread communication cannot be processed in this way). In the future, only local variables will be processed, which can effectively solve this problem.
2. Use threads with caution in MFC. Because the MFC framework assumes that your message processing is completed in the main thread. First, the window handle belongs to the thread. If the thread with the window handle exits, if the other thread processes the window handle, the system will encounter a problem. To avoid this situation, MFC keeps making Assert errors when calling the message (window) processing function in the Child thread. A typical example is CSocket. Because CSocket uses a hidden window to implement false blocking, it is inevitable to use a message processing function. If you use CSocket in a child thread, you may see the assert pop-up.
3. Do not register COM components in different threads at the same time. Two threads, one registering 1.ocx, 2.ocx, 3.ocx, 4.ocx; and the other registering 5.ocx, 6.ocx, 7.ocx, 8.ocx. the result is a deadlock, which occurs in FreeLibrary and DllRegisterServer, respectively, the eight ocx files are made in MFC and may also contain MFC bugs. However, the DllRegisterServer is stuck in GetModuleFileName, while GetModuleFileName is an API! If you see the cause, please kindly advise.
4. Do not make the thread so complicated. Many beginners hate to use all the functions related to the thread. They are mutually exclusive here. They wait, start the thread later, and shut down the thread later, which is not inferior to the goto statement. A good multi-threaded program should use as few threads as possible. How can this sentence be understood? That is to say, we should try to unify a data sharing area to store data queues, and the worker thread will retrieve data from the queue, process the data, and then put it back, so that the data will be modularized and visualized; instead of starting a worker thread for processing every data, it is closed when processing is finished. Even if it is directly written, it will become tired after maintenance.

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.