MFC interface packaging class (assertion failed when the member function call when multi-thread)

zhaozj2021-02-16  46

MFC Interface Package - Multi-threaded Member Functions Calling Assembly Failure often sees the following questions on the forum: DWORD WINAPI ThreadProc (Void * PDATA) // Thread function (for example to get data from COM) {//// Data acquisition loop // data is obtained in the variable I. Cabcdialog * PDIALOG = Reinterpret_cast (PDATA); // If Assert_Valid (PDIALOG) will assert fails PDIALOG-> m_data = i; PDIALOG-> Updatedata (false); // Updatedata Interior Assert_Valid (this) assertion failed ...} Bool Cabcdialog :: OnNInitdialog () {cdialog :: OnItDialog (); // Other initialization code CreateThread (null, 0, threadproc, this, 0, NULL); // Create Thread Return True;} Note that both assertions in the above comments fails, this paper explains why the assertion failed and explains why MFC does this implement and corresponding processing methods. Before explaining the underlying implementation of the MFC interface package, because of its and the window, first explain the basic knowledge of the window class to make a pad. The window class window is a structure that represents a window type, which is very similar to the concept of classes in C (although its expression is completely different, C class is just a memory layout and its operations this concept. Type), so it is called window classes. The window is a logical concept with device operating capabilities, namely a thing that can operate the device (usually a display). Since the window is an instance of the window class, it is like a class of C , it is possible to have a member function (although the expression is different), but must clarify the purpose of the window - operating equipment (this can also be targeted from Microsoft The function of the API developed by the window is mainly due to the convenience of the equipment. Therefore, the window should not be used for the creation of the function object because it has the function of the member function, which is good, but serious violations of the needs of semantics (on the semantics, refer to my other article - "Semantic needs" ), Is not advocated, but due to the addition of MFC interface packaging classes, most of the programmers often mix logic into the interface. The window class is a structure, most of which is not an important sense, just Microsoft's wish, if you do not want to use the interface API (Windows User Interface API, you can no matter those members. Among them, only one member is important - LPFNWndProc, message processing function. The outside world (code using the window) can only pass the message operation window, which is like an instance of a well-oriented-oriented class with a good object-oriented class in C to operate through its public member function. Therefore, the message processing function represents everything of a window (ignoring the role of other members in the window class).

It is easy to find that this instance is only a member function (message processing function), which does not have a member variable, that is, no specific memory is associated with a specific window, the window will not have a state (Windows also provides Window Properties API Mitigate this situation). This is also the root cause of the above problem. In order to handle the problem that the window cannot have a state (this is also a flexible performance of Windows), there are many ways, and the MFC is extended to the existing window classes, selecting a map handle (The unique label of the window) and a memory block are bound, and this memory block is an instance of the MFC interface package (from CWND start derived). The MFC status state is that the instance of the instance allows information to reappear across time periods, and the example of C classes is the value of the member variable of the instance by the external public member function to achieve a status. Effect. In MFC, there are three states: module state, process state, and thread state. The state of the three instances of modules, processes, and threads, respectively. Since the code is running by thread, and and the other two relationships are also very close, it is also known as local data. Module local data has a local variable of module. The module refers to a PE file loaded into the virtual memory space, that is, the EXE file itself and its DLL file it loaded. The locality of the module is the same pointer, and the different memory space is accessed from different modules. This actually only declares a global variable with each module, and the previous "code" is in the MFC library file, and then pass through a switching process (the address of the global variable of the module to be used to the foregoing pointer) Module locality can be realized. In the MFC, this process is switched by calling AFXSETMODULESTATE, and usually uses AFX_Manage_State this macro to process, so the following common statements are switches for module status: AFX_Manage_State (); MFC defines a structure (AFX_MODULE_STATE), its instance has a module locality, records the global application object pointer of this module, and a global variable such as a resource handle. One of the members variables are thread local data, and the type is AFX_MODULE_THREAD_STATE, which is the key to this article. Process local data has a localized variable. The same locality is the same, the same pointer, points to different memory spaces in different processes. This mechanism of virtual memory space for Windows itself is already implemented, but the global variables defined in the DLL, if the DLL supports Win32S, it is shared with global variables, that is, different processes load the same DLL will access the same memory. Win32s is for WIN32-based applications that can be run on Windows 3.1, because Windows 3.1 is 16-bit operating system, which has been eliminated, and the current DLL model itself has achieved process locality (but can still be shared To achieve the effect of the DLL in Win32S), the process status is actually a global variable. There are many structures in the MFC as local data, such as _afx_win_state, _afx_debug_state, _afx_db_state, etc., all of which have process local global variables inside the MFC. Thread local data has thread local variables. As above, the same pointer, different threads will access different memory spaces.

