MFC多线程编程注意事项

非线性动力学_用MatCont分析非线性动力学方程组(一) 写在前面:笔者打算写两到三篇系列文章介绍MatCont,此为第一篇《用MatCont分析非线性动力学方程组(一)》。MatCont是一个基于Matlab而开发的免费工具包,专门用来做非线性动力学模型分叉、向量场、周期性等分析,符号(连续性)运算和数值(离散性)运算均适用。该工具的开发及研究项目由比利时Ghent大学的Willy Govaerts教授、荷兰Utrecht大学的Yuri Kuznets... 阅读详情
MFC多线程编程注意事项
PeterLee整理 2008-05-26
关于启动线程时传输窗口对象(指针?句柄?)的问题:  
   
  在选择菜单中的开始线程后:  
  void   cmainframe::onmenu_start()  
  {  
        ...  
        afxbeginthread(mythread,   this);  
        ...  
  }  
   
  线程函数如下:  
  uint   mythread(lpvoid   pparam)  
  {  
        cmainframe*   pmainfrm   =   (cmainframe   *)pparam;  
        ...  
  }  
   
  问题一:  
        这样的代码是不是有问题?  
        (文档中说线程间不能直接传输mfc对象的指针,应该通过传输句柄实现)  
   
  问题二:  
        这样使用开始好像没有问题,直接通过pmainfrm访问窗口中的view都正常。  
        但发现访问状态条时:  
              pmainfrm->m_wndstatusbar.setpanetext(2,   "test);  
        出现debug   assertion   failed!(在窗口线程中没有问题)  
        位置是wincore.cpp中的  
              assert((p   =   pmap->lookuppermanent(m_hwnd))   !=   null   ||  
              (p   =   pmap->lookuptemporary(m_hwnd))   !=   null);  
        为什么访问view能正常,但访问状态条时不可以呢?  
   
  问题三:  
        如果通过传输句柄实现,怎样做呢?  
        我用下面的代码执行时有问题:  
        void   cmainframe::onmenu_start()  
        {  
              ...  
              hwnd   hwnd   =   getsafehwnd();  
              afxbeginthread(mythread,   hwnd);  
              ...  
        }  
   
        uint   mythread(lpvoid   pparam)  
        {  
              cmainframe*   pmainfrm   =   (cmainframe   *)(cwnd::fromhandle((hwnd)pparam));  
              ...  
        }  
        执行时通过线程中得到pmainfrm,访问其成员时不正常。 
 
网友:hewwatt
大致原因解释如下:  
 1.   mfc的大多数类不是线程安全的,cwnd及其消息路由是其中之最  
 2.   mfc界面类的大多数方法,最后都是通过sendmessage实现的,而消息处理的  
        过程中会引发其他消息的发送及处理。如果消息处理函数本身不是线程安全的  
        你从工作线程中调用这些方法迟早会同你界面线程的用户消息响应发生冲突  
 3.   cxxxx::fromhandle会根据调用者所在线程查表,如果查不到用户创建的cxxxx  
        对应对象,它会创建一个临时对象出来。由于你在工作线程中调用该方法,当然  
        不可能查到界面主线程中你所建立起来的那个对象了。这时mfc会你创建一个临时  
        对象并返回给你,你根本不可能期望它的成员变量会是有意义的。   所以要用  
        也只能用cwnd::fromhandle,因为它只包含一个m_hwnd成员。   不过,要记住  
        跨线程直接或间接地调用::sendmessage,通常都是行为不可预测的。  
 
MFC界面包装类(多线程时成员函数调用的断言失败)
日期:2006-9-17 18:06:00     [Host01.Com] 
MFC界面包装类
——多线程时成员函数调用的断言失败
   经常在论坛上看到如下的问题:
DWORD WINAPI ThreadProc( void *pData )
  // 线程函数(比如用于从COM口获取数据)
{
    // 数据获取循环
    // 数据获得后放在变量i中
    CAbcDialog *pDialog = reinterpret_cast< CAbcDialog* >( pData );
    ASSERT( pDialog );  // 此处如果ASSERT_VALID( pDialog )将断言失败
    pDialog->m_Data = i;
    pDialog->UpdateData( FALSE );  // UpdateData内部ASSERT_VALID( this )断言失败
   …
}
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    // 其他初始化代码
    CreateThread( NULL, 0, ThreadProc, this, 0, NULL );  // 创建线程
    return TRUE;
}
   注意上面注释中的两处断言失败,本文从MFC底层的实现来解释为什么会断言失败,并说明MFC为什么要这样实现及相应的处理办法。
   在说明MFC界面包装类的底层实现之前,由于其和窗口有关,故先讲解窗口类这个基础知识以为后面做铺垫。

