编程经验_Visual C++ 6.0_启动程序时伴随主窗口弹出程序说明窗口(1)

来源:互联网 发布:淘宝客推广链接点击数 编辑:程序博客网 时间:2024/05/18 18:43

    想实现这样一个功能:启动主程序的同时弹出一个说明主程序功能的对话框。在主程序对话框的初始化函数里面加上:

    CAboutDlg cad;

    cad.DoModule();

将模态显示出About对话框,在About对话框资源中加一个只读的Edit控件,在控件里写上对程序的说明。

    运行:首先弹出About对话框,不关闭它主程序的对话框初始化函数没有执行完(应该是某个函数没有返回,大概跟对话框是模态的有关,待查),关闭About对话框后才会出现主程序窗口。

    既然这样,我在显示主窗口的主线程中专门起一个线程来显示About对话框,试图同时显示出主窗口和说明窗口。

在OnInitDlg()中增加:

    aboutDlgThread = CreateThread(NULL, 0, showAboutDlg, NULL, 0, NULL);

然后定义一个方法作为线程的入口函数(就像主线程的main()函数):

    void showAboutDlg(){......}

编译报错。原来是线程入口函数必须声明为DWORD WINAPI showAboutDlg(LPVOID lpParameter);的形式。

WINAPI是什么意思?Go to Definition,发现在WINDEF.H中有定义:

#define CALLBACK __stdcall

#define WINAPI __stdcall

