VC串口通信技术网《VC串口上位机编程方法简介》介绍了串口编程的常见方法,其中就有使用串口dll控件的方法,dll是一种动态链接库,使用起来非常方便。
本文利用VC编程工具,对Windows提供的API函数进行封装,实现了一个串行通信动态链接库dll的完整实例,实例既给出了DLL函数的编写方法,又包括一份用其它编程工具(VB)对此DLL进行声明及调用的范例。为了使大家能看得懂,本例作了简化。希望对需要进行串行通信编程的网友及需要学习编写DLL的编程爱好者提供一点帮助。
一、有关dll的常识
1、什么是dll
DLL是动态链接库Dynamic Link Library 的简称, DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个DLL 副本的内容。DLL 是一个包含可由多个程序同时使用的代码和数据的库。Windows API中的所有函数就是包含在DLL中,它有众多优点,如:简化软件项目管理以便分工合作,有助于节省内存,便于资源共享并且可以用多种语言来编写。
用VC++ 编写动态链接有以下四种类型:Win32 DLL、MFC常规DLL(动态链接MFC)、MFC常规DLL(静态链接MFC)、MFC扩展DLL等,关于它们之间的区别,在此不一一介绍,可参阅相关文档。本程序的编写为Win32 DLL。
如何没有dll编写基础,请查阅相关文档,进一步学习dll开发。
2、串口通信一般步骤
在Windows系统,用CreateFile函数来打开串口设备,在打开之前,应该对串口相关参数进行配置,系统通过一个叫DCB(Device Control Block)的结构对串行口进行配置,通过Windows API GetCommState函数可以得到串行通讯口的状态信息,使用SetCommState函数可以对串行通讯口进行设置,配置好后即可打工串口,打开串口后可以通过ReadFile和WriteFile读写串口。实现串行通信一般需按以下四步进行:
(1) 打开串口。由于串口是独占性资源,因此应用程序打开串口后,别的应用程序就不能再打开此串口了,所以在使用CreateFile打开串口时相关参数要注意,应使用独点方式。
(2)配置串口。利用GetCommState函数获取串口当前配置,根据需要更改DCB 结构中的参数,然后用SetCommState函数设置串口通讯参数。
(3)数据传送。在串口上进行数据发送接收,并根据需要进行校验,触发一些事件等等。这个串口通讯DLL的目的就是收发数据。
(4)关闭串口。不需要此串口时,关闭串口,供其它的应用程序使用。
3、有关CALLBACK函数
在编写dll时,CALLBACK函数是必不可少的。在微软的官方手册中是这样定义CALLBACK函数的:“CALLBACK函数是由应用程序定义而由操作系统调用的函数”。在我们编写DLL时,就是由应用程序定义,而调用是由我们编写的DLL来执行,这一机制在被调用者(DLL)和调用者(应用程序)之间进行信息传递是非常有用的。这一特性,让初学者较难理解,但是它却是编写串行通信程序者的福音,正是由于使用这一特性,才可很方便地在动态链接库中实现像MSComm控件中的OnComm 事件,并且可根据需要进行灵活控制。
二、串口通信动态链接库dll的编写
下面演示了如何用VC缩写串口通信dll过程。
1、从VC++ 6.0的File菜单中选择New命令,并在列表框中选择Win32 Dynamic-Link Library项,创建一个工程,如下图所示:
2、往工程中添加头文件
在新建一个com.h文件,里加入对外提供的函数接口,如要有5个,如下所示:
DLL的源代码模块需要包含该头文件,而且用户应用程序在使用此串口dll时也要包含com.h。另外,你会发现,MyComDll中包含了extern "C"链接指示符,这是因为在用C++编写动态链接库时,通常在经C++编译器编译后,其函数名称会改变,如ComOpen编译后,展现给调用者的名字是:_ComOpen@8这种形式,这样应用程序在调用时,链接程序就会提示找不到指定的函数。加上extern“C”后的作用就告诉编译器不要改变变量名或函数名。__declspec(dllimport)是告诉编译器,应用程序将从这个DLL 模块引入这些函数,__declspec(dllexport) 是告诉编译器这些函数是从产生的DLL模块输出给别的应用程序调用。
3、往工程中添加C/C++源代码文件
在C++源代码模块中,首先应包含如下头文件,及定义相关变量:
然后实现本动态链接库的五个输出函数:
(1)ComOpen(int port)
此函数接口功能是用来打开指定的串行通讯端口,参数port即为通讯口号(不用在前面加com了)。打开端口后,通过API函数GetCommState得到该端口的配置;根据需要更改其波特率、数据位、停止位等,以及设置触发信号事件的字符(即设置BCB的EvtChar字段);然后通过调用SetCommState设置端口。
串口打开成功后,将创建一个ComThreader线程,在ComThreader中,循环监控串行口是否收到有效数据,若收到则触发回调函数。
(2)GetComData (LPBYTE buf)
此函数是将串口接收缓冲区中的数据放到接收缓冲区buf中。
(3)SendDataToCom(LPBYTE Cmd,int CmdLen)
此函数是将指定长度的数据发送到串行口中。其长度由于变量CmdLen指定,发送的数据即为变量Cmd中的内容。发送数据时,需要将线程ComThreader暂时挂起,以避免与接收数据的线程冲突,产生紊乱。
(4)ComClose()
此函数用来关闭已打开的串口,无参数。通过事件触发以及WaitForSingleObject 、WaitForMultipleObjects函数,中断线程,关闭创建的各种事件、文件,释放相应资源,因为采用了多线程技术,因此需要特别注意主线程和子线程的相互同步。
(5)SetCallBack(int controlport,void (CALLBACK *outfunc)(int controlport))
此函数的功能是设置回调函数,controlport为指定的端口,outfunc是外部应用程序传过来的函数指针,其主要目的是将应用程序的函数指针传给DLL中的一个指针函数,DLL在特定的时刻(本程序是当串行端口接收到有效数据时)通过内部的指针函数,调用外部的应用 程序,从而实现“回调”功能。
4、往工程中添加模块定义文件
模块定义文件(module-definition)文件是以.def为扩展名的文本文件,为了能被其他开发工具如Visual Basic、Delphi等使用,创建的DLL文件必须要有模块定义文件,否则在应用程序调用ComOpen时会出现“Can’t find DLL entry point ComOpen in Comdll.dll”错误提示。Exports节和extern "C"的作用一样,告诉编译器不要改变输出的函数名。
四、动态链接库函数的VB 调用示范
1、VB测试程序
以下程序示范了在VB中如何调用由VC++编写的动态链接库中的函数。因为动态链接库中使用了回调函数,在VB代码中必须将回调函数放到标准的.BAS模块中,不可放在窗体模块中,也不能将其附加到类模块中。CALLBACK函数只是触发同一工程中一个窗体上的定时器控件,对串口数据进行采集,其定义如下:
在标准的.BAS模块中,还需定义该DLL函数的调用方式,具体如下:
在窗体程序中,当打开串口时,调用DLL的SetCallBack函数设置回调函数,调用方式如下:
关键字AddressOf是将CallBackFunc的函数地址传递给DLL中的指针函数void (CALLBACK* infunc) (int port)。
2、调试方法
串行通讯的调试相对来说是比较麻烦,在实践中,可以在同一台具有两个串行通讯口的机器上进行调试,只需要将两个串口的RXD和TXD交叉连接,并将5脚对等连接,就可以进行调试了;当然,也可用一个串口进行调试,只需将同一串口的2、3连接。
在调试过程中,需提醒读者注意的一点是:动态链接库与执行文件应在同一目录下,否则出现找不到动态链接库的问题;由于Windows操作系统本身的原因,在对动态链接库的第一次使用时,须将其拷贝至系统安装目录的system32子目录中才能避免上述问题。