窗口类
   窗口类是一个结构,其一个实例代表着一个窗口类型,与C++中的类的概念非常相近(虽然其表现形式完全不同,C++的类只不过是内存布局和其上的操作这个概念的类型),故被称作为窗口类。
   窗 口是具有设备操作能力的逻辑概念,即一种能操作设备(通常是显示器)的东西。由于窗口是窗口类的实例,就象C++中的一个类的实例,是可以具有成员函数的 (虽然表现形式不同),但一定要明确窗口的目的——操作设备(这点也可以从Microsoft针对窗口所制订的API的功能看出,主要出于对设备操作的方 便)。因此不应因为其具有成员函数的功能而将窗口用于功能对象的创建,这虽然不错,但是严重违反了语义的需要(关于语义,可参考我的另一篇文章——《语义 的需要》),是不提倡的,但却由于MFC界面包装类的加入导致大多数程序员经常将逻辑混入界面。
   窗口类是个结构,其中的大部分成员都没什么重要意义,只是Microsoft一相情愿制订的,如果不想使用界面API(Windows User Interface API),可以不管那些成员。其中只有一个成员是重要的——lpfnWndProc,消息处理函数。
   外 界(使用窗口的代码)只能通过消息操作窗口,这就如同C++中编写的具有良好的面向对象风格的类的实例只能通过其公共成员函数对其进行操作。因此消息处理 函数就代表了一个窗口的一切(忽略窗口类中其他成员的作用)。很容易发现,窗口这个实例只具有成员函数(消息处理函数),不具有成员变量,即没有一块特定 内存和一特定的窗口相关联,则窗口将不能具有状态(Windows还是提供了Window Properties API来缓和这种状况)。这也正是上面问题发生的根源。
   为 了处理窗口不能具有状态的问题(这其实正是Windows灵活的表现),可以有很多种方法,而MFC出于能够很容易的对已有窗口类进行扩展,选择了使用一 个映射将一个窗口句柄(窗口的唯一标示符)和一个内存块进行绑定,而这块内存块就是我们熟知的MFC界面包装类(从CWnd开始派生延续)的实例。

MFC状态
   状 态就是实例通过某种手段使得信息可以跨时间段重现,C++的类的实例就是由外界通过公共成员函数改变实例的成员变量的值以实现具有状态的效果。在MFC 中,具有三种状态:模块状态、进程状态、线程状态。分别为模块、进程和线程这三种实例的状态。由于代码是由线程运行,且和另外两个的关系也很密切,因此也 被称作本地数据。
模块本地数据
   具 有模块本地性的变量。模块指一个加载到进程虚拟内存空间中的PE文件,即exe文件本身和其加载的dll文件。而模块本地性即同样的指针,根据代码从不同 的模块执行而访问不同的内存空间。这其实只用每个模块都声明一个全局变量,而前面的“代码”就在MFC库文件中,然后通过一个切换的过程(将欲使用的模块 的那个全局变量的地址赋给前述的指针)即可实现模块本地性。MFC中,这个过程是通过调用AfxSetModuleState来切换的,而通常都使用 AFX_MANAGE_STATE这个宏来处理,因此下面常见的语句就是用于模块状态的切换的:
