1般的程序会包括多层的类封装,这里我们将最外层类撤除,也就是说这个类拆掉后,类里面的函数都成了全局函数。
如果你有1大把public变量的话,聪明的你可以将变量改成查询函数,将其内联并直接return便可:
// 1种查询底盘运行状态的函数,返回数据包的首地址并报告包长度
unsigned char* QueryState(int queryFlag, unsigned short* pkgLength); // 常见的查询函数
// 1个人物跟踪器状态报告函数(直接返回变量的查询函数)
unsigned int QueryPerson(int queryFlag); // 里面1个大switch然后各种return
如果有构造和析构函数,在public里另写1个Init和Release函数。
// 假定我们有这样1个类
class MecanumController
{
public:
enum ChassisState
{
CHASSIS_BASE,
CHASSIS_VOLTAGE,
CHASSIS_CURRENT,
CHASSIS_ERRCODE,
// ...
};
public:
bool Open(const char* port_name);
unsigned char SendByte(unsigned char data);
unsigned char Move(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed);
unsigned char* QueryState(int queryFlag, unsigned int bytesLength);
private:
HANDLE usart_handle;
unsigned char ReceiveByte(unsigned char data);
void Helper(void* lp);
};
可以发现,这里有1些private变量,依照常理它应当被封装起来不可见,这里我们拆开后照旧写在外便可。
清静起见,你可以把private里面的声明放在源文件代码的上面。
对enum或typedef,如果你的函数的参数直接把枚举名字写上去了,建议改成int来减少改动。
清静起见,把enum和typedef放在源文件代码的上面。
PS:你也能够再写1个头文件,如果头文件只含枚举等方便调用的定义内容,还可与DLL1起发布,便于开发者的查看,视封装的复杂程度而定,自己进行平衡。
头文件变化以下,class段完全删除,只剩下全局声明:
// 类中的public函数
bool Open(const char* port_name);
unsigned char SendByte(unsigned char data);
unsigned char Move(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed);
void* QueryState(int queryFlag, unsigned int bytesLength);
在源文件里的变化不过是删除MecanumController::并把private里的那些声明拷贝进去。
此时,我们的头文件里只剩下了public函数。
现在,我们在头文件中加入:
#define XXXAPI extern "C" _declspec(dllexport)
// 叫XXXAPI只是1个习惯,XXX代表了1些名字,你也能够起1个其他的名字或直接不define
并修改这几个函数声明,修改后的头文件像这样:
//#include"xxx"
#define output extern "C" _declspec(dllexport)
output bool Open(const char* port_name);
output unsigned char SendByte(unsigned char data);
output unsigned char Move(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed);
output void * QueryState(int queryFlag, unsigned int bytesLength);
如果你的工程不是DLL的,可以在解决方案里新建1个 并按之前所做的去配置环境,注意,你输出的DLL名称是和你工程名1样的,建议起好名字,如果不嫌麻烦的话你导出后你可以重命名1下。
按这样配置后就是1个DLL工程了。
如果你是win32工程且设置为DLL空项目,照上面修改以后在解决方案管理器中对其右键点生成便可导出。
没有手抖的话,在输出里会看到以下信息:
Windows提供以下Windows API用于DLL的装载、报告函数入口和DLL的卸载
分别是:
// DLL装载
HINSTANCE LoadLibraryW(LPCWSTR lpLibFileName); // Unicode工程使用wchar_t
HINSTANCE LoadLibraryA(LPCSTR lpLibFileName); // MultiByte工程使用char
// 报告函数入口
FARPROC GetProcAddress(HINSTANCE hModule, _In_ LPCSTR lpProcName);
// DLL卸载
BOOL FreeLibrary(HINSTANCE hLibModule);
LoadLibraryW和LoadLibraryA可以通过宏定义LoadLibrary自动选择正确的函数,所以我们直接叫这个函数为LoadLibrary,如果函数成功读取并载入DLL于内存,则返回1个非0的HINSTANCE变量,否则返回NULL。
FARPROC是1个整形变量(int),在minwindef.h中定义。
#ifdef _WIN64
typedef INT_PTR (FAR WINAPI *FARPROC)();
...
#else
typedef int (FAR WINAPI *FARPROC)();
...
在不同解决方案下的长度视工程的目标平台而定,x64对应64bit整形,x86对应32bit整形,其它未定义的目标平台同32bit整形。GetProcAddress本身会返回1个存储函数入口地址的整形变量。
FreeLibrary正常使用便可,传入HINSTANCE,如果DLL被成功载入则通过DLL载入时给出的HINSTANCE值来卸载DLL。
1个DLL通过LoadLibrary和FreeLibrary可以被屡次使用,对1个DLL文件第1次使用LoadLibrary时,Windows会检查并将DLL,如果DLL适用则载入内存,DLL占用的计数器加1,当DLL被载入后继续被其它代码中的LoadLibrary使用时,Windows会制作1个内存映照来提高空间效力,计数器继续加1。FreeLibrary是将计数器减1,计数器为0时,Windows从内存中卸载DLL,否则只删除对应HINSTANCE的映照。(个人理解,不管第几次载入DLL,DLL只有1个副本存在于内存中,且每次载入都会产生1个内存映照(镜像)以便于资源管理,对释放而言,计数器为0时除删除映照外还多了1个delete操作)
终究我们要封装成1个类供开发者使用:流程是LoadLibrary,如果成功则用GetProcAddress初始化函数入口,释放时履行FreeLibrary。
我们给开发者的时候还是1个类封装,头文件内容以下:
// MecanumController.h
#pragma once
#include<Windows.h>
class MecanumController
{
public:
typedef float * State_Value;
typedef unsigned short * State_Code;
typedef unsigned char * State_Package;
enum ChassisState
{
CHASSIS_BASE,
CHASSIS_VOLTAGE,
CHASSIS_CURRENT,
CHASSIS_ERRCODE
};
public:
// INIT&UINIT
MecanumController(const char* port_name); // 不建议直接使用,直接使用不能肯定实例是不是可用,且产生未知的dll计数
void Release() { // 实例可用时卸载实例
FreeLibrary(hdll);
}
__inline static MecanumController * CreateInstance(const char* chassis_port_name) {
HINSTANCE hd=LoadLibrary(L"MecanumController.dll");
if(hd == NULL) return NULL;
FreeLibrary(hd);
return new MecanumController(chassis_port_name);
} // 如果实例可工作,返回1个实例地址,否则返回NULL
// FUNCTION
unsigned char(*SendByte)(unsigned char data);
unsigned char(*Move)(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed);
void *(*QueryState)(int queryFlag, unsigned int bytesLength);
private:
HINSTANCE hdll;
};
MecanumController::MecanumController(const char* port_name)
{
// 尝试载入DLL
hdll = LoadLibrary(L"MecanumController.dll");
if (hdll == NULL) return;
// 初始化串口,这里外部有helper来保证指定串口可用,普通场景不建议在这里写1个容易失败的流程
((bool(*)(const char* port_name))GetProcAddress(hdll, "Open"))(port_name);
// 配置函数入口
SendByte = (unsigned char(*)(unsigned char data))GetProcAddress(hdll, "SendByte");
Move = (unsigned char(*)(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed))GetProcAddress(hdll, "Move");
QueryState = (void *(*)(int queryFlag, unsigned int bytesLength))GetProcAddress(hdll, "QueryState");
}
初始化分为两个函数,其中构造函数去履行不会出错的流程,专有1个实例化函数来履行容易出错的流程,在确保成功后返回1个实例。
这个类里面长相奇异的就是函数指针了:
// FUNCTION
unsigned char(*SendByte)(unsigned char data);
unsigned char(*Move)(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed);
void *(*QueryState)(int queryFlag, unsigned int bytesLength);
由于运算符的优先级关系,我们写1个函数原型的指针时是这样
返回类型 (*名字)(参数),这样就给机器1个带栈模型的指针。
使用GetProcAddress时需要类型转换:
SendByte = (unsigned char(*)(unsigned char data))GetProcAddress(hdll, "SendByte");
Move = (unsigned char(*)(unsigned short TanslationSpeed, short Yaw, short RotationAngleSpeed))GetProcAddress(hdll, "Move");
QueryState = (void *(*)(int queryFlag, unsigned int bytesLength))GetProcAddress(hdll, "QueryState");
你也能够直接靠GetProcAddress履行1个函数:
((bool(*)(const char* port_name))GetProcAddress(hdll, "Open"))(port_name);
为了便于开发者使用并查询类型定义,我们还要把定义写进去。
你也能够写1个专门的xxxdef.h来存储大量的定义,但也有可能可能破坏了简单的封装,请自行决定。
public:
typedef float * State_Value;
typedef unsigned short * State_Code;
typedef unsigned char* State_Package;
enum ChassisState
{
CHASSIS_BASE,
CHASSIS_VOLTAGE,
CHASSIS_CURRENT,
CHASSIS_ERRCODE
};
我们写1个rundll的程序吧:
// App.c
#include "MecanumController.h"
#include <iostream>
using namespace std;
int main()
{
auto chassis = MecanumController::CreateInstance("COM3");
if (!chassis)
{
cerr << "找不到MecanumController.dll" << endl;
return -1;
}
for (size_t i = 0; i < 100; i++) chassis->Move(i,0,0);
system("pause");
return 0;
}
下一篇 实现微信发送位置效果