#
调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同。Windows提供了两种将DLL映像到进程地址空间的方法:
1. 隐式的加载时链接
这种方法需要DLL工程经编译产生的LIB文件,此文件中包含了DLL允许应用程序调用的所有函数的列表,当链接器发现应用程序调用了LIB文件列出的某个函数,就会在应用程序的可执行文件的文件映像中加入一些信息,这些信息指出了包含这个函数的DLL文件的名字。当这个应用程序运行时,也就是它的可执行文件被操作系统产生映像文件时,系统会查看这个映像文件中关于DLL的信息,然后将这个DLL文件映像到进程的地址空间。
系统通过DLL文件的名称,试图加载这个文件到进程地址空间时,它寻找DLL 文件的路径按照先后顺序如下:
·程序运行时的目录,即可执行文件所在的目录;
·当前程序工作目录
·系统目录:对于Windows95/98来说,可以调用GetSystemDirectory函数来得到,对于WindowsNT/2000来说,指的是32位Windows的系统目录,也可以调用GetSystemDirectory函数来得到,得到的值为SYSTEM32。
·Windows目录
·列在PATH环境变量中的所有目录
VC中加载DLL的LIB文件的方法有以下三种:
①LIB文件直接加入到工程文件列表中
在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中“Add Files to Project”菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。
②设置工程的 Project Settings来加载DLL的LIB文件
打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。
③通过程序代码的方式
加入预编译指令#pragma comment (lib,”*.lib”),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。
当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。
2 显式的运行时链接
隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:
①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。
②使用GetProcAddress函数得到要调用DLL中的函数的指针。
③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。
之前已经介绍了Windows程序的设计过程及关键,简单一句话就是把界面设计好,关键是做好交互操作的处理过程。
Window程序设计的大框架基本固定:设计窗口类,注册,创建,显示,更新,消息循环。在这个过程中,设计、注册、创建、显示和更新窗体,包括消息循环过程等等所有操作都是调用API函数来完成的,所谓的API函数就是操作系统提供给应用程序的编程接口。
除了提供API函数库供编程使用,针对面向对象编程理念,微软还对这些API函数进行了封装,将其根据功能划分到各种C++类中。程序员通过调用类的成员函数来调用这些API函数。
MFC就是微软为了简化程序员的开发工作所开发的一套C++类的集合。除了将函数封装到类,MFC还将每一个窗口应用程序都需要的步骤进行了封装,即将Window程序设计的大框架进行了封装。所以简单来说,MFC就是对API函数库和Windows程序设计过程进行了封装,目的是简化程序员的开发工作,但是对初学者却造成了很多不必要地困扰,在没有了解MFC的架构前看代码无从下手,找不到入口点。
下面以通过MFC AppWizard生成的单文档MFC应用程序为例来进行讲解,工程名为Test。自动生成的工程有五个类:CAboutDlg,CMainFrame,CTestApp,CTestDoc,CTestView,和一个CTestApp类型的全局对象theApp。
Windows应用程序的消息处理过程中,操作系统是通过应用程序实例句柄来找到消息所属的应用程序,再通过窗口句柄进一步找到消息所属的窗口,进而调用其窗口过程函数。对于MFC程序而言,通过产生一个应用程序类CTestApp的对象theApp来唯一标识应用程序的实例。每个MFC程序有且仅有一个应用程序类(CWinApp)的派生类(CTestApp)。每个MFC程序实例有且仅有一个该派生类的实例化对象,即theApp全局对象。
全局变量是在main函数之前分配内存空间,所以在进入主函数之前,首先是创建CTestApp的对象。而CTestApp是CWinApp的子类,所以在创建该类的实例前,首先调用了CWinApp的构造函数。该函数完成程序运行时的一些初始化工作。
接着进入主函数。主函数中即完成窗体的设计、注册、创建、显示、更新等等一系列过程,并启动消息循环过程。具体表现就是调用theApp的InitApplication、InitInstance和Run函数。InitApplication函数完成MFC内部管理方面的工作。由CTestApp的父类CWinApp完成。InitInstance函数因为CTestApp重写之,所以调用的是CTestApp类内的函数实现。这两个函数完成了窗体的设计直到更新显示。而Run函数则启动了消息循环过程。
至此,MFC的大框架介绍完毕。
Windows程序设计的关键是窗口过程函数的设计。一般的Windows应用程序在窗口过程函数中使用switch对消息进行判别并分类处理。而MFC的消息循环并不是采用Window应用程序所采用的方式,它采用专门的消息映射机制。
至于原因,个人总结是:在介绍一般Windows程序设计时,每个窗口对象对应一个窗口过程函数,该窗口的所有消息均由该函数进行处理;而MFC架构中采用了文档/视图结构,该架构由五个类共同实现:CAboutDlg,CMainFrame,CTestApp,CTestDoc,CTestView。该应用程序可能接收到的消息也被划分到不同的类中,并且该架构中类的继承层次较复杂,如果将类可能接收到的消息作为类的成员变量存在,类的体积将非常庞大,所以采用消息映射机制来实现。
MFC消息映射机制的具体实现是:
在每个能接收和处理消息的类中,定义一个消息和消息函数静态对照表,即消息映射表。在消息映射表中,消息与对应的消息处理函数指针是成对出现的。某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中。当有消息需要处理时,程序只能搜索该消息静态表,查看表中是否含有该消息,就可知道该类能够处理此消息。如果能处理该消息,则同样依照静态表能很容易找到并调用对应的消息处理函数。
Windows应用程序至少有一个窗口,称为主窗口。Windows应用程序通过窗口和外界交互,是一种基于消息、事件驱动的设计模式,每个窗口对应一个窗口过程函数,负责对交互信息进行处理。应用程序的设计者可以根据需要设计窗口的特征。所以Windows应用程序的设计过程如下:
l 设计一个窗体类。指定该类窗体的特征,例如是否显示最大化/最小化框,指定窗口过程函数的地址(即指定窗体过程函数的名称)
l 注册窗体类。只有注册了该窗体类,后面创建窗体类对象时,系统才知道该窗体的特征。窗体类对象涉及到与系统的交互,所以需要向系统登记该类类型,而不是简单的设计类,创建对象。
l 创建窗口类对象。
l 显示并更新窗口类对象。窗口类对象创建之后,只是在内存中存在该对象,想要显示出来还需要调用其显示函数。一开始窗体类对象的界面属于无效状态,调用更新函数使其有效。当窗体部分或全部被挡住时,被挡住的部分就处于无效状态,想要再次显示需要更新之。
l 进行消息循环。即开启一个死循环处理应用程序中窗口的消息,即根据消息调用窗口过程函数。当窗口销毁时跳出该死循环,结束应用程序。
Windows应用程序设计的关键是窗口过程函数的设计。
Windows应用程序的关键就是消息循环部分,借由消息队列存放捕获到的消息,然后循环从消息队列中取出消息进行处理。其消息处理过程如下:
l 操作系统接收到应用程序的窗口消息,例如捕获到用户在窗口单击鼠标左键事件,将消息投递到该应用程序的消息队列中;
l 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理。例如,对于操作系统,鼠标按下和鼠标抬起分别对应LButtonDown和LButtonUp两个消息,应用程序将这两个消息处理为一个鼠标单击消息。
l 应用程序调用DispatchMessage,将消息回传给操作系统。
l 操作系统利用消息中包含的窗口过程函数指针调用窗口过程,对消息进行处理。
上述过程是一般的消息处理过程,其实消息又分为两类:进队消息和不进队消息,上述过程是进队消息的处理过程,不进队消息是操作系统捕获到消息后直接调用应用程序的处理过程,而不放入消息队列中。
个人认为,进队消息应该是需要预处理的消息,因为最后还是回到操作系统,由操作系统调用窗口过程函数进行处理。至于为何非要回到操作系统,个人理解也和进队/不进队有关,因为有的消息不进队则直接由操作系统调用处理函数,所以进队的消息也统一再回到操作系统,由操作系统调用处理函数。所以在这里可以看出,程序员设计的窗口过程函数不是由应用程序调用,而是由操作系统调用,所以该函数类型为回调函数,即函数的调用方和函数的设计方不是同一人。
函数参数是以栈的形式存取,从右至左入栈,当无法列出传递给函数的所有实参的类型和数目时,可用省略号指定参数表。
获取省略号指定的参数的方法:在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。
va_list是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。在调用参数表之前,在函数体中声明一个va_list类型的变量ap;
然后用va_start()实现ap的初始化,让它指向可变参数表里面的第一个参数。该函数有两个参数,第一个参数就是ap,第二个参数是在变量表前面紧挨着的一个变量,即“……”之前的那个参数;
然后是获取参数,调用va_arg()函数,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把ap的位置指向变参表的下一个变量位置;
获取所有参数之后,必须将这个ap指针关掉,以免发生危险,方法是调用va_end()函数,它将输入的参数ap置为NULL。
标准C++将编译过程定义为9个阶段:
1. 字符映射:文件中的物理源字符被映射到源字符集中,其中包括三字符运算符的替换、控制字符(行尾的回车换行)的替换。
2. 行合并:以反斜杠结束的行和它接下来的行合并
3. 标记化:每一条注释被一个单独的空字符所替换,源代码被分析成预处理标记
4. 预处理:调用预处理指令并扩展宏,使用include指令包含的文件,重复1到4阶段。1到4阶段统称为预处理阶段。
5. 字符集映射:源字符集成员、转义序列被转换成等价的执行字符集成员
6. 字符串连接:将相邻的字符串进行连接
7. 翻译:将写好的C++程序代码进行语法和语义分析编译,并翻译成可执行的目标代码
8. 处理模板:此时会调用库函数
9. 链接:把所有编译好的单元全部链接为一个整体文件,其实这一步可以比作是一个“连线的过程。”链接时最重要的是检查全局空间里面是不是有重复定义或者缺失定义。
链接包括三个过程:预编译,编译,链接
编译阶段分两步:检查函数或者变量是否存在它们的声明;检查语句是否符合C++语法
预编译阶段粗略的认为就是“宏展开”,即建立名与体的对等关系,以便在编译阶段进行替换。
编译:将源代码转换为机器可认识代码的过程。编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序
将{}内的语句作为一个整体,在定义宏的时候特别关键;
另外,使用该结构可以避免使用goto语句,如果在{}内进行多项判断,只要一项判断错误,则跳过之后的其他判断,则使用该结构直接跳出,而不用使用goto语句。
在C语言中定义结构体的语法如下:
struct student
{
string name;
};
接下来,定义该结构体的一个s变量的语法如下:
struct student s;
即在定义结构体变量时需要带上struct关键字。有一种方法可以使得在定义结构体变量时不带struct关键字:使用类型别名。
typedef struct student
{
string name;
}stu;
stu s;
在上面的结构体定义中也可以省略结构体名称student,因为定义别名为的就是简化定义语句,后面不会使用该结构体的原名,所以不明确写出student也可以。
C++对结构体类型进行了调整:C++中的结构体和类除了默认访问级别不同外,没有其他区别。所以,在定义结构体类型时,即使前面没有typedef,在定义结构体变量时也不需要带有struct关键字。
LPSTR:即 char *,指向以'\0'结尾的8位(单字节)ANSI字符数组指针
LPWSTR:即wchar_t *,指向'\0'结尾的16位(双字节)Unicode字符数组指针
LPCSTR:即const char *
LPCWSTR:即const wchar_t *
LPTSTR:LPSTR、LPWSTR两者二选一,取决于是否宏定义了UNICODE或ANSI
LPCTSTR: LPCSTR、LPCWSTR两者二选一,取决于是否宏定义了UNICODE或ANSI,
1.string 转 CString
CString.format(”%s”, string.c_str());
2.char 转 CString
CString.format(”%s”, char*);
3.char to string
string s(char *);
4.string 转 char *
char *p = string.c_str();
5.CString 转 string
string s(CString.GetBuffer());
6.string -> CString
CString.format(”%s”, string.c_str());
用c_str()确实比data()要好.
7.CString -> string
string s(CString.GetBuffer());
GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.
8.有三个函数可以将字符串的内容转换为字符数组和C—string
1.data(),返回没有”\0“的字符串数组
2,c_str(),返回有”\0“的字符串数组
3,copy()
9.将字符转换为整数,可以使用atoi、_atoi64或atol。
10.将数字转换为CString变量,可以使用CString的Format函数
CString s;
int i = 64;
s.Format(”%d”, i)
Format函数的功能很强,值得你研究一下。
11.cstring TO char *
charpoint=strtest.GetBuffer(strtest.GetLength());
12.CString转换 char[100]
char a[100];
CString str(”aaaaaa”);
strncpy(a,(LPCTSTR)str,sizeof(a));
-
BOOL GetCommState( HANDLE hFile, LPDCB lpDCB );
-
- 读取串口设置(波特率,校验,停止位,数据位等)
-
- 参数:
- hFile是由CreateFile函数返回指向已打开串行口的句柄。
-
lpDCB :指向设备控制块DCB。
-
- 返回值:
- 函数调用成功,返回非0;函数调用失败,返回0。
-
- 当应用程序仅仅需要修改一部分串行口的配置值时,可以通过GetCommState函数获得当前的DCB结构,然后更改参数,再调用SetCommState函数设置修改过的DCB来配置串行口。