AFX_MANAGE_STATE( AfxGetStaticModuleState() );
    MFC中定义了一个结构(AFX_MODULE_STATE),其实例具有模块本地性,记录了此模块的全局应用程序对象指针、资源句柄等模块级的全局变量。其中有一个成员变量是线程本地数据,类型为AFX_MODULE_THREAD_STATE,其就是本文问题的关键。
进程本地数据
   具 有进程本地性的变量。与模块本地性相同,即同一个指针,在不同进程中指向不同的内存空间。这一点Windows本身的虚拟内存空间这个机制已经实现了,不 过在dll中定义的全局变量,如果dll支持Win32s,则其是共享其全局变量的,即不同的进程加载了同一dll将访问同一内存。Win32s是为了那 些基于Win32的应用程序能在Windows 3.1上运行,由于Windows 3.1是16位操作系统,早已被淘汰,而现行的dll模型其本身就已经实现了进程本地性(不过还是可以通过共享节来实现Win32s中的dll的效果), 因此进程状态其实就是一全局变量。
    MFC中作为本地数据的结构有很多,如_AFX_WIN_STATE、_AFX_DEBUG_STATE、_AFX_DB_STATE等,都是MFC内部自己使用的具有进程本地性的全局变量。
线程本地数据
   具有线程本地性的变量。如上,即同一个指针,不同的线程将会访问不同的内存空间。这点MFC是通过线程本地存储(TLS——Thread Local Storage,其使用方法由于与本文无关,在此不表)实现的。
    MFC中定义了一个结构(_AFX_THREAD_STATE)以记录某些线程级的全局变量,如最近一次的模块状态指针,最近一次的消息等。
模块线程状态
    MFC中 定义的一个结构(AFX_MODULE_THREAD_STATE),其实例即具有线程本地性又具有模块本地性。也就是说不同的线程从同一模块中和同一线 程从不同模块中访问MFC库函数都将导致操作不同的内存空间。其应用在AFX_MODULE_STATE中,记录一些线程相关但又模块级的数据,如本文的 重点——窗口句柄映射。

包装类对象和句柄映射
   句 柄映射——CHandleMap,MFC提供的一个底层辅助类,程序员是不应该直接使用它的。其有两个重要的成员变量:CMapPtrToPtr m_permanentMap, m_temporaryMap;。分别记录永久句柄绑定和临时句柄绑定。前面说过,MFC使用一个映射将窗口句柄和其包装类的实例绑定在一起, m_permanentMap和m_temporaryMap就是这个映射,分别映射永久包装类对象和临时包装类对象,而在前面提到过的 AFX_MODULE_THREAD_STATE中就有一个成员变量:CHandleMap* m_pmapHWND;(之所以是CHandleMap*是使用懒惰编程法,尽量节约资源)以专门完成HWND的绑定映射,除此以外还有如 m_pmapHDC、m_pmapHMENU等成员变量以分别实现HDC、HMENU的绑顶映射。而为什么这些映射要放在模块线程状态而不放在线程状态或 模块状态是很明显的——这些包装类包装的句柄都是和线程相关的(如HWND只有创建它的线程才能接收其消息)且这个模块中的包装类对象可能不同于另一个模 块的(如包装类是某个DLL中专门派生的一个类,如a.dll中定义的CAButton的实例和b.dll中定义的CBButton的实例如果同时在一个 线程中。此时线程卸载了a.dll,然后CAButton的实例得到消息并进行处理,将发生严重错误——类代码已经被卸载掉了)。
包装类存在的意义有二:包装对HWND的操作以加速代码的编写和提供窗口子类化(不是超类化)的效果以派生窗口类。包装类对象针对线程分为两种:永久包装类对象(以后简称永久对象)和临时包装类对象(以后简称临时对象)。临时对象的意义仅仅只有包装对HWND的操作以加速代码编写,不具有派生窗口类的功能。永久对象则具有前面说的包装类的两个意义。
   
