以文本方式查看主题

-  计算机科学论坛  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  VC实现串行通信的动态链接库(DLL)  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=27021)


--  作者:卷积内核
--  发布时间:2/7/2006 10:44:00 AM

--  VC实现串行通信的动态链接库(DLL)
本文是作者根据工程中的实践经验,以实例方式介绍了用VC++编写DLL实现串行通信的方法,并给出了用其它语言调用该DLL的范例。

  一、引言

  串行通讯口作为计算机与外部串行设备进行数据传输的重要端口,因其使用简单、方便,在业界的各种计算机系统中得到了广泛的应用。由于应用范围很广,软件人员在串行通信方面也积累了丰富的编程经验。关于串行通信的文章不时见于报端,并且还有一些专门讨论
串行通讯编程的书籍,为软件人员提供了丰富的资源,同时对初次涉足串口编程的软件开发者提供很好的范例帮助。

  虽然关于串口编程的资料很多,但由于工程上需求多种多样,需要根据不同的条件对串口进行灵活控制,在实际应用中,常用的方法有:

  (1)用汇编或标准通讯函数,往指定端口直接读写数据;

  (2)利用Visual Studio提供的MSComm控件;

  (3)使用Windows提供的API函数。

  本文是作者在工程实践中根据实际需要,在Visual C++环境中,直接使用Windows提供的API函数实现一个串行通信动态链接库(DLL)的完整实例,实例既给出了DLL函数的编写方法,又包括一份用其它编程工具(VB)对此DLL进行声明及调用的范例。为了使读者一目了然,并减少代码量,本例作了简化,但完全可以运行。希望对需要进行串行通信编程的同行以及需要学习编写DLL的编程爱好者提供一点帮助。

  二、准备知识

  1、DLL基本概念

  动态链接库(DLL) 是Windows操作系统的基础,Windows API中的所有函数就是包含在DLL中,它有众多优点,如:简化软件项目管理以便分工合作,有助于节省内存,便于资源共享并且可以用多种语言来编写。

  用VC++ 编写动态链接有以下四种类型:Win32 DLL、MFC常规DLL(动态链接MFC)、MFC常规DLL(静态链接MFC)、MFC扩展DLL等,关于它们之间的区别,在此不一一介绍,可参阅相关文档。本程序的编写为Win32 DLL。

  2、串行通信基础知识

  在Windows系统,系统通过一个叫DCB(Device Control Block)的结构对串行口进行配置,通过Windows API GetCommState函数可以得到串行通讯口的状态信息,使用SetCommState函数可以对串行通讯口进行设置,实现串行通信一般需按以下四步进行:

  (1) 打开串口。由于串口是独占性资源,因此应用程序打开串口后,别的应用程序就不能再打开此串口了。

  (2)配置串口。利用GetCommState函数获取串口当前配置,根据需要更改DCB 结构中的参数,然后用SetCommState函数设置串口通讯参数。

  (3)数据传送。在串口上进行数据发送接收,并根据需要进行校验,触发一些事件等等。这个串口通讯DLL的目的就是收发数据。

  (4)关闭串口。不需要此串口时,关闭串口,供其它的应用程序使用。

  3、CALLBACK函数

  在微软的官方手册中是这样定义CALLBACK函数的:“CALLBACK函数是由应用程序定义而由操作系统调用的函数”。在我们编写DLL时,就是由应用程序定义而由此DLL执行的函数,这一机制在被调用者(DLL)和调用者(应用程序)之间进行信息传递是非常有用的。这一特性,让初学者较难理解,但是它却是编写串行通信程序者的福音,正是由于使用这一特性,才可很方便地在动态链接库中实现MSComm控件中的OnComm 事件,并且可根据需要进行灵活控制。

  三、串行通讯动态链接库的编写

  以上将一些基础知识进行了适当的介绍,下面再着重讨论怎样编写一个动态链接库(DLL)。

  1、从VC++ 6.0的File菜单中选择New命令,并在列表框中选择Win32 Dynamic-Link Library项,创建一个工程。

  2、往工程中添加头文件

  根据需要,我们在头文件中定义此DLL提供给外部调用的五个输出函数,其定义方法如下:

//com.h
#ifndef _COM_DLL_H
#define _COM_DLL_H
#ifdef MyComDll
#else
#define MyComDll extern "C" __declspec(dllimport)
#endif
MyComDll int FAR PASCAL ComOpen(int port);
MyComDll int FAR PASCAL ComClose();
MyComDll int FAR PASCAL SetCallback(void (CALLBACK* fun)(int port));
MyComDll int FAR PASCAL GetComData(LPBYTE buf);
MyComDll BOOL FAR PASCAL SendDataToCom(LPBYTE Cmd,int CmdLen);
#endif

  DLL的源代码模块需要包含该头文件,若要编写调用该动态链接库函数的C/C++应用程序,也需要包含此头文件。另外,你会发现,MyComDll中包含了extern "C"链接指示符,这是因为在用C++编写动态链接库时,通常在经C++编译器编译后,其函数名称会改变,如