This MFC is implemented by thread local storage (TLS - Thread Local Storage, which is not related to this article). A structure (_AFX_THREAD_STATE) is defined in the MFC to record certain thread-level global variables, such as the most recent module status pointer, the last message, etc. A structure defined in the module thread state MFC (AFX_MODULE_THREAD_STATE), which has a thread locality and a module locality. That is to say, different threads will access the MFC library function from different modules from the same module and the same thread will result in different memory spaces. Its application is in AFX_Module_State, records some threads related but module-level data, such as the focus of this article - window handle map. Packaging class object and handle mapping handle map - CHANDLEMAP, MFC provides a underlying auxiliary class, and programmers should not use it directly. It has two important member variables: cmapptoptr m_permanentmap, m_temporarymap ;. Record the permanent handle binding and temporary handle binding. As mentioned earlier, the MFC uses a mapping to bind the window handle and the instance of the window, M_PrManentMap and M_TemPorarymap are this mapping, map the permanent packaging class object and the temporary package class object, and the AFX_Module_thread_state mentioned earlier There is a member variable: ChandleMap * m_pmaphwnd; (whose CHANDLEMAP * is using lazy programming, try to save resources) to complete HWND binding mappings, in addition to this member variable such as m_pmaphdc, m_pmaphmenu, to achieve HDC respectively , Hmenu's tied top map. Why is these mappings to be placed in a module thread state without placing a thread state or module status is obvious - the handle of these packaging packages is related to threads (such as HWND only to create its thread to receive its message) And the packaging object in this module may be different from another module (such as packaging class is a class specifically derived in a DLL, such as the instance of Cabutton defined in A.dll and CBButton defined in B.DLL If it is in a thread. At this time, the thread unloads A.dll, and then the Cabutton instance gets the message and processes, which will have a serious error - class code has been uninstalled). The meaning of the packaging class has two: the operation of the HWND to the HWND to the preparation of the code and provide the window subclass (not overdamping) to derive the window class. Packaging objects are divided into two types: permanent packaging objects (later referred to as permanent objects) and temporary packages (later referred to as temporary objects). The meaning of the temporary object is only only the operation of the HWnd is written to accelerate the code, does not have the function of the derived window class. Permanent objects have two senses of the previous packaging classes. When creating a window (ie CWnd :: Createex), the MFC processes the notification by hook in advance (WM_CREATE and WM_NCCREATE), with the AFXWndProc subcatenation created the window and add the corresponding CWnd * to the current thread's permanent object mapping In AfxWndProc, you are always by CWnd :: FromenPermanent (get a permanent object corresponding to HWnd) to obtain a permanent object corresponding to the window handle of the current message, and then processes the message by calling the resulting CWnd *'s WindowProc member function. To achieve the effect of derived window classes. This is to say that the permanent object has the meaning of window subclass, not just the operation of encapsulating HWnd.