在创建窗口时(即CWnd::CreateEx中),MFC通过钩子提前(WM_CREATE和WM_NCCREATE之前)处理了通知,用AfxWndProc子类化了创建的窗口并将对应的CWnd*加入当前线程的永久对象的映射中,而在AfxWndProc中,总是由CWnd::FromHandlePermanent(获得对应HWND的永久对象)得到当前线程中当前消息所属窗口句柄对应的永久对象,然后通过调用得到的CWnd*的WindowProc成员函数来处理消息以实现派生窗口类的效果。这也就是说永久对象具有窗口子类化的意义,而不仅仅是封装HWND的操作。
   
要将一个HWND和一个已有的包装类对象相关联,调用CWnd::Attach将此包装类对象和HWND映射成永久对象(但这种方法得到的永久对象不一定具有子类化功能,很可能仍和临时对象一样,仅仅起封装的目的)。如果想得到临时对象,则通过CWnd::FromHandle这个静态成员函数以获得。临时对象之所以叫临时,就是其是由MFC内部(CHandleMap::FromHandle)生成,其内部(CHandleMap::DeleteTemp)销毁(一般通过CWinThread::OnIdle中调用AfxUnlockTempMaps)。因此程序员是永远不应该试图销毁临时对象的(即使临时对象所属线程没有消息循环,不能调用CwinThread::OnIdle,在线程结束时,CHandleMap的析构仍然会销毁临时对象)。

原因
    为什么要分两种包装类对象?很好玩吗?注意前面提过的窗口模型——只能通过消息机制和窗口交互。注意,也就是说窗口是线程安全的实例。窗口过程的编写中不用考虑会有多个线程同时访问窗口的状态。如果不使用两种包装类对象,在窗口创建的钩子中通过调用SetProp将创建的窗口句柄和对应的CWnd*绑定,不一样也可以实现前面说的窗口句柄和内存块的绑定?
    CWnd
的派生类CA,具有一个成员变量m_BGColor以决定使用什么颜色填充底背景。线程1创建了CA的一个实例a,将其指针传进线程2,线程2设置a.m_BGColor为红色。这已经很明显了,CA::m_BGColor不是线程安全的,如果不止一个线程2,那么a.m_BGColor将会出现线程访问冲突。这严重违背窗口是线程安全的这个要求。因为使用了非消息机制与窗口进行交互,所以失败。
   
继续,如果给CA一个公共成员函数SetBGColor,并在其中使用原子操作以保护m_BGColor,不就一切正常了?呵,在CA::OnPaint中,会两次使用m_BGColor进行绘图,如果在两次绘图之间另一线程调用CA::SetBGColor改变了CA::m_BGColor,问题严重了。也就是说不光是CA::m_BGColor的写操作需要保护,读操作亦需要保护,而这仅仅是一个成员变量。
   
那么再继续,完全按照窗口本身的定义,只使用消息与它交互,也就是说自定义一个消息,如AM_SETBGCOLOR,然后在CA::SetBGColor中SendMessage这个消息,并在其响应函数中修改CA::m_BGColor。完美了,这是即符合窗口概念又很好的设计,不过它要求每一个程序员编写每一个包装类时都必须注意到这点,并且最重要的是,C++类的概念在这个设计中根本没有发挥作用,严重地资源浪费。
   
因此,MFC决定要发挥C++类的概念的优势,让包装类对象看起来就等同于窗口本身,因此使用了上面的两种包装类对象。让包装类对象随线程的不同而不同可以对包装类对象进行线程保护,也就是说一个线程不可以也不应该访问另一个线程中的包装类对象(因为包装类对象就相当于窗口,这是MFC的目标,并不是包装类本身不能被跨线程访问),“不可以”就是通过在包装类成员函数中的断言宏实现的(在CWnd::AssertValid中),而“不应该”前面已经解释地很清楚了。因此本文开头的断言失败的根本原因就是因为违反了“不可以”和“不应该”。
   