ComOpen编译后,展现给调用者的名字是:_ComOpen@8之类,这样应用程序在调用时,链接程序就会抱怨找不到指定的函数。加上extern“C”后,就告诉编译器不要改变变量名或函数名。__declspec(dllimport)是告诉编译器,应用程序将从这个DLL 模块引入这些函数,__declspec(dllexport) 是告诉编译器这些函数是从产生的DLL模块输出给别的应用程序调用。

  3、往工程中添加C/C++源代码模块

  在C++源代码模块中,首先应包含如下头文件,及定义相关变量:

#include <windows.h>
#define MyComDll extern "C" __declspec(dllexport)
#include "com.h"  

  然后就定义本动态链接库的五个输出函数:

  (1)ComOpen(int port)

  此函数用来打开指定的串行通讯口,其参数port即为通讯口号。打开端口后,通过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在特定的时刻(本程序是当串行端口接收到有效数据时)通过内部的指针函数,调用外部的应用
程序,从而实现“回调”功能。

//com.cpp
void (CALLBACK* infunc) (int port);
int FAR PASCAL SetCallBack(int controlport, void (CALLBACK*
outfunc)(int controlport))
{
if(outfunc!=NULL)
infunc=outfunc;
return 1;
}  

  4、往工程中添加模块定义文件

  模块定义文件(module-definition)文件是以.def为扩展名的文本文件,为了能被其他开发工具如Visual Basic、Delphi等使用,创建的DLL文件必须要有模块定义文件,否则在应用程序调用ComOpen时会出现“Can’t find DLL entry point ComOpen in Comdll.dll”的
错误提示。Exports节和extern“C”的作用一样,告诉编译器不要改变输出的函数名。

LIBRARY COMDLL
DESCRIPTION ’COM Communication Demo’
EXPORTS
ComOpen @ 1
ComClose @ 2
SendDataToCom @ 3
SetCallBack @ 4
GetComData @ 5  

  四、动态链接库函数的VB 调用示范

  1、VB测试程序

  以下程序示范了在VB中如何调用由VC++编写的动态链接库中的函数。因为动态链接库中使用了回调函数,在VB代码中必须将回调函数放到标准的.BAS模块中,不可放在窗体模块中,也不能将其附加到类模块中。CALLBACK函数只是触发同一工程中一个窗体上的定时器控件,对串口数据进行采集,其定义如下:

Sub CallBackFunc(ByVal port As Long) Form1.Timer1.Interval = 1
Form1.Timer1.Enabled = True
End Sub

  在标准的.BAS模块中,还需定义该DLL函数的调用方式,具体如下:

Option Explicit
Public Declare Function ComOpen Lib "Comdll.dll" (ByVal
port As Long) As Long
Public Declare Function ComClose Lib "Comdll.dll" () As
Long
Public Declare Function SendDataToCom Lib "Comdll.dll"
(ByRef Cmd As Byte, ByVal CmdLen As Long) As Long
Public Declare Function SetCallBack Lib "Comdll.dll" (ByVal
port As Long, ByVal func As Long) As Long
Public Declare Function GetComData Lib "Comdll.dll" (ByRef
buf As Byte) As Long  

  在窗体程序中,当打开串口时,调用DLL的SetCallBack函数设置回调函数,调用方式如下:

SetCallBack (comport, AddressOf CallBackFunc);

  关键字AddressOf是将CallBackFunc的函数地址传递给DLL中的指针函数void (CALLBACK* infunc) (int port)。

  2、调试方法

  串行通讯的调试相对来说是比较麻烦,在实践中,可以在同一台具有两个串行通讯口的机器上进行调试,只需要将两个串口的RXD和TXD交叉连接,并将5脚对等连接,就可以进行调试了;当然,也可用一个串口进行调试,只需将同一串口的2、3连接。

  在调试过程中,需提醒读者注意的一点是:动态链接库与执行文件应在同一目录下,否则出现找不到动态链接库的问题;由于Windows操作系统本身的原因,在对动态链接库的第一次使用时,须将其拷贝至系统安装目录的system32子目录中才能避免上述问题。

  五、结束语

  此DLL是本人在工作中的一点经验积累,去除工程实践中的繁琐枝叶,只涉及串行通讯的关键代码,目的是展示以DLL实现串行通信的方法,希望本文能对读者起到抛砖引玉的作用。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
37.109ms