To associate an HWnd and an existing packaging class, call CWnd :: attach to map this packaging class object and hwnd into a permanent object (but the permanent object obtained by this method does not necessarily have subclassics, it is likely Like the temporary object, only the purpose of the package). If you want to get a temporary object, you will get it through the CWnd :: FromHandle. Temporary objects are temporary, it is generated by MFC internal (Chandlemap :: deleteTemp) destroyed (usually invited by cwinthread :: onIdle). Therefore, the programmer is never trying to destroy the temporary object (even if the temporary object belongs to the thread without a message loop, cwinthread :: OnIdle, when the thread ends, ChandleMap's destruction still destroy temporary objects). Cause Why is the two packaging objects? Very fun? Note The window models previously mentioned - can only interact with the message mechanisms and windows. Note that the window is an instance of thread security. Writing the window process is not considered to have multiple threads to access the window at the same time. If you do not use two packaging classes, the hook created by calling SetProp and the corresponding CWND * binding in the hook created, which can be used to achieve the binding of the window handle and memory blocks. CWnd derived class CA, has a member variable m_bgcolor to determine what color fill background. Thread 1 creates an instance A of CA, passing its pointer to thread 2, thread 2 sets A.M_BGColor is red. This is already very obvious, ca :: m_bgcolor is not a thread secure, if more than one thread 2, A.M_BGColor will appear thread access conflicts. This serious violation of the window is this requirement for thread safety. Because the non-message mechanism is used to interact with the window, it failed. Continue, if you give a public member function setBGColor, use atomic operations to protect M_BGColor, isn't it normal? Oh, in CA :: OnPain, you will use m_bgcolor twice. If you call Ca :: setBgcolor to change Ca :: m_bgcolor, the problem is serious if there is another thread between two drawings. That is to say, it is necessary to protect the write operation of CA :: m_bgcolor, and read operations need to be protected, and this is just a member variable. Then continue, fully follow the definition of the window itself, only the message is interacting with it, that is, custom a message, such as am_setbgcolor, then in Ca :: SetBGColor this message, and modify CA :: in its response function m_bgcolor. Perfect, this is the window concept and good design, but it requires every programmer to write every packaging class, and the most important thing is that the concept of the C class is fundamentally in this design. No role, severe resources are wasted. Therefore, the MFC determines the advantages of the concept of the C class, so that the packaging object is equivalent to the window itself, so the above two packaging objects are used.

Let the packaging object different from the thread can thread the packaging objects, that is, a thread should not access the package object in another thread (because the package object is equivalent to the window, this is The object of MFC is not to be accessed across the package itself), "can't" is implemented by the assertion macro in the package member function (in CWnd :: AssertValid), and "should not" have explained before It is very clear. Therefore, the fundamental reason for the failure of the beginning of this article is because it violates "not" and "should not". Although the packaging object cannot be accessed across thread, the window handle can be accessed across threads. Because the packaging object is not only equivalent to the window, it also changed the interaction mode of the window (this is also the application of the concept of C classes), so that it is not necessary to use a message mechanism to interact with the window. Note The previously mentioned, if you access the package class object across thread, and use the concept of the C class to operate it, it must be threaded, and "cannot be accedible to" eliminate this problem. Therefore, the generation of temporary objects is just as mentioned earlier, and it is convenient for code, and does not provide subclass effect, because the window handle can be accessed across threads. The solution has already understood the cause of failure, so make the following modification: DWORD WINAPI ThreadProc (Void * PDATA) // Thread function (such as acquiring data from the COM port) {// data acquisition cycle // data is obtained after the variable I Cabcdialog * pdialog = static_cast (CWnd :: fromHandle (reinterpret_cast (pdata))); assert_valid (pdialog); // Maybe assert fails PDIALOG-> m_data = i; // This is not Good design, please refer to my other article: "Semantic needs" PDIALOG-> Updatedata (false); // updatedata internal assert_valid (this) may assert failure ...} Bool CabcDialog :: OnNInitDialog () {CDIALOG: : OnInitdialog (); // Other initialization code CreateThread (null, 0, threadproc, m_hwnd, 0, null); // Create thread return true;} The reason why it is "possibly", because there is a key object is HWND The packaging of the operation is not a package of the window. So all HWnd temporary objects are CWnd instances, even if the above forcibly converted to CabcDialog * is still CWnd *, when calling CabcDialog :: AssertValid in Assert_Valid, it defines some additional inspections, then this is a CWnd. Example rather than a CabcDialog instance, resulting in assertion failure. Therefore, all CabcDialog should be replaced with CWND. Although it is not failed, it still mistakes (what should be pdialog-> m_data), because the temporary object is the package of HWND operation, and unfortunately Updatedata is just MFC own A dialog data exchange mechanism (DDX) operation, which is not implemented by sending a message to the HWND, but through the virtual function mechanism.

转载请注明原文地址:https://www.9cbs.com/read-22594.html

New Post(0)