虽然包装类对象不能跨线程访问,但是窗口句柄却可以跨线程访问。因为包装类对象不仅等同于窗口,还改变了窗口的交互方式(这也正是C++类的概念的应用),使得不用非得使用消息机制才能和窗口交互。注意前面提到的,如果跨线程访问包装类对象,而又使用C++类的概念操作它,则其必须进行线程保护,而“不能跨线程访问”就消除了这个问题。因此临时对象的产生就只是如前面所说,方便代码的编写而已,不提供子类化的效果,因为窗口句柄可以跨线程访问。

解决办法
    已经了解失败的原因,因此做如下修改:
DWORD WINAPI ThreadProc( void *pData )  //
线程函数(比如用于从COM口获取数据)
{
    //
数据获取循环
    //
数据获得后放在变量i中
    CAbcDialog *pDialog = static_cast< CAbcDialog* >(
                              CWnd::FromHandle( reinterpret_cast< HWND >( pData ) ) );
    ASSERT_VALID( pDialog );  //
此处可能断言失败
    pDialog->m_Data = i;      //
这是不好的设计,详情可参看我的另一篇文章:《语义的需要》
    pDialog->UpdateData( FALSE );  // UpdateData
内部ASSERT_VALID( this )可能断言失败
    …
}
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    //
其他初始化代码
    CreateThread( NULL, 0, ThreadProc, m_hWnd, 0, NULL );  //
创建线程
    return TRUE;
}
   
之所以是“可能”,因为这里有个重点就是临时对象是HWND操作的封装,不是窗口类的封装。因此所有的HWND临时对象都是CWnd的实例,即使上面强行转换为CAbcDialog*也依旧是CWnd*,所以在ASSERT_VALID里调用CAbcDialog::AssertValid时,其定义了一些附加检查,则可能发现这是一个CWnd的实例而非一个CAbcDialog实例,导致断言失败。因此应将CAbcDialog全部换成CWnd,这下虽然不断言失败了,但依旧错误(先不提pDialog->m_Data怎么办),因为临时对象是HWND操作的封装,而不幸的是UpdateData只是MFC自己提供的一个对话框数据交换的机制(DDX)的操作,其不是通过向HWND发送消息来实现的,而是通过虚函数机制。因此在UpdateData中调用实例的DoDataExchange将不能调用CAbcDialog::DoDataExchange,而是调用CWnd::DoDataExchange,因此将不发生任何事。
   
因此合理(并不一定最好)的解决方法是向CAbcDialog的实例发送一个消息,而通过一个中间变量(如一全局变量)来传递数据,而不是使用CAbcDialog::m_Data。当然,如果数据少,比如本例,就应该将数据作为消息参数进行传递,减少代码的复杂性;数据多则应该通过全局变量传递,减少了缓冲的管理费用。修改后如下:
#define AM_DATANOTIFY ( WM_USER + 1 )
static DWORD g_Data = 0;
DWORD WINAPI ThreadProc( void *pData )  //
线程函数(比如用于从COM口获取数据)
{
    //
数据获取循环
    //
数据获得后放在变量i中
    g_Data = i;
    CWnd *pWnd = CWnd::FromHandle( reinterpret_cast< HWND >( pData ) );
    ASSERT_VALID( pWnd );  //
本例应该直接调用平台SendMessage而不调用包装类的,这里只是演示
    pWnd->SendMessage( AM_DATANOTIFY, 0, 0 );
    …
}
BEGIN_MESSAGE_MAP( CAbcDialog, CDialog )
    …
    ON_MESSAGE( AM_DATANOTIFY, OnDataNotify )
    …
