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;
}
下一篇 實現微信發送位置效果