assert()函数用法总结

assert()函数用法总结


  assert宏的原型定义在中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include
void assert( int expression );
  assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。请看下面的程序清单badptr.c:
复制代码
#include
#include
#include
int main( void )
{
       FILE *fp;
    
       fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
       assert( fp );                           //所以这里不会出错
       fclose( fp );
    
       fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
       assert( fp );                           //所以这里出错
       fclose( fp );                           //程序永远都执行不到这里来
       return 0;
}
复制代码
[root@localhost error_process]# gcc badptr.c 
[root@localhost error_process]# ./a.out 
a.out: badptr.c:14: main: Assertion `fp' failed.
  已放弃使用assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:
#include
#define NDEBUG
#include


用法总结与注意事项:
  1)在函数开始处检验传入参数的合法性如:
复制代码
int resetBufferSize(int nNewSize)
{
  //功能:改变缓冲区大小,
  //参数:nNewSize 缓冲区新长度
  //返回值:缓冲区当前长度 
  //说明:保持原信息内容不变     nNewSize<=0表示清除缓冲区
  assert(nNewSize >= 0);
  assert(nNewSize <= MAX_BUFFER_SIZE);
  ...
}
复制代码
  
  2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败,如:
  不好:
assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
  好:
assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);
  3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题,如:


  错误:
assert(i++ < 100);
  这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。


  正确:
 assert(i < 100);
 i++;
  4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感。


  5)有的地方,assert不能代替条件过滤。


一些通过FromHandle()或者Create...()获得的指针需要delete吗? 
   我知道很多gdi对象在Create后需要使用DeleteObject()释放其句柄,但是否需要delete呢? 
  
  我给你说三种情况,但我们首先做一个假设,就是MFC封装的GDI类在析构时没有做任何动作,也就是说,它是个纯粹的“简单封装”,那么: 
   1。pBmp->Detach将使C++的对象与GDI对象分离开来,但二者都没有释放。此时必须分别用delete pBmp和DeleteObject将二者分别释放; 
   2。pBmp->DeleteObject将使GDI对象被释放,而C++对象本身不会释放。你可以用Attach重新使其与某个GDI对象关联,或者,用delete将其释放; 
   3。delete pBmp(注意,我们假定析构时不调用DeleteObject)将使C++对象消亡,而对应的GDI对象依然存在。要使GDI对象释放,必须再次调用DeleteObject。 
   注意上面说的第三种情况,在实际的MFC实现中,为了简化程序员的负担,在C++对象析构时,与之关联的GDI对象也会释放。我之所以这样假定,是为了让你明白C++对象与GDI对象的区别。 
   还有,new的东西不是在栈里,而是在堆里。




GetSafeHwnd()和GetSafeHandle()的主要区别:
1.使用者不同:
(1)窗体使用:
GetSafeHwnd()用于获取窗体的安全句柄(即HWND),有了HWND我们就可以方便的对HWND指向的窗体进行所需的操作了;
(2)GDI对象使用:
GetSafeHandle(),用于获取GDI对象的句柄。


注意:在使用指针时强烈建议这么做:
// pSomeWnd 为一个窗体的指针
if ( NULL != pSomeWnd && NULL != pSomeWnd->GetSafeHwnd())
{
  // do something.
   }
//////////////////////////////////////////////////////////////////////
补充知识:
内存句柄与指针的区别:
(1).句柄其实就是指针,但是他和指针最大的不同是:给你一个指针,你可以通过这个指针做任何事情,也许是好事,也许是通过这个指针破坏内存,干一些捣乱的事情。这个我想大家都会碰到过,因为乱用指针可能会导致程序崩溃。
句柄就没有这个缺点,通过句柄,你只能干一些windows允许你干的事情(例如调用一些api函数等等),没有了指针的随意。
(2).句柄是一些表的索引也就是指向指针的指针。 句柄和指针都是地址,句柄是windows编程的一个关键性的概念,编写windows应用程序总是要和各种句柄打交道。
所谓句柄,就是一个唯一的数,用以标识许多不同的对象类型,如窗口、菜单、内存、画笔、画刷等。在win32里,句柄是指向一个“无类型对象”(void)的指针,也就是一个4字节长的数据。
无论它的本质是什么,句柄并不是一个真正意义上的指针。
从构造上看,句柄是一个指针,尽管它没有指向用于存储某个对象的内存位置。事实上,句柄指向一个包含了对该对象进行引用的位置。
句柄的声明是这样的:
typedef void handle
由于windows是一个多任务操作系统,它可以同时运行多个程序或一个程序的多个副本。这些运行的程序称为一个实例。为了对同一程序的多个副本进行管理,windows引入了实例句柄。windows为每个应用程序建立一张表,实例句柄就好象是这张表的一个索引。
 
不同之处还在于:
1、句柄所指的可以是一个很复杂的结构,并且很有可以是与系统有关的,比如说上面所说的线程的句柄,它指向的就是一个类或者结构,他和系统有很密切的关系,当一个线程由于不可预料的原因而终止时,系统就可以回收它所占用的资源,如cpu,内存等等。反过来想可以知道,这个句柄中的某一些项,是与系统进行交互的。由于windows系统是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存等。 
2、指针它也可以指向一个复杂的结构,但是通常是用户定义的,所有的必需的工作都需用户完成,特别是在删除的时候。 但在vc++6.0中也有一些指针,它们都是在处理一些小问题才用的,如最常见的字符的指针 它也是需要用户处理的,譬如你动态分配了内存;但是cstring就不要用户处理了,它其实是vc++中的一个类,所有的操作都由成员函数完成,产生(分配)由构造函数,删除(回收)由析构函数完成。


附注:获得窗口句柄三种方法
1.HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName);
 
  HWND FindWindowEx( HWND hwndParent, HWND hwndChildAfter,
                        LPCTSTR lpszClass, LPCTSTR lpszWindow );
2.HWND WindowFromPoint( 
          POINT Point);//获得当前鼠标光标位置的窗口hwnd
3.BOOL CALLBACK EnumChildProc( HWND hwnd, LPARAM lParam);
 
BOOL EnumChildWindows( HWND hWndParent,
                       WNDENUMPROC lpEnumFunc,
                       LPARAM lParam );
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam ); 
BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam );


补充知识:指针和句柄之间的转换
a.由指针获得句柄 
      CWND* pwnd ;
      HWND  hwnd ;
      hwnd = pwnd-> GetSafeHwnd();
b.由句柄得到指针:
      CWND* pwnd = FromeHandle(hmyhandle);
      pwnd-> SetWindowText(" hello world!" ) ;
mfc类中有的还提供了标准方法,比如window 句柄 : 
static CWND pascal FromHandle( HWND hwnd )
HWND GetSafeHwnd( ) const
对于位图: 
static cbitmap pascal fromhandle( hbitmap hbitmap )
static cgdiobject pascal fromhandle( hgdiobj hobject )
hgdiobj getsafehandle( ) const


有人说句柄就是一个标示,一个id号,是错误的。一个id号可以包括多个资源,比如说单文档中的idr_mainframe,一般是指在硬盘上的资源。但是当把硬盘上的资源调入内存以后,将有一个句柄指向它,但是句柄只能指向一个资源。而且句柄知道所指的内存有多大。而指针指向地址,它不知道分配的内存有多大。
但是如果你定义一个句柄,然后在vc里面右击鼠标,选择" go to definition of handle,你会发现它的本质就是一个指针,但是它的作用不同于指针。
句柄是个指针,指向一块内存,但至于这块内存跟句柄所标识的对象是怎么联系起来的,调用者不需要清楚,调用者只需要知道,这个句柄联系着一个win32对象。
    句柄是物理地址,可以跨进程传递,例如,handle ha进程a的一个窗口,你可以在进程b中利用一个跟ha相等的值(相等就是说它们强制转成int32的值相等)初始化一个句柄,利用这个句柄你可以对进程a的那个对象进行操作,例如movewindow showwindow等。
句柄包含了一些引用计数之类的东西,所以我的上一点说的给句柄赋值是不安全的,windows api提供了一些函数,可以对句柄进行操作。


    句柄就是受限的指针。
它是由操作系统管理的,你不能通过它直接存取操作系统创建的数据结构(应该先获取对应的指针)。
操作系统在创建一个对象(如gdi, file)等的时候,它会为这个对象上下文context保留一块数据结构,然后把它放在一张全局表中。句柄就是这块数据结构在表中的索引。




这个就是由于ASCII和Unicode的问题引起的,第一个程序就遇到这个问题,还是很幸运的,以后就会注意了!


 


注意到了MessageBox和TextOut都是有多种表示的,它们里面的参数直接加上L就能够解决!在最开始我就给都加上了L,但是还是出现了问题,这个问题就是问题2!


 


2.


 


sprintf函数,这个函数加上后就会出现不匹配的问题,据网友得知,这个函数的宽字符形式为:swprintf


 


我重新定义了szChar,wchar_t szChar;


这样szChar就是宽字符型了,然后用swprintf函数,swprintf(szChar,L"char is %d",wParam);


 


还有一个函数是我自己发现的,呵呵,兴奋!就是strlen这个函数,在这儿当然也是不可以用的,我在书上见到过这个函数的宽字符型,wcslen,然后这样程序就算全部转成了Unicode型的!运行通过,兴奋啊!呵呵,代码贴出来:


#include
#include




LRESULT CALLBACK winmainpro(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);
 
int WINAPI WinMain(
  HINSTANCE hInstance,  // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,      // pointer to command line
  int nCmdShow          // show state of window

{
    WNDCLASS wndcls;
    wndcls.cbClsExtra=0;
    wndcls.cbWndExtra=0;
    wndcls.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
    wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
    wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);
    wndcls.hInstance=hInstance;
    wndcls.lpfnWndProc=winmainpro;
    wndcls.lpszClassName=L"weiwenjing";
    wndcls.lpszMenuName=NULL;
    wndcls.style=CS_HREDRAW|CS_VREDRAW;
    RegisterClass(&wndcls);


    HWND hwnd;
    hwnd=CreateWindow(
  L"weiwenjing",  // pointer to registered class name
  L"weiwenjing", // pointer to window name
  WS_OVERLAPPEDWINDOW,        // window style
  0,                // horizontal position of window
  0,                // vertical position of window
  600,           // window width
  400,          // window height
  NULL,      // handle to parent or owner window
  NULL,          // handle to menu or child-window identifier
  hInstance,     // handle to application instance
  NULL        // pointer to window-creation data
);


    ShowWindow(hwnd,SW_SHOWNORMAL);
    UpdateWindow(hwnd);


    MSG msg;
    while(GetMessage(&msg,hwnd,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }


    return 0;
}


LRESULT CALLBACK winmainpro(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
    switch(uMsg)
    {
    case WM_CHAR:
        wchar_t szChar[20];
        swprintf(szChar,L"char is %d",wParam);
        MessageBox(hwnd,szChar,L"iloveyou",0);
        break;
    case WM_LBUTTONDOWN:
        MessageBox(hwnd,L"left button is checked",L"iloveyou",0);
        HDC hdc;
        hdc=GetDC(hwnd);
        TextOut(hdc,0,50,L"第一个程序,happy !",wcslen(L"第一个程序,happy !"));
        ReleaseDC(hwnd,hdc);
        break;
    case WM_PAINT:
        HDC hDC;
        PAINTSTRUCT ps;
        hDC=BeginPaint(hwnd,&ps);
        TextOut(hDC,0,0,L"windows编程第一课",wcslen(L"windows编程第一课"));
        EndPaint(hwnd,&ps);
        break;
    case WM_CLOSE:
        if(IDYES==MessageBox(hwnd,L"是否真的退出窗口!",L"iloveyou",MB_YESNO))
        {
            DestroyWindow(hwnd);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }
    return 0;
}




 MessageBox函数的第一个参数MSDN上是这样解释的:
int MessageBox(          HWND hWnd,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);
Parameters
 
hWnd
[in] Handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.
 
owner window和parent window有什么区别呢?不指定owner window和指定owner window实质上有什么不同呢?
 
关于owner window和parent window的区别,网上的文章这样介绍:
 
一、概念和区别
 
    在windows系统中,每个窗口对象都对应有一个数据结构,形成一个list链表。系统的窗口管理器通过这个list来获取窗口信息和管理每个窗口。这个数据结构中有四个数据用来构建list,即child、sibling、parent、owner四个域。
    所以我们可以看到,窗口之间的关系有两种:owner-owned 关系和 parent-child关系。前者称之为拥有/被拥有关系,后者称之为父/子关系。在这篇文字中,我把owner窗口称之所有者窗口。换句话说,一个窗口在有一个父窗口(parent)的同时,还可能被不同的窗口拥有(owner),也可以有自己的子窗口(child)。在MFC 的CWnd类中,所有者窗口保存在m_hWndOwner成员变量中,父窗口则保存在m_hParent中,但是这两个值并不一定和窗口对象数据结构中的值相对应。
  
    窗口之间的关系,决定了窗口的外在表现。比如显示、销毁等。
 
    如果一个窗口数据的owner域非NULL,则它和该窗口建立了owner-owned 关系,拥有关系决定了:
    (1)被拥有的窗口永远显示在拥有它的那个窗口的前面;
    (2)当所有者窗口最小化的时候,它所拥有的窗口都会被隐藏;
    (3)当所有者窗口被销毁的时候,它所拥有的窗口都会被销毁。
    需要注意的是,隐藏所有者窗口并不会影响它所拥有的窗口的可见状态。比如:如果窗口 A 拥有窗口B,窗口B拥有窗口C,则当窗口A最小化的时候,窗口B被隐藏,但是窗口 C还是可见。
 
 
    如果一个窗口的parent域非NULL,则它和该窗口之间就建立了parent-child关系。父子决定了:
    (1)窗口在屏幕上面的显示位置。父窗口提供了用来定位子窗口的坐标系统,一个子窗口只能显示在它的父窗口的客户区中,之外的部分将被裁减。这个裁减法则决定了如果父窗口不可见,则子窗口肯定不可见。如果父窗口移动到了屏幕之外,子窗口也一样。
    (2)当父窗口被隐藏时,它的所有子窗口也被隐藏。
    (3)父窗口被销毁的时候,它所拥有的子窗口都会被销毁。
     注意!最小化父窗口不会影响子窗口的可见状态,子窗口会随着父窗口被最小化,但是它的WS_VISIBLE属性不会变。
 
    Windows系统为什么要使用两种关系呢?这是为了更加灵活的管理窗口。举个例子:组合框(combobox)的下拉列表框(list box)可以超出组合框的父窗口的客户区,这样有利于显示,因此系统创建该list box的时候,是作为控制台窗口(desktop window)的子窗口,它的父窗口hWndParent是NULL,这样,list box的显示区域是限制在整个屏幕内,但是该list box的所有者却是组合框的第一个非子窗口祖先(比如对话框),当它的所有者窗口销毁后,该 list box自动销毁。
 
    另外,窗口之间消息的传递也和窗口关系有关,通常,一个窗口会把自己的通知消息发送给它的父窗口,但不全是这样,比如,CToolBar发送通知消息给它的所有者窗口而不是父窗口。这样以来,就可以允许工具条作为一个窗口(比如一个 OLE 容器程序窗口)的子窗口的同时,能够给另一个窗口(比如in-place框架窗口)发送消息。至于某类窗口到底是把消息发送给谁,是父窗口还是所有者窗口,microsoft并没有明示。还有,在现场(in-place)编辑的情况下,当一个 server 窗口激活或者失效的时候,框架窗口所拥有的子窗口自动隐藏或者显示,这也是通过直接调用SetOwner函数实现的。
 
 
二、窗口类型的说明和限制
 
(1)控制台窗口(desktop window)。这是系统最早创建的窗口。可以认为它是所有 WS_OVERLAPPED 类型窗口的所有者和父窗口。Kyle Marsh在他的文章“Win32 Window Hierarchy and Styles”中指出,当系统初始化的时候,它首先创建控制台窗口,大小覆盖整个屏幕。所有其它窗口都在这个控制台窗口上面显示。窗口管理器所用的窗口 list中第一个就是这个控制台。它的下一层窗口叫做顶级窗口(top-level),顶级窗口是指所有非child、没有父窗口,或者父窗口是desktop的窗口,它们没有WS_CHILD属性。
 
(2)WS_OVERLAPPED类型的窗口可以显示在屏幕的任何地方。它们的所有者窗口是控制台。
 
     Overlapped 类型的窗口属于顶级窗口,一般作为应用程序的主窗口。不论是否给出了WS_CAPTION、WS_BORDER属性,这类窗口创建后都有标题栏和边框。 Overlapped窗口可以拥有其它顶级窗口或者被其它顶级窗口所拥有。所有overlapped窗口都有WS_CLIPSIBLINGS属性。系统可以自动设置 overlapped窗口的大小和初始位置。
 
    当系统 shuts down的时候,它将销毁所有overlapped类型的窗口。
 
(3)WS_POPUP类型的窗口可以显示在屏幕任何地方,它们一般没有父窗口,但是如果明确调用SetParent,这类窗口也可以有父窗口。
 
     WS_POPUP类型的窗口的所有者是在CreateWindow函数中通过设置hWndParent参数给定的,如果hWndParent不是子窗口,则该窗口就成为这个新的弹出式窗口的owner,否则,系统从hWndParent的父窗口向上找,直到找到第一个非子窗口,把它作为该弹出窗口的 owner。当owner窗口销毁的时候,系统自动销毁这个弹出窗口。
 
     Pop-up类型的窗口也属于顶级窗口,它和 overlapped 窗口的主要区别是弹出式窗口不需要有标题栏,也不必有边框。弹出式可以拥有其它顶级窗口或者被拥有。所有弹出式窗口也都有 WS_CLIPSIBLINGS属性。
 
(4)所有者窗口(owner)只能是 overlapped 或者 pop-up 类型的窗口,子窗口不能是所有者窗口,也就是说子窗口不能拥有其它窗口。
 
    overlapped 或者 pop-up 类型的窗口在拥有其它窗口的同时,也可以被拥有。
 
    在使用CreateWindowEx创建 WS_OVERLAPPED 或者 WS_POPUP类型的窗口时,可以在 hwndParent 参数中给出它的所有者窗口的句柄。如果 hwndParent 给出的是一个child 类型的窗口句柄,则系统自动将新创建窗口的所有权交给该子窗口的顶级父窗口。在这种情况下,参数hwndParent被保存在新建窗口的parent域中,而它的所有者窗口句柄则保存在owner域中。
 
(5)缺省情况下,对话框和消息框属于 owned 窗口,除非在创建它们的时候明确给出了WS_CHILD属性,(比如对话框中嵌入对话框的情形)
否则由系统负责给它们指定owner窗口。需要注意的是,一旦创建了owned类型的窗口,就无法再改变其所有关系,因为WIN32没有没有提供改变窗口所有者的方法。
 
     而且在Win32中,由于有多线程的存在,所以要注意保证父子窗口或者owner/owned 窗口要同属于一个线程。
 
(6)对于 WS_CHILD类型的窗口,它的父窗口就是它的所有者窗口。一个子窗口的父窗口也是在CreateWindow函数中用hWndParent参数指定的。子窗口只能在父窗口的客户区中显示,并随父窗口一起销毁。
     子窗口必须有一个父窗口,这是它和overlapped 以及 pop-up 窗口之间的主要区别。父窗口可以是顶级窗口,也可以是其它子窗口。
 
 
三、几个相关函数的说明
 
(1)获取/设置所有者窗口
 
    win32 API提供了函数GetWindow函数(GW_OWNER 标志)来获取一个窗口的所有者窗口句柄。
    GetWindow(hWnd, GW_OWNER)永远返回窗口的所有者(owner)。对于子窗口,函数返回 NULL,因为它们的父窗口就相当于所有者(注意,是“相当于”)。因为Windows系统没有维护子窗口的所有者信息。
 
    MFC中则是通过如下函数得到所有者窗口指针:
    _AFXWIN_INLINE CWnd* CWnd::GetOwner() const
      { return m_hWndOwner != NULL ? CWnd::FromHandle(m_hWndOwner) : GetParent(); }
    从上述代码我们可以看出,它返回的值和GetWindow返回的有所区别,如果当前窗口没有owner,那么将返回它的父窗口指针。
 
    但是Windows没有提供改变窗口所有者的方法。MFC中则提供了改变所有者的方法:
    _AFXWIN_INLINE void CWnd::SetOwner(CWnd* pOwnerWnd)
      { m_hWndOwner = pOwnerWnd != NULL ? pOwnerWnd->m_hWnd : NULL; }
 
    另外,mfc还提供了CWnd::GetSafeOwner( CWnd* pParent, HWND* pWndTop );函数,可以用来得到参数pParent的第一个非child属性的父窗口指针。如果这个参数是NULL,则返回当前线程的主窗口(通过 AfxGetMainWnd得到)。框架经常使用这个函数查找对话框或者属性页的所有者窗口。
 
(2)获取/设置父窗口
 
     WIN32 API给出了函数GetParent和SetParent。而mfc也是完全封装了这两个函数:
 
    _AFXWIN_INLINE CWnd* CWnd::SetParent(CWnd* pWndNewParent)
      { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::SetParent(m_hWnd,
   pWndNewParent->GetSafeHwnd())); }
 
    _AFXWIN_INLINE CWnd* CWnd::GetParent() const
      { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); }
 
    对于SetParent,msdn里面说明了父子窗口必须是同一个进程的。但是由于窗口句柄是系统全局唯一的,不属于同一个进程的情况下,也可以成功调用,但是后果未知。
    GetParent的返回值比较复杂,对于overlapped类型的窗口,它返回0,对于WS_CHILD类型,它返回其父窗口,对于WS_POPUP 类型,它返回其所有者窗口,如果想得到创建它时所传递进去的那个hwndParent参数,应该用 GetWindowWord(GWW_HWNDPARENT)函数。
 
(3)GetWindowWord(hWnd, GWW_HWNDPARENT)返回一个窗口的父窗口,如果没有,则返回其所有者。
 
(4)上面谈到,当一个owner窗口被最小化后,系统自动隐藏它所拥有的窗口。当owner窗口被恢复的时候,系统自动显示它所拥有的窗口。在这两种情况下,系统都会发送(send)WM_SHOWWINDOW消息给被拥有的窗口。某些时候,我们可能需要隐藏 owned窗口,但并不想最小化其所有者窗口,这时候,可以通过ShowOwnedPopups函数来实现,该函数设置或者删除当前窗口所拥有的窗口的WS_VISIBLE属性,然后发送WM_SHOWWINDOW消息更新窗口显示。
 
看完上文,我们知道了指定owner window的效果。那么不指定owner window呢?经过测试,我发现弹出的是一个非模式对话框。

你可能感兴趣的:(assert()函数用法总结)