用VC开发串口通信dll控件

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项,创建一个工程,如下图所示:

   VC串口dll编写-第一步  
 

  2、往工程中添加头文件

  在新建一个com.h文件,里加入对外提供的函数接口,如要有5个,如下所示:

  1. //com.h   
  2. #ifndef _COM_DLL_H   
  3. #define _COM_DLL_H   
  4. #ifdef MyComDll   
  5. #else   
  6. #define MyComDll extern "C" __declspec(dllimport)   
  7. #endif   
  8. MyComDll int FAR PASCAL ComOpen(int port);   
  9. MyComDll int FAR PASCAL ComClose();   
  10. MyComDll int FAR PASCAL SetCallback(void (CALLBACK* fun)(int port));   
  11. MyComDll int FAR PASCAL GetComData(LPBYTE buf);   
  12. MyComDll BOOL FAR PASCAL SendDataToCom(LPBYTE Cmd,int CmdLen);   
  13. #endif 

  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. #include <windows.h>   
  2. #define MyComDll extern "C" __declspec(dllexport)   
  3. #include "com.h" 

  然后实现本动态链接库的五个输出函数:

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

  1. //com.cpp   
  2. void (CALLBACK* infunc) (int port);   
  3. int FAR PASCAL SetCallBack(int controlport, void (CALLBACK*   
  4.                            outfunc)(int controlport))   
  5. {   
  6.     if(outfunc!=NULL)   
  7.         infunc=outfunc;   
  8.     return 1;   
  9. }  
  10.  

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

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

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

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

  1、VB测试程序

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

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

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

  1. Option Explicit   
  2. Public Declare Function ComOpen Lib "Comdll.dll" (ByVal   
  3. port As LongAs Long   
  4. Public Declare Function ComClose Lib "Comdll.dll" () As   
  5. Long   
  6. Public Declare Function SendDataToCom Lib "Comdll.dll"   
  7. (ByRef Cmd As ByteByVal CmdLen As LongAs Long   
  8. Public Declare Function SetCallBack Lib "Comdll.dll" (ByVal   
  9. port As LongByVal func As LongAs Long   
  10. Public Declare Function GetComData Lib "Comdll.dll" (ByRef   
  11. buf As ByteAs Long 

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

  1. SetCallBack (comport, AddressOf CallBackFunc); 

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

  2、调试方法

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

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

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/*====================================================================================================== =======================================================================================================*/ /*======================================================================================================== 如何使用? 1.引入DLL文件,引入头本文件,就已声明了导入函数 //要引入DLL的方法 //1、把SERIALPORTDLL1.dll和SERIALPORTDLL1.lib拷贝到工程目录下 //2、然后在 工程->设置->连接->分类->常规->对象/库模块输入:SERIALPORTDLL1.lib 2.调用Init初始化串口 3.调用SetReceiveFuntion(&ReveveChar);指定接收和处理数据的函数 4.实现void ReveveChar(WPARAM data, LPARAM port)这个函数 5.调用SendData发送数据 6.ClosePort(); ========================================================================================================*/ /*======================================================================================================= 函 数 名: Init 功 能: 打开并初始化串口 参数说明: UINT port //端口号 UINT baud //波特率 char parity = 'N' //校验位,默认为无校验位 UINT databits = 8 //数据位,一个字节的位数,默认为8位 UINT stopbit = 1 //停止位,默认为1位 使用例子: Init(2,9600); //打开串口2,波特率9600,其它值为默认值(无奇偶校验、数据位为8、停止位为1) Init(4,4800,'N',10);//打开串口4,波特率为4800,无奇偶校验,数据位为10,(停止位默认为1) ... ... ======================================================================================================*/ extern "C" __declspec(dllimport) void Init( UINT port, //端口号 UINT baud, //波特率 char parity = 'N', //校验位 UINT databits=8 , //数据位 UINT stopbit=1 //停止位 ); /*========================================================================================================== 函 数 名: SendData 功 能: 向串口发送数据,使用之前必须先调用Init函数初始化串口 参数说明: char data[] //要发送的数据 int datalen //发送数据的长度 使用例子: 例1: char a[] = {0x01,0x03,0x00,0x03,0x00,0x02,0x34,0x0B};//准备要发送的十六进制:01 03 00 03 00 02 34 0B SendData(a,8); //向串口发送十六进制数:01 03 00 03 00 02 34 0B 例2: char a[] = "$01001000020110*" //准备要发送的字符串$01001000020110* SendData(a,16); //向串口发送:$01001000020110* ============================================================================================================*/ extern "C" __declspec(dllimport) void SendData( char data[], int datalen ); /*=================================================================================================================== 函 数 名: SetReceiveFuntion 功 能: 指定接收数据的函数,指定好后,串口接收缓冲区每有一个字节的数据时会自动触发和调用所指定的函数 参数说明: a、参数需要一个函数指针, b、该指针指向一个用于接收数据的函数(该函数由用户自己声明、定义和实现), c、用户定义该函数时必须要满足一下条件: 1、该函数为全局函数 2、返回值为void 3、形参为两个,类型为:形参为UINT,LONG 使用例子: 1、void ReveveChar(WPARAM data, LPARAM port);//声明一个用于接收数据的全局函数,本头文件已默认声明这个函数,用户无需再次声明 2、 CString strTemp; void ReveveChar(WPARAM data, LPARAM port)//实现这个接收数据并处理数据的函数 { static int RecevCount = 0; //用于接收字节数的计数 char str[512] = ""; sprintf(str,"%02x",data); //接收十六进制数,并格式化为字符形式 strTemp += str; RecevCount++; if (RecevCount >= 8) //接收到数据长度等于了协议长度,则开始进行协议的处理 { RecevCount = 0; strTemp = "接收到得数据协议为" + strTemp; AfxMessageBox(strTemp); strTemp = ""; } } 3、SetReceiveFuntion(&ReveveChar);//指定void ReveveChar(WPARAM data, LPARAM port);该函数接收和处理数据 =========================================================================================================================*/ extern "C" __declspec(dllimport) void SetReceiveFuntion(void (*pfCallBack)(WPARAM data, LPARAM port)); /*================================================= 函 数 名: ClosePort 功 能: 关闭由Init函数打开的串口 参数说明: 无需参数 调用例子: ClosePort(); =================================================*/ extern "C" __declspec(dllimport) void ClosePort(); /*=============================================================================================================== 函 数 名: ReveveChar 功 能: 每当串口接收到一个字符(字节)时就会自动触发和调用这个函数, 用户只需实现不必也不能去亲自调用, 注意:打开串口后,必须要调用SetReceiveFuntion(ReveveChar) 参数说明: data //接收到的是串口中的一个字节数据 port //端口号,表明是哪个端口接收到的数据 调用例子: 只要实现,无需自己调用。 实现处理数据可参考如下简单的例子,思想是边接收数据边处理 void ReveveChar(WPARAM data, LPARAM port) { static int RecevCount = 0; //用于接收字节数的计数 char str[512] = ""; sprintf(str,"%02x",data); //接收十六进制数,并格式化为字符形式 strTemp += str; RecevCount++; if (RecevCount >= 8) //接收到数据长度等于了协议长度,则开始进行协议的处理 { RecevCount = 0; strTemp = "接收到得数据协议为" + strTemp; AfxMessageBox(strTemp); strTemp = ""; } } 说明:本函数并非为导出函数,写在这里仅仅是起到声明一个函数的作用,用户也可以删除掉,自己声明和实现! ===============================================================================================================*/ void ReveveChar(WPARAM data, LPARAM port);

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值