#define WINAPIV __cdecl       //WINAPIV在跟VC 6.0配套的MSDN 2001中找不到

    是用来指定函数参数的入栈顺序、由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法(详细的要查MSDN)。

    Win32 api都遵循__stdcall调用约定,而在VC 6.0环境中,默认的编译选项是__cdecl,所以对于那些需要遵循__stdcall调用约定的函数,在声明时必须显式加上__stdcall。WINAPI和CALLBACK宏是为了说明函数是一个windows api或者是回调函数。

    于是乎,把线程入口函数改成:

    DWORD WINAPI showAboutDlg(LPVOID lpPara){......}

    编译还报错:error C2664: 'CreateThread' : cannot convert parameter 3 from 'unsigned long (void *)' to 'unsigned long (__stdcall *)(void *)'    None of the functions with this name in scope match the target type

    貌似还是__stdcall的问题,困惑了。

    网上给了两种解决办法。

    办法一:showAboutDlg()是类的成员函数时声明为static函数

    结果:编译报错'static' should not be used on member functions defined at file scope

    办法二:将showAboutDlg()定义在类外面,作为全局函数

    结果:编译通过,运行error:Debug assertion error

    编译成release版,运行通过,结果正常。

    疑点重重啊,试试非模态对话框。果然非模态对话框的方法不是blocking式的。使用非模态对话框要注意两个问题:

    第一:要调用ShowWindow()将非模态对话框显式出来,而DoModule()方法有显式功能,所以模态对话框不需要调用这个函数。

    第二:既然非模态对话框是non-blocking的,所以要注意对象生命周期的问题。如果在OnInitDlg()中使用局部对象,主窗口初始化完成后这个对象就被销毁了,所以还是看不到说明窗口。有两种解决办法:

    第一:使用指针,在堆上new一个非模态对话框对象。在堆上分配的内存与程序的整个生命周期是一致的。但是指针变量是局部变量的话,可能会导致无法引用的对象,对象还存在,而指向它的指针变量生命周期已经结束了。

    第二:将非模态对话框对象定义为主窗口类的成员。才发现在VC 6.0自动生成的代码中,About对话框这个类的定义是在.cpp文件中的,现在想在.h文件中主窗口类的定义里面持有About对话框对象,我一时又忘记了怎么在类的定义前面作类的声明,只好把整个About对话框类都Ctrl V Ctrl C到.h文件中。

    运行,OK,程序主窗口和说明窗口同时显示出来了。

    美中不足:虽然能对程序主窗口进行操作,但说明窗口始终盖在主窗口上面,不能让主窗口显示在最前。而且程序说明窗口的位置不在主窗口正中间,不知道位置怎么调。

    后来我记起来在《The C++ Programming Language(Special Edition)》书里面看到,C++中类的定义其实只是类的声明,所以C++中不能在类的定义里面给成员变量赋初值(JAVA可以),叫类的定义完全是历史的原因,这样有一个好处,避免很多头文件互相包含的时候类的定义重复。声明是不会重复的,但是定义不能重复。于是,我在.h文件里面把About对话框这个类的定义复制粘贴了两遍,然后编译,报错:类重复定义。一滴汗。

    难道是我记错了或者理解错了?

    也可能是VC 6.0不支持Bjarne Stroustrup说的话,VC中避免重复定义的典型做法是用条件编译将整个文件保护起来。

    问题源源不断,当我想修改About对话框,增加一些对程序的说明的时候发现,因为我改了About对话框类定义的位置,不能再按常规地在资源编辑器中使用类向导了,里面一片空白。当然我可以先修改好了About对话框之后再把About类定义移到.h文件中去,还可以仔细研究下在资源编辑器中增加控件和用类向导增加成员变量时代码里面到此做了哪些变化,以方便以后的修改。不过这样做不好,我记得有一次在华为面试的时候考官问我们:“你们平时编程的时候有没有发现什么编译器的BUG?或者是其它什么问题?”下面7个应聘的全傻掉了。其实Visual C++ 6.0使用的时候问题还蛮多的,特别是手动修改了它自动生成的代码,删东西的时候常常删不干净,留下一堆垃圾。高手当然可以全手动地把代码改得更好,什么makefile、RC都可以手动改,我等菜鸟还是慢慢来。底层机制固然要尽量搞清楚,但还是要多倚重集成工具的便利。

    于是,我把About对话框的类定义又拷回来(原先只是注释掉了),然后到网上找怎么声明一个后面才定义的类:

    class 类名;

    编译,报错:uses undefined class 'CAboutDlg'

    CAboutDlg类是在AGADlg.cpp中定义的,我在AGADlg.h中CAGADlg类定义里面要用到CAboutDlg类,我在.h文件的类定义前面直接声明:

    class CAboutDlg;

    编译还是报错?!按照《C++ Primer》的说法,语法上应该是没有问题的啊···

    用关键字“error C2079”百度到了答案,确实是很细节的一个问题。结论是:在类的定义之前不可以使用类名来定义一个对象,只能用来定义引用或者指针。因为声明class 类名;只是告诉了编译器,把ClassName当成一个type来使用,但是在类的定义之前编译器不知道应该为这个类的对象分配多大的存储空间,所以只能用来定义引用或指针,因为不论指向多大的对象,引用变量或指针变量的大小是一定的。这大概是为什么一个类/结构体的定义里面不能有一个自身的对象作为成员,却能有一个指向自身对象的指针作为成员。

    道理想清楚了很简单,需要注意的是当使用模板时,比如list<myClass>:如果myClass已经定义了,当然没有问题。但如果myClass还没有定义,仅仅作了声明,就会出问题,因为不知道扔到容器里面的对象大小是多大。

    换成指针,编译通过。运行报错···xxx内存不能为“written”。

    调试,定位到将AboutDlg创建出来的语句cad->Create(IDD_ABOUTBOX, NULL);

    原来又是一个低级错误,在CAGADlg类定义中增加CAboutDlg对象的指针作为成员的时候,忘记了“同时”对这个成员进行初始化。代码里调一调再运行试一试(这就叫调试),OK,没问题。

    强调一下,在C++中增加一个变量的时候要马上就初始化,这应该成为编程习惯。C++的设计者提出可以在任何能够写一条语句的地方声明一个变量,就是为了将声明变量和初始化绑到一起。JAVA会给类的成员变量提供默认初始值,C++则不会。忘记变量初始化是用C++编程时要特别注意的问题。

    在MSDN里查Create()的时候看到这样一句话:

    Use the WS_VISIBLE style in the dialog-box template if the dialog box should appear when the parent window is created.

    似乎要实现我开头说的功能,只需要改一下dialog-box的属性就OK了。绕了一大圈,发现了很多编程中的细节问题,呵呵呵。

    怎么改它的属性?

 

 

    后来查《C++ Primer》,果然是我理解错了:

    A class is completely defined once the closing curly brace appears. Once the class is defined, all the class members are known. The size required to store an object of the class is known as well. A class may be defined only once in a given source file.

    When a class is defined in multiple files, the definition in each file must be identical. By putting class definitions in header files, we can ensure that a class is defined the same way in each file that uses it. By using header guards, we ensure that even if the header is included more than once in the same file, the class definition will be seen only once.

    It is possible to declare a class without defining it:

        class MyClass;

    This declaration, sometimes referred to as a forward declaration, introduces the name MyClass into the program and indicates that MyClass refers to a class type. After a declaration and before a definition is seen, the type MyClass is an incompete type -- it's known that MyClass is a type but not known what members that type contains.

    Note : An incompete type can be used only in limited ways. Objects of the type may not be defined. An incompete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a parameter or return type.

    A class must be fully defined before objects of that type are created. The class must be defined -- and not just declared -- so that the compiler can know how much storage to reserve for an object of that class type. Similary, the class must be defined before a reference or pointer is used to access a member of the type.

    原来大师早有教诲。动手实践太少,没有注意到这些细节。