由于引入了不同的库有同样的声明,可能在编译过程中出现警告有重复的定义,这个问题很好解决,只需要在项目属性的链接配置中,增加如下代码明确指定忽略 4006 和 4221 警告。
/IGNORE:4006,4221
最近帮朋友做一款工具,设计到对操作系统串口的操作,虽然这个东西已经是历史产物了,但是还有很多设备再用,索性从网络上找了一些代码最终完成这个小功能。下面资料将介绍串口在打开、关闭、读和写的时候一些注意事项以及参数的配置(代码中有详细注释。)
在串口打开的时候,我们要对串口做一些基础的初始化,比如波特率、数据位、校验位、停止位几个参数,他们分别被声明在 WinBase.h
头文件中。
打开串口的代码如下:
bool SerialPortManager::Open(ReceiveDataCallback cb/* = nullptr*/)
{
if (serial_handle_ != NULL)
{
return false;
}
if (cb != nullptr)
{
cb_ = cb;
}
serial_handle_ = CreateFile(com_.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (serial_handle_ == INVALID_HANDLE_VALUE)
{
return false;
}
// 获取旧的 dcb 数据
DCB dcb;
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(serial_handle_, &dcb))
{
Close();
return false;
}
// 修改 dcb 数据然后设置端口属性
// CBR_115200;
dcb.ByteSize = byte_size_;
dcb.BaudRate = baud_rate_;
dcb.StopBits = stop_bits_;
dcb.Parity = parity_;
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
if (!SetCommState(serial_handle_, &dcb))
{
Close();
return false;
}
// 设置读写缓冲区大小
SetupComm(serial_handle_, 1024, 1024);
// 清空数据
PurgeComm(serial_handle_, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
// 设置超时 10 秒
COMMTIMEOUTS to;
memset(&to, 0, sizeof(to));
to.ReadIntervalTimeout = 1000;
to.ReadTotalTimeoutMultiplier = 500;
to.ReadTotalTimeoutConstant = 5000; //设定写超时
to.WriteTotalTimeoutMultiplier = 500;
to.WriteTotalTimeoutConstant = 2000;
SetCommTimeouts(serial_handle_, &to);
PostReadThread();
QLOG_APP(L"Serial port device is ready, serial: {0}, baud rate: {1}, byte size: {2}, parity: {3}, stop bits: {4}.")
<< com_ << baud_rate_ << byte_size_ << parity_ << stop_bits_;
return true;
}
其中除了打开串口时传递的参数外,还包含了一些串口处理数据超时、读写缓冲区大小等属性,需要用到的根据自己的环境来配置。 串口的关闭很简单,只需要关闭掉 CreateFile 返回的句柄就可以了,这里不多介绍。
串口的读写可以同步也可以异步,但是同步方式会造成一个问题就是当你调用了 ReadFile
在等待串口数据时,再去调用 WriteFile
就会被阻塞,因为 ReadFile
一直没有返回。所以我还是推荐大家用异步方式来读写串口,代码如下:
void SerialPortManager::ReadSerialPortThread()
{
QLOG_APP(L"PostReadThread is running....");
while (TRUE)
{
if (!serial_handle_)
{
QLOG_ERR(L"Failed to read data from serial port, serial port handle is null.");
break;
}
// 计算最小需要读取的数据量
DWORD read_size = 1024;
// read_size = min(read_size, (DWORD)com_stat.cbInQue);
// 开始异步读取
OVERLAPPED over_lapped;
memset(&over_lapped, 0, sizeof(OVERLAPPED));
over_lapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
std::shared_ptr<BYTE> buffer;
buffer.reset(new BYTE[read_size]);
BOOL bReadStatus = ReadFile(serial_handle_, buffer.get(), read_size, &read_size, &over_lapped);
if (!bReadStatus) // 如果 ReadFile 函数返回 FALSE
{
DWORD last_error = GetLastError();
if (last_error == ERROR_IO_PENDING)
{
QLOG_APP(L"Read file return ERROR_IO_PENDING..");
BOOL bRet = GetOverlappedResult(serial_handle_, &over_lapped, &read_size, TRUE);
if (bRet)
{
// 返回 true 代表读取到了数据
QLOG_APP(L"Read data {0}") << nbase::UTF8ToUTF16((char*)buffer.get());
cb_((char*)buffer.get());
PurgeComm(serial_handle_, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
}
else
{
// 返回 false 可能是句柄已经被 close 了
QLOG_APP(L"GetOverlappedResult returned false");
}
continue;
}
QLOG_ERR(L"Failed to read data from serial prot, error code = {0}") << last_error;
}
}
QLOG_APP(L"PostReadThread is quit....");
}
bool SerialPortManager::WriteData(const std::string& data)
{
QLOG_APP(L"Begin to write data [{0}] to serial port.") << data;
DWORD bytes_written = data.size() + 1;
OVERLAPPED over_lapped;
memset(&over_lapped, 0, sizeof(OVERLAPPED));
over_lapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
BOOL write_stat = WriteFile(serial_handle_, data.c_str(), bytes_written, &bytes_written, &over_lapped);
if (!write_stat)
{
DWORD last_error = GetLastError();
if (last_error == ERROR_IO_PENDING)
{
WaitForSingleObject(over_lapped.hEvent, 2000);
return true;
}
return false;
}
QLOG_APP(L"Finished to write data.");
return true;
}
读因为是异步操作,我们需要传一个 OVERLAPPED
结构体到 ReadFile
的最后一个参数。写也是一样,我们可以用 GetOverlappedResult
或 WaitForSingleObject
来等待操作事件完成(记得要初始化 OVERLAPPED
否则会报错的)。两种方式读写我都做了演示,可以根据自己的需求改造。 读写操作的时候可以获取当前返回值判断是不是 ERROR_IO_PENDING
来确定是不是有数据还没有读取完成。
串口的读写其实还是相对简单的,上面代码基本上把可能出现问题的点都体现出来了,最后再来罗列一下注意事项。
最近要用到 libeay32.lib、ssleay32.lib 两个静态库文件,因为以前项目中其中一个文件在 64 位编译时选择的不是 MTd,而导致我引入该库以后提示运行时库和其他库声明冲突,其实实际原因就是生成选项不一样。最终我还是决定自己编译 openssl 的库来使用。
安装 Perl 和 NASM,默认下一步下一步就可以了。Perl 安装的时候记得勾选将执行程序添加到系统环境变量中。NASM 安装时没有选项,需要在完成后要将执行程序添加到系统的环境变量中。如下图所示:
解压 openssl-1.0.2p.tar.gz 到任意目录,比如 D:\openssl-1.0.2p
打开 VS 的命令行工具(我这里安装的是 VS2013),所以目录在 C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts
下,如果想编译 32 位的静态库就使用 VS2013 x86 本机工具命令提示
,如果想编译 64 位,就使用 VS2013 x64 本机工具命令提示
。
打开后切换到 D:\openssl-1.0.2p 目录,执行如下命令生成 makefile 文件。
perl configure VC-WIN32
如果是生成 64 位则执行
perl configure VC-WIN64A
成功后如下所示:
如果你要编译 debug 版本,则修改 ms/do_nasm.bat
文件,将原来
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl nasm VC-WIN32 >ms\nt.mak
perl util\mk1mf.pl dll nasm VC-WIN32 >ms\ntdll.mak
perl util\mk1mf.pl nasm BC-NT >ms\bcb.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
修改为:
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl nasm debug VC-WIN32 >ms\nt.mak
perl util\mk1mf.pl dll nasm debug VC-WIN32 >ms\ntdll.mak
perl util\mk1mf.pl nasm BC-NT >ms\bcb.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
就是将第二行和第三行编译选项增加了 debug。修改完成后执行 ms/do_nasm.bat
。
ms\do_nasm.bat
运行后结果如下:
修改完成执行如下命令开始编译(如果想编译成 dll,则执行 nmake -f ms\ntdll.mak,编译前要修改 ms\ntdll.mak 将 CFLAG 的 /MD 属性修改为 /MT,与你调用项目匹配):
nmake -f ms\nt.mak
如果没有错误,几分钟后编译后的文件就会生成于 D:\openssl-1.0.2p\out32
目录下。
wxsqlite3 相比普通的 sqlite3 的好处就是可以对数据库进行加密,但这个 wxsqlite3 的编译过程真的是非常曲折,我自己也是折腾了几个小时才真正编译出来一个可用的 wxsqlite3 的静态库文件。那么接下来废话不多说开始准备环境。
处理文件路径信息是经常要用到的字符串处理的手段,应用场景非常的多,不论是 Linux 还是 Windows,在我没接触这一系列函数之前,都是使用一系列字符串处理函数来自己写。而在 Windows 环境下,系统给我们提供了一系列处理路径相关的 API,我们在需要使用的时候直接调用即可,不但可以避免自己使用字符串处理函数处理时可能造成的各种问题,还可以加快我们编程的速度。当然如果你还没有使用字符串处理函数自己处理过路径等信息,我强烈建议你先自己尝试学习一下。轮子可不重复制造,但你必须要清楚轮子的制作工艺,否则在出现故障时就不知道如何处理了。
从 www.visualstudio.com 下载的 VS2017RC 安装包只有离线版本的,搜索了一下网络上的资料,在微软的官网上找到了解决方案。地址:https://docs.microsoft.com/en-us/visualstudio/install/create-an-offline-installation-of-visual-studio
在国内下载这些资源速度非常的慢,没办法我自己挂了个 VPN,下载了一份离线的安装包,有需要的从百度网盘下载运行里面的 setup 程序就可以了。
以前写过一些脚本调用 Shell Object 实现的解锁和锁定程序到任务栏的功能,这几天刚好用到,却不想用脚本实现,由于没有了解过 VC 如何操作 Shell,所以就在 github 上搜索了一下,找到了示例代码,自己修改了一下以后备用。代码有使用方法和注释。
用过 VS2012 以上版本的人心里肯定清楚,想通过 Help Viewer 去下载帮助文档,那速度简直无法忍受,选择几个项目一晚上甚至几天都下载不完。很多人被逼的直接用 Google 在线的帮助了,搜索一下函数名结果就直接到 MSDN 的页面中。但并不是所有时候都有网络的,所以最终希望还是要寄托在 Help Viewer 上,但是这货下载速度的确太慢了,为了解决这个问题本文提供两种方案。一种是使用网络上其他人下载好的离线包直接安装(比较大),另外一种就是用一个 CodePlex 上提供的下载工具 Visual Studio 2012/2013/2015 Help Downloader
进行下载。
在日常开发过程中,我们常常用如下这种形式的结构体来传递数据。
typedef struct _PATH_INFO {
HANDLE hPPid; // 父进程 PID
HANDLE hPid; // 子进程 PID
ULONG PathLength; // 子进程路径长度
TCHAR Path[1]; // 用于存储子进程路径
} PATH_INFO, *PPATH_INFO;
其中前三个成员用来描述一个进程的父进程PID和自身进程的PID以及路径长度信息,而最后一个成员来描述该路径的实际内容,由于路径长度是不定的,我们为了节省内存,加了一个 PathLength 的成员来描述路径长度,不会将实际储存路径的成员设置成固定的 512 大小或者 1024 大小,这样会非常浪费内存,在使用过程中,我们会想如下这种方式来给结构体分配内存和填充数据。
你有没有发现,使用笔记本开发 Windows 程序的时候,屏幕的宽度虽然可以满足 Visual Studio 的工具栏和代码编辑器的位置,但是高度却很不尽人意。能看到代码的位置很有限,如果再把输出、搜索等窗口放到代码编辑框的下面,那看代码的空间有少了很多,哪怕你是高分辨率的屏幕也会有同样的感受。所以对使用笔记本开发程序的人来说,Visual Studio 纵向显示内容的区域真的是寸土寸金。
文本主要介绍 Win10 系统下安装 VS2015 及 WDK10 驱动开发环境的搭建,搭建完成后可通过 VS2015 直接新建驱动项目并编译通过后可在 Win7 以上系统运行。无需自己制作模版等繁琐步骤(其实早在 VS2013+WDK8.1 的时候就已经支持直接从 IDE 中新建项目)。以下是相关截图:
如果你进入本文时,若对与 多字节
Unicode
ASCII
等这些概念上不清楚的话,请转到如下文章学习:
API入门系列之一 -那‘烦人’的Windows数据类型
http://blog.csdn.net/beyondcode/article/details/4015769
API入门系列之二 -Unicode还是ASCII
http://blog.csdn.net/beyondcode/article/details/4018731
API入门系列之三 -那迷惑人的Windows字符和字符指针类型
http://blog.csdn.net/beyondcode/article/details/4021085