zoukankan      html  css  js  c++  java
  • VS2015 编写C++的DLL,并防止DLL导出的函数名出现乱码(以串口通信为例,实现串口通信)

      参考链接:https://blog.csdn.net/songyi160/article/details/50754705

    1、新建项目

      

      

      

      建立好的项目界面如下:

        

      

      接着在解决方案中找到【头文件】然后右击选择【添加】》【新建项】,在弹出的添加新项对话框中进行如下选择:

       

      继续按上面的方法在解决方案中找到【源文件】然后右击选择【添加】》【新建项】,在弹出的添加新项对话框中进行如下选择:

      

      项目准备建好了,现在开始编程了。

    2 编程实现串口通信

      我们先编写刚才建立好的 “ WzSerialPort.h ” 文件,该文件主要是做一些函数声明:

    #pragma once
    
    #ifndef _WZSERIALPORT_H
    #define _WZSERIALPORT_H
    
    class WzSerialPort
    {
      public:
        WzSerialPort();
        ~WzSerialPort();
    
        // 打开串口,成功返回true,失败返回false
        // portname(串口名): 在Windows下是"COM1""COM2"等,在Linux下是"/dev/ttyS1"等
        // baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200 
        // parity(校验位): 0为无校验,1为奇校验,2为偶校验,3为标记校验(仅适用于windows)
        // databit(数据位): 4-8(windows),5-8(linux),通常为8位
        // stopbit(停止位): 1为1位停止位,2为2位停止位,3为1.5位停止位
        // synchronizeflag(同步、异步,仅适用与windows): 0为异步,1为同步
        bool open(const char* portname, int baudrate, char parity, char databit, char stopbit, char synchronizeflag = 1);
    
        //关闭串口,参数待定
        void close();
    
        //发送数据或写数据,成功返回发送数据长度,失败返回0
        int send(const void *buf, int len);
    
        //接受数据或读数据,成功返回读取实际数据的长度,失败返回0
        int receive(void *buf, int maxlen);
    
     private:
        int pHandle[16];
        char synchronizeflag;
    };
    
    #endif

      接着编写 “ WzSerialPort.cpp ” 的文件,该文件主要实现串口打开关闭以及数据传输函数:

    #include "WzSerialPort.h"
    
    #include <stdio.h>
    #include <string.h>
    
    #include <WinSock2.h>
    #include <windows.h>
    
    WzSerialPort::WzSerialPort()
    {
    
    }
    
    WzSerialPort::~WzSerialPort()
    {
    
    }
    
    bool WzSerialPort::open(const char* portname,
        int baudrate,
        char parity,
        char databit,
        char stopbit,
        char synchronizeflag)
    {
        this->synchronizeflag = synchronizeflag;
        HANDLE hCom = NULL;
        if (this->synchronizeflag)
        {
            //同步方式
            hCom = CreateFileA(portname, //串口名
                GENERIC_READ | GENERIC_WRITE, //支持读写
                0, //独占方式,串口不支持共享
                NULL,//安全属性指针,默认值为NULL
                OPEN_EXISTING, //打开现有的串口文件
                0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
                NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
        }
        else
        {
            //异步方式
            hCom = CreateFileA(portname, //串口名
                GENERIC_READ | GENERIC_WRITE, //支持读写
                0, //独占方式,串口不支持共享
                NULL,//安全属性指针,默认值为NULL
                OPEN_EXISTING, //打开现有的串口文件
                FILE_FLAG_OVERLAPPED, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
                NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
        }
    
        if (hCom == (HANDLE)-1)
        {
            return false;
        }
    
        //配置缓冲区大小 
        if (!SetupComm(hCom, 1024, 1024))
        {
            return false;
        }
    
        // 配置参数 
        DCB p;
        memset(&p, 0, sizeof(p));
        p.DCBlength = sizeof(p);
        p.BaudRate = baudrate; // 波特率
        p.ByteSize = databit; // 数据位
    
        switch (parity) //校验位
        {
        case 0:
            p.Parity = NOPARITY; //无校验
            break;
        case 1:
            p.Parity = ODDPARITY; //奇校验
            break;
        case 2:
            p.Parity = EVENPARITY; //偶校验
            break;
        case 3:
            p.Parity = MARKPARITY; //标记校验
            break;
        }
    
        switch (stopbit) //停止位
        {
        case 1:
            p.StopBits = ONESTOPBIT; //1位停止位
            break;
        case 2:
            p.StopBits = TWOSTOPBITS; //2位停止位
            break;
        case 3:
            p.StopBits = ONE5STOPBITS; //1.5位停止位
            break;
        }
    
        if (!SetCommState(hCom, &p))
        {
            // 设置参数失败
            return false;
        }
    
        //超时处理,单位:毫秒
        //总超时=时间系数×读或写的字符数+时间常量
        COMMTIMEOUTS TimeOuts;
        TimeOuts.ReadIntervalTimeout = 1000; //读间隔超时,该时间为串口每次接收等待的时间间隔,数据不多可以把该时间改小,这里每次等待1000mS间隔
        TimeOuts.ReadTotalTimeoutMultiplier = 500; //读时间系数
        TimeOuts.ReadTotalTimeoutConstant = 5000; //读时间常量
        TimeOuts.WriteTotalTimeoutMultiplier = 500; // 写时间系数
        TimeOuts.WriteTotalTimeoutConstant = 2000; //写时间常量
        SetCommTimeouts(hCom, &TimeOuts);
    
        PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空串口缓冲区
    
        memcpy(pHandle, &hCom, sizeof(hCom));// 保存句柄
    
        return true;
    }
    
    void WzSerialPort::close()
    {
        HANDLE hCom = *(HANDLE*)pHandle;
        CloseHandle(hCom);
    }
    
    int WzSerialPort::send(const void *buf, int len)
    {
        HANDLE hCom = *(HANDLE*)pHandle;
    
        if (this->synchronizeflag)
        {
            // 同步方式
            DWORD dwBytesWrite = len; //成功写入的数据字节数
            BOOL bWriteStat = WriteFile(hCom, //串口句柄
                buf, //数据首地址
                dwBytesWrite, //要发送的数据字节数
                &dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
                NULL); //NULL为同步发送,OVERLAPPED*为异步发送
            if (!bWriteStat)
            {
                return 0;
            }
            return dwBytesWrite;
        }
        else
        {
            //异步方式
            DWORD dwBytesWrite = len; //成功写入的数据字节数
            DWORD dwErrorFlags; //错误标志
            COMSTAT comStat; //通讯状态
            OVERLAPPED m_osWrite; //异步输入输出结构体
    
                                  //创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
            memset(&m_osWrite, 0, sizeof(m_osWrite));
            m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent");
    
            ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
            BOOL bWriteStat = WriteFile(hCom, //串口句柄
                buf, //数据首地址
                dwBytesWrite, //要发送的数据字节数
                &dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
                &m_osWrite); //NULL为同步发送,OVERLAPPED*为异步发送
            if (!bWriteStat)
            {
                if (GetLastError() == ERROR_IO_PENDING) //如果串口正在写入
                {
                    WaitForSingleObject(m_osWrite.hEvent, 1000); //等待写入事件1秒钟
                }
                else
                {
                    ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
                    CloseHandle(m_osWrite.hEvent); //关闭并释放hEvent内存
                    return 0;
                }
            }
            return dwBytesWrite;
        }
    }
    
    int WzSerialPort::receive(void *buf, int maxlen)
    {
        HANDLE hCom = *(HANDLE*)pHandle;
    
        //if (this->synchronizeflag)
        //{
        //    //同步方式,这里因为发送用了同步,接收想用异步,又没有重新初始化串口打开,就直接注释掉用串口异步接收了
        //    DWORD wCount = maxlen; //成功读取的数据字节数
        //    BOOL bReadStat = ReadFile(hCom, //串口句柄
        //        buf, //数据首地址
        //        wCount, //要读取的数据最大字节数
        //        &wCount, //DWORD*,用来接收返回成功读取的数据字节数
        //        NULL); //NULL为同步发送,OVERLAPPED*为异步发送
        //    if (!bReadStat)
        //    {
        //        return 0;
        //    }
        //    return wCount;
        //}
        //else
        {
            //异步方式,用同步会阻塞
            DWORD wCount = maxlen; //成功读取的数据字节数
            DWORD dwErrorFlags; //错误标志
            COMSTAT comStat; //通讯状态
            OVERLAPPED m_osRead; //异步输入输出结构体
    
                                 //创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
            memset(&m_osRead, 0, sizeof(m_osRead));
            m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent");
    
            ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
            if (!comStat.cbInQue)return 0; //如果输入缓冲区字节数为0,则返回false
    
            BOOL bReadStat = ReadFile(hCom, //串口句柄
                buf, //数据首地址
                wCount, //要读取的数据最大字节数
                &wCount, //DWORD*,用来接收返回成功读取的数据字节数
                &m_osRead); //NULL为同步发送,OVERLAPPED*为异步发送
            if (!bReadStat)
            {
                if (GetLastError() == ERROR_IO_PENDING) //如果串口正在读取中
                {
                    //GetOverlappedResult函数的最后一个参数设为TRUE
                    //函数会一直等待,直到读操作完成或由于错误而返回
                    GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE);
                }
                else
                {
                    ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
                    CloseHandle(m_osRead.hEvent); //关闭并释放hEvent的内存
                    return 0;
                }
            }
            return wCount;
        }
    }

      再接着,我们编写 “ USER_COM.cpp ” 文件,该文件主要是实现把 “ WzSerialPort.cpp ” 文件里的串口函数进行一次封装,封装声明成可供DLL外部调用的函数:

    // USER_COM_DLL.cpp : 定义 DLL 应用程序的导出函数。
    //
    
    #include "stdafx.h"
    #include "USER_COM.h"
    
    #include <iostream>
    #include "WzSerialPort.h"
    #include "Windows.h"
    
    
    using namespace std;
    
    /*类重命名*/
    WzSerialPort w;
    /*************************************************
    函数名:bool OpenCOM()
    功  能:打开串口
    传入值:无
    返回值:串口打开成功返回true,串口打开失败返回false
    *************************************************/
    bool OpenCOM()
    {
      return w.open("COM1", 115200, 0, 8, 1); //这里配置打开串口1,配置波特率为115200,数据位为8位,奇偶校验位为0,停止位为1,最后一位是同步异步选择位(隐藏)
    }
    /************************************************* 函数名:void COM_Send(unsigned char cmd, unsigned char data1, unsigned char data2) 功 能:发送数据 传入值:num为要发送的数据量大小,Send_buff为发送数据的数组,数组大小要跟num大小一致 返回值:发送成功返回发送数据长度,发送失败返回0 *************************************************/ int COM_Send(int num ,uint8_t* Send_buff) { sen_len = w.send(Send_buff, num); if (sen_len==0) return 0; else return sen_len ; } /************************************************* 函数名:void Close_COM() 功 能:关闭串口 传入值:无 返回值:无 *************************************************/ void Close_COM() { w.close(); } /************************************************* 函数名:void COM_RX(uint8_t* Rx_buff) 功 能:接收数据数据 传入值:uint8_t* Rx_buff 接收的串口数据的缓存BUFF数组指针, 接收到数据后,直接把数据填到该Rx_buff,默认可接收的数据最大长度为255 返回值:int len 返回值为实际接收到的数据长度,返回0则代表没有接收到数据或者数据校验出错 *************************************************/ int COM_RX(uint8_t* Rx_buff) { //该函数编译X86的时候,调用时使用X64编译器无法调用,调用编译时要调用的话就要用X64编译 uint8_t buff[255];int i = 0; memset(buff, 0, 255); int len = w.receive(buff, 255); //参数:接收的数据buff;接收的最大数据长度;返回值为实际接收到的数据长度,其他APP使用该函数时使用新线程调用for (i = 0; i < len; i++) Rx_buff[i] = buff[i];return len; }

      最后我们在头文件那里,新建一个 “ USER_COM.h ” 文件实现把 “ USER_COM.cpp ” 文件里的函数声明为DLL的外部接口:

    #ifdef USER_COM_EXPORTS
    #define USER_COM_API __declspec(dllexport) //声明为DLL导出函数的宏定义
    #else
    #define USER_COM_API __declspec(dllimport)
    #endif
    
    #include "stdint.h" 
    
    
    extern "C" USER_COM_API bool OpenCOM();
    extern "C" USER_COM_API int  COM_Send(unsigned char cmd, unsigned char data1, unsigned char data2);
    extern "C" USER_COM_API int  COM_RX(uint8_t* Rx_buff);
    extern "C" USER_COM_API void Close_COM();

      

    注意:__stdcall定义导出函数入口点调用约定为_stdcall

               extern "C" 说明导出函数使用C编译器,则函数名遵循C编译器的函数名修饰规则,不加extern "C"说明使用C++编译器的函数名修饰 规则,两种规则区别如下:

    (1)C编译器的函数名修饰规则 

             对于__stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数。

       例如 _functionname@number。__cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number

     (2)C++编译器的函数名修饰规则

             C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,甚至参数个数、参数类  型。不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识 和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。

      使用 extern "C" 跟不使用 extern "C" 的形成的DLL函数名差异如下图测试DLL函数名所示:

      

      

      由图可以看出两者之间最终形成的名字会有差异的,到这里编译就已经可以建成串口通信的 DLL 了,不过形成的的DLL通过函数查看器还会有一些除函数名以外的符号,最后进行函数命名规范就好了。“ .h ” 头文件的作用仅仅能导出动态库、明确编译链接方式及确定入口点约定,还一个重要作用是打包给开发者,使其了解动态库导出的函数及对应的的参数,为了确保导出函数名及入口点函数不变,此时需添加.def文件。

    3 添加 def 文件,保持函数名以及入口点函数不变

      在解决方案中找到【源文件】右击选择【添加】》【新建项】,在弹出的添加新项对话框中进行如下图所示选择:

      然后编写 “ USER_COM.def ” 文件,使用def文件的意义:将编译器生成的函数修饰去掉,用更加自然、更加容易理解、更加容易记忆的名字来命名函数,而不是一串人一看就吓一跳的 修饰名字。

    LIBRARY "USER_COM"
    
    EXPORTS
     OpenCOM @ 1
     COM_Send @ 2
     COM_RX @ 3
     Close_COM @ 4

    4 编译形成DLL

       最后编译就可以形成对应的DLL了,要是要编译32位的DLL就选择X86,要是要选择64位的DLL就选择X64即可。

      用函数查看器看,函数名都正常了:

       

  • 相关阅读:
    “小咖秀”火爆的背后,给我们开发者带来的思考
    移动互联网时代,好程序员的标准是什么?
    Android Studio之gradle的配置与介绍
    Android新组件RecyclerView介绍,其效率更好
    常见面试第二题之什么是Context
    常见面试题之ListView的复用及如何优化
    Ext学习之——活用Grid表格和TabPanel页切
    Ext学习之——实现Combo的本地模糊搜索(支持拼音)
    ExtJS学习之——实现Store数据过滤filterBy
    软件工程实训总结
  • 原文地址:https://www.cnblogs.com/xingboy/p/11157790.html
Copyright © 2011-2022 走看看