END_MESSAGE_MAP()
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    //
其他初始化代码
    CreateThread( NULL, 0, ThreadProc, m_hWnd, 0, NULL );  //
创建线程
    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 );
}
 
“线程安全”是一个什么概念?
以前常听高手告诫MFC对象不要跨线程使用,因为MFC不是线程安全的。比如CWnd对象不要跨线程使用,可以用窗口句柄(HWND)代替。CSocket/CAsyncSocket对象不要跨线程使用,用SOCKET句柄代替.那么到底什么是线程安全呢?什么时候需要考虑?如 果程序涉及到多线程的话,就应该考虑线程安全问题。比如说设计的接口,将来需要在多线程环境中使用,或者需要跨线程使用某个对象时,这个就必须考虑了。关 于线程安全也没什么权威定义。在这里我只说说我的理解:所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
    一般而言“线程安全”由多线程对共享资源的访问引起。如果调用某个接口时需要我们自己采取同步措施来保护该接口访问的共享资源,则这样的接口不是线程安全的.MFC和STL都不是线程安全的. 怎样才能设计出线程安全的类或者接口呢?如果接口中访问的数据都属于私有数据,那么这样的接口是线程安全的.或者几个接口对共享数据都是只读操作,那么这样的接口也是线程安全的.如果多个接口之间有共享数据,而且有读有写的话,如果设计者自己采取了同步措施,调用者不需要考虑数据同步问题,则这样的接口是线程安全的,否则不是线程安全的。
 
 
多线程的程序设计应该注意些什么呢
1、尽量少的使用全局变量、static变量做共享数据,尽量使用参数传递对象。被参数传递的对象,应该只包括必需的成员变量。所谓必需的成员变量,就是必定会被多线程操作的。很多人图省事,会把this指针(可能是任意一个对象指针)当作线程参数传递,致使线程内部有过多的操作权限,对this中的参数任意妄为。整个程序由一个人完成,可能会非常注意,不会出错,但只要一转手,程序就会面目全非。当两个线程同时操作一个成员变量的时候,程序就开始崩溃了,更糟的是,这种错误很难被重现。(我就在郁闷这个问题,我们是几个人,把程序编成debug版,经过数天使用,才找到错误。而找到错误只是开始,因为你要证明这个bug被修改成功了,也非常困难。)其实,线程间数据交互大多是单向的,在线程回调函数入口处,尽可能的将传入的数据备份到局部变量中(当然,用于线程间通讯的变量不能这么处理),以后只对局部变量做处理,可以很好的解决这种问题。
2、在MFC中请慎用线程。因为MFC的框架假定你的消息处理都是在主线程中完成的。首先窗口句柄是属于线程的,如果拥有窗口句柄的线程退出了,如果另一个线程处理这个窗口句柄,系统就会出现问题。而MFC为了避免这种情况的发生,使你在子线程中调用消息(窗口)处理函数时,就会不停的出Assert错误,烦都烦死你。典型的例子就时CSocket,因为CSocket是使用了一个隐藏窗口实现了假阻塞,所以不可避免的使用了消息处理函数,如果你在子线程中使用CSocket,你就可能看到assert的弹出了。
3、不要在不同的线程中同时注册COM组件。两个线程,一个注册1.ocx, 2.ocx, 3.ocx, 4.ocx; 而另一个则注册5.ocx, 6.ocx, 7.ocx, 8.ocx,结果死锁发生了,分别死在FreeLibrary和DllRegisterServer,因为这8个ocx是用MFC中做的,也可能是MFC的Bug,但DllRegisterServer却死在GetModuleFileName里,而GetModuleFileName则是个API唉!如果有过客看到,恰巧又知道其原因,请不吝赐教。
4、不要把线程搞的那么复杂。很多初学者,恨不能用上线程相关的所有的函数,这里互斥,那里等待,一会儿起线程,一会儿关线程的,比起goto语 句有过之而无不及。好的多线程程序,应该是尽量少的使用线程。这句话怎么理解呐,就是说尽量统一一块数据共享区存放数据队列,工作子线程从队列中取数据, 处理,再放回数据,这样才会模块化,对象化;而不是每个数据都起一个工作子线程处理,处理完了就关闭,写的时候虽然直接,等维护起来就累了。
高压控制单元与接触器 —— BMS电池的“高压大门” 高压接触器在BMS电池管理系统中扮演着“高压大门”的角色,负责控制电池包与整车高压母线的连接与隔离。接触器的工作原理基于线圈产生的磁力吸合接点,通过主触点接通高压电流路径,辅助触点检测状态,灭弧结构保护接点。预充电路的设计是为了避免直接连接大电容时产生的浪涌电流冲击,通过预充电阻逐步充电,确保系统安全上电。接触器控制策略包括顺序控制、辅助触点检测、温度监控、故障断电和背靠背MOS辅助控制,以确保系统的高效和安全运行。工程师在选择接触器型号、控制线圈、检测状态、软件冗余控制和预充失败保护等方面提供了具体建议。 阅读详情

相关推荐

Unity WebGL发布避坑指南:从内存设置到字体打包,一次搞定所有常见问题

本文详细解析Unity WebGL发布的完整流程,从内存设置到字体打包,提供全面的避坑指南。涵盖关键检查清单、内存与性能优化配置、资源处理专项优化及平台适配技巧,帮助开发者高效解决常见问题,确保项目顺利发布。

weixin_28693309的博客 147

MFC 多线程编程总结

MFC 多线程编程总结

小黄的博客 1万+

K230目标识别数字提问:模型只能识别近距离的目标,稍远一点就无法正确检测...如何解决?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1899

MFC多线程

1.MFC多线程简介 MFC对多线程进行了一层简单的封装,在Visual C++中每个线程都是从CWinThread类继承而来的。每一个应用程序的执行都有一个主线程,这个主线程也是从CWinThread类继承而来的。可以利用CWinThread对象创建应用程序执行的其它线程。 MFC用CWinThread对象来表示所有线程。利用MFC可以创建两种线程,分别称之为工作者线程和用户界面线程

weicomm的专栏 1671

轨迹优化 | 图解欧氏距离场与梯度场算法(附ROS C++/Python实现)

距离场在运动规划中则常用于地图建模和轨迹梯度优化。本文图解欧氏距离场EDT的算法原理,并基于双线性插值给出欧氏梯度场的公式推导,并给出ROS C++和Python的算法实现加深理解

FRIGIDWINTER的博客 4646

【unity实战】Unity游戏开发:如何用ScriptableObject与序列化多态实现可复用的模块化效果系统?

本文介绍了两种实现卡牌游戏效果系统的模块化设计方案。方案一使用ScriptableObject创建独立的效果资产,通过组合实现不同效果,优点是数据可复用、可视化编辑,缺点是同类不同参数效果需创建大量SO文件。方案二利用SerializeReference特性直接嵌套效果实例,避免SO文件过多,但需要插件支持编辑器多态显示。两种方案都将效果与卡牌解耦,提高了代码复用性和扩展性,解决了硬编码带来的重复、维护困难和扩展不便等问题。开发者可根据项目需求选择合适方案,或结合两者优势构建更灵活的系统。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 436

MFC多线程技术

MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。 工作者线程没笑消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程之外的用户输入,响应用户及系统产生的事件和消息等。但对于Win32的API编程而言,这两种编程是没有区别的,他们都只需要线程...

weixin_34007886的博客 1141

SCADA|测试KingSCADA4.0信创版采集汇川PLC AC810数据

摘要:雷工分享KingSCADA4.0信创版采集汇川PLC数据的配置过程。由于项目繁忙,笔记更新延迟。信创产业要求采用国产化产品,此次通过ModbusTCP协议连接AC810 PLC。主要步骤包括:新建IOServer应用(需厂家协助从3.8升级至4.0)、安装ModbusTCP驱动、配置设备地址(192.168.1.88:5021)、运行IOServer并通过网页监控变量。尽管信创版操作体验较差,但出于国产化战略需要仍需使用。雷工表示理解产品初期不足,相信未来会逐步优化。2025年4月11日记录于四川绵阳

做有价值的事,积累有价值的笔记! 863

MFC中创建和使用线程的方法

有关创建线程的问题有三种方法: 1.C语言函数,调用_beginthread(); 2.API函数,调用CreateThread(); 3.MFC函数,调用AfxBeginThread(); 推荐使用MFC函数AfxBeginThread(); 利用MFC里的AfxBeginThread函数能很方便地创建线程以及对线程进行等待、唤醒等操作。 1、函数原型 CWinThread* AfxBeg...

u011555996的博客 1万+

【目标跟踪】基于Kalman滤波实现无人机运动轨迹跟踪附matlab代码

1 简介 在近代控制中,需要对某一特定的运动目标进行跟踪监测,并需要对其下一时间的运动位置,运动方向,速度等信息进行预先估算,才能达到对目标的即时控制.本文简要讨论了用卡尔曼滤波算法对单个目标航迹进行预测,并借助于Matlab仿真工具,对实验的效果进行评估. 2 部分代码 clc; close all; %initial state xo=25; vox=2; %Observations **** % X-Direction Calcula

qq_59747472的博客 4731

华为Ascend C算子开发能力认证(中级)(个人记录,有完整代码,已通过认证,仅供参考)

本文介绍了在昇腾NPU环境下实现Sigmoid算子的完整流程。首先需要在华为云ModelArts-Notebook或开发者套件上搭建环境,要求CANN版本为8.0.0.beta1。主要内容包括:1)实现SigmoidCustom算子的kernel侧核函数代码;2)完成host侧Tiling结构体实现;3)支持Float16数据类型。代码实现部分详细展示了核函数计算流程、数据拷贝和牛顿迭代优化方法。最后介绍了编译部署流程,包括环境配置、代码上传、编译执行等步骤,并提供了提交规范说明。该实现通过调用Ascend

qq_73044452的博客 1107

MFC多线程编程注意事项 (转)

MFC多线程编程注意事项 PeterLee整理 2008-05-26 1.        表现——错误示例 关于启动线程时传输窗口对象(指针?句柄?)的问题:         在选择菜单中的开始线程后:     void   cmainframe::onmenu_start()     {           ...           afxbeginthread

oushaojun2的专栏 815

【思考模型框架】采用双钻模型:发散(桌面研究/用户访谈)→收敛(需求归类/优先级排序),输出需求规格书、追踪矩阵及产品待办清单。

设计思维双钻模型是一种以用户为中心的需求管理方法,通过"发散-收敛"的双循环确保问题定位准确、方案切实可行。第一钻石(探索→定义)聚焦"找对问题",运用用户访谈、田野调查等方法挖掘真实需求;第二钻石(开发→交付)解决"做对方案",通过原型测试、A/B测试验证可行性。该模型强调低保真验证、多团队协作和真实用户测试,避免资源浪费与伪需求开发。典型应用场景包括产品功能优化、用户体验提升等需要精准落地的需求场景。核心价值在于建立从用户痛点到解决方案的可控闭

奶爸的编程之路,也就一周冷个三天 319

c语言mfc多线程编程,C++ 关于MFC多线程编程的注意事项

在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread 来创建一个工作线程,看一下这个函数的说明:CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,int nPriority = THREAD_PRIORITY_NORMAL,UINT nStackSize = 0,DWORD...

weixin_42463165的博客 235

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: 游戏开发中常用的设计模式
下一篇: 【分析】魔兽争霸3的MPQ文件及模型格式分析
olncy
博客等级 码龄20年 30粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值