目录
一、PIMPL模式概念:
PIMPL(pointer to implementation, 指向实现的指针)是一种用来对“类的接口与实现”进行解耦合的方法。就是将真正的实现细节的Implementor从类定义的头文件中分离出去,公有类通过一个私有指针指向隐藏的实现类,是促进接口和实现分离的重要机制,可以避免在头文件中暴露私有细节。
Pimpl 并不是严格意义上的设计模式(它是受制于 C++ 语言特定限制的变通方案),这种惯用法可以看作桥接设计模式的一种特例。
1.1 pImpl手法的优势和目的
1.1.1屏蔽实现细节
私有成员完全可以隐藏在共有接口之外,尤其对于闭源API的设计尤其的适合。此外,很多代码实现会依赖自身所在平台的宏等,这些琐碎的东西也不适合暴露给用户,避免造成干扰。
在动态库项目中,设备提供的原生动态库往往不符合平台需求,需要添加一层接口去做适配,而不同设备有自身的sdk库和使用细节,这些对平台而言均是干扰项,此时使用pImpl手法接入中间适配层可以有效的屏蔽掉各个设备自身的实现细节,提供给平台一个干净简洁的接口文件。
1.2 .2加速编译
这通常是用pImpl手法的最重要的收益,称之为编译防火墙(compilation firewall),主要是阻断了类的实现和类的实现两者的编译依赖性。这样,类用户不需要额外include不必要的头文件,同时实现类的成员可以随意变更,而公有类的使用者不需要重新编译。
在实际项目编译时间优化时,对于类似common.h的超大头文件除了按照功能等标准进行拆分外,更近一步对拆分的类的使用方法上可以使用pImpl手法,避免这些类的波动导致用这些类的用户类重新编译。
1.2.3 更好的二进制兼容性
通常对一个类的修改,会影响到类的大小、对象的表示和布局等信息,那么任何该类的用户都需要重新编译才行。而对于使用pImpl手法,如果实现变更被限制在实现类中,那公有类只持有一个实现类的指针,所以实现做出重大变更的情况下,pImpl也能够保证良好的二进制兼容性。
1.2.4 惰性分配
实现类可以做到按需分配或者实际使用时候再分配,从而节省资源提高响应。
二、PIMPL模式项目中应用:
2.1 项目背景
在高速公路收费站项目中,一般收费平台需要接入称重的地磅系统、车牌车型等识别的智能相机识别系统、ETC和天线扣费系统以及道闸管控系统等;对于此类项目各个省市的设备均有所不同,同一省市项目中也可能多家设备同时接入共同联调。本项目是以智能相机识别系统为背景,对于不同项目采购的不同类型识别相机进行平台动态库接口的封装,由于平台接口基本一致,但是各种类型设备却各有特点,因此采用Pimip模式屏蔽具体的设备接入部分,提供干净的统一接口给高速收费系统平台。
2.2 对外接口代码:
2.2.1 对外库接口头文件
#pragma once
#define WINAPI __cdecl
#define API __declspec(dllexport)
#include <windows.h>
#include <memory>
enum VPROPT_TYPE
{
OPT_VEHICLEINFO,
OPT_REVIEWINFO
};
typedef struct
{
int ID; //ID号
//车头车牌信息///
char plateStr[20]; //车头车牌号码,可能是空或无牌车
char plateColor[5]; //车头车牌颜色,包括蓝、白、黑、黄、绿、无
//车尾车牌信息///
char tailPlateNump[20]; //车尾车牌号码
char tailPalteColor[5]; //车尾车牌颜色
//车型信息
int carType; //车型结果[数值型],客车:1-4, (没有给11),货车:11-16,专项作业:21-26
char carTypeStr[64]; //车型结果[字符串],客1-客4,货1-货6,专1-专6 (没有给货1)
int szZhoushu; //轴数(没有给0)
int szLunshu; //轮数(没有给0)
//图片和视频//
char colpic[260]; //车辆侧面图绝对路径
char headpic[260]; //车头车牌图绝对路径
char tailpic[260]; //车尾车牌图绝对路径
char recfile[260]; //录像文件绝对路径
char platepic[260]; //车头车牌小图绝对路径
char plateBin[260]; //车头车牌二值图图绝对路径
}SCarInfoResult;
typedef struct
{
HWND hWnd; //窗口句柄
int msgNo; //自定义消息
}MessageCallBackInfo;
typedef int(*VehRec_GetCarDataFun)(int handle, SCarInfoResult pCarData);
typedef unsigned int VPR_HANDLE;
//对外接口类 与库文件一起提供给平台
class VehRecongize
{
public:
VehRecongize(char *iLogPath);
//动态库初始化
API int WINAPI VehRec_Init();
//动态库资源释放
API int WINAPI VehRec_Free();
//设备连接
API int WINAPI VehRec_Connect(char *devIP, char *savepath);
//设备断开
API void WINAPI VehRec_DisConnect(int handle);
//设置抓拍回调函数
API int WINAPI VehRec_SetCarDataFun(int handle, VehRec_GetCarDataFun pCallBack);
//注册序号
API int WINAPI VehRec_RegisterMessage(int handle, HWND hWnd, int msgNo);
//获取车辆抓拍信息
API int WINAPI VehRec_GetCarInfo(int handle, SCarInfoResult *pCarInfoRes);
//校验相机状态
API int WINAPI VehRec_CheckStatus(int handle, char* pStatus);
//开始视频播放
API int WINAPI VehRec_StartDisplay(int handle, int nWidth, int nHeight, int type, int nWinid);
//结束视频播放
API int WINAPI VehRec_StopDisplay(int handle, int nWinid);
//时间同步
API int WINAPI VehRec_SyncTime(int handle, char* sSyncTime);
//手动抓拍
API BOOL WINAPI VehRec_Capture(int handle);
//字符叠加
API BOOL WINAPI VehRec_AddCharacter(int handle,char*character);
private:
//具体camera的实现类可封装在CameraApiImplement中,此处以指针形式声明和调用具体设备接口
//达到对调用改库平台关于具体设备的细节屏蔽,提供干净的接口类
class CameraApiImplement;
std::unique_ptr<CameraApiImplement> pImip;
};
2.2.2 对外库接口部分实现:
这里只写了库的初始化、释放和设备连接的关键实现部分;对于库日志等共有的没必要在pimip中封装,只需将设备相关的功能接口和设备管理等进行封装到pimip即可,此处对外接口类即可实现基本不变,只需根据具体的设备不同修改pimip中的设备相关部分代码即可。
#include"vehRecongizeDll.h"
//实际设备相关的业务均屏蔽在pImip实现里面 此处调用pImip指针即可
//pImip抽象出通用的接口 具体不同设备的实现隐藏在pImip的实现类里面
VehRecongize::VehRecongize(char *iLogPath):pImip(std::make_unique<CameraApiImplement>())
{
}
API int WINAPI VehRecongize::VehRec_Init()
{
if (!g_logger) { g_logger = new Logger(iLogPath); }
log_sprintf(*g_logger, "VehRec_InitEx iLogPath=%s", iLogPath);
if (!pImip.g_DevMag)
{
pImip.g_DevMag = new DeviceManager(*g_logger, 3);
}
if (!pImip.g_DevMag->isSdkInit())
{
return -3;
}
return 0;
}
API int WINAPI VehRecongize::VehRec_Free()
{
log_sprintf(*g_logger, "Enter VehRec_Free ");
if (!pImip.g_DevMag)
{
return -99;
}
if (!pImip.g_DevMag->CameraAllLogoutAndCloseAlarm())
{
log_sprintf(*g_logger, "VehRec_Free error. %s", __func__);
return -1;
}
if (pImip.g_DevMag) { delete pImip.g_DevMag; pImip.g_DevMag = nullptr; }
if (pImip.g_iniFile) { delete pImip.g_iniFile; pImip.g_iniFile = nullptr; }
if (g_logger) { delete g_logger; g_logger = nullptr; }
return 0;
}
API int WINAPI VehRecongize::VehRec_Connect(char *devIP, char *savepath)
{
if (!g_logger) { g_logger = new Logger(); }
if (devIP == NULL || savepath == NULL)
{
log_sprintf(*g_logger, "VehRec_Connect param error!");
return -1;
}
log_sprintf(*g_logger, "Enter VehRec_Connect! devIP %s, savepath %s", devIP, savepath);
if (!g_iniFile) {
std::string path = GetModuleLocalPath("VehRecDll.dll");
if (path.empty())
{
log_sprintf(*g_logger, "iniFile path empty");
return -1;
}
path.append("Config.ini");
g_iniFile = new IniFile(path.c_str());
if (!g_iniFile->IsOpen())
{
log_sprintf(*g_logger, "iniFile open failure %s", path.c_str());
delete g_iniFile;
g_iniFile = NULL;
return -1;
}
}
int handle = -1;
if (g_iniFile->IsOpen())
{
std::string m_strUser;
std::string m_strPassword;
DWORD m_nPort;
// read camera config
m_strUser = g_iniFile->readstring("DETECTOR", "User", "admin");
m_strPassword = g_iniFile->readstring("DETECTOR", "Password", "abc12345");
m_nPort = (DWORD)g_iniFile->readinteger("DETECTOR", "Port", 8000);
if (!pImip.g_DevMag)
{
pImip.g_DevMag = new DeviceManager(*g_logger, 3);
}
if (handle = pImip.g_DevMag->CameraLoginAndAlarm(m_strUser.c_str(), m_strPassword.c_str(), devIP, m_nPort), handle < 1000)
{
log_sprintf(*g_logger, "VPR_Init Device failed IP %s", devIP);
return -4;
}
}
else
{
log_sprintf(*g_logger, "IniFile not open %s", __func__);
return -1;
}
pImip.g_DevMag->setSaveDir(savepath);
return handle;
}
//...
三、D指针的应用:
3.1D指针的基本概念
D指针(也被称为P指针或d指针)是Qt中Pimp模式的一种实现,用于隐藏类的实现细节,从而实现更好的封装性和二进制兼容性。它特别用于Qt中的许多核心类,以允许在不破坏二进制兼容性的情况下进行重大更改。 基本思路是这样的:每个公开的Qt类(例如QWidget)都有一个私有的实现类(例如QWidgetPrivate),并且这个公开类含有一个指向其私有实现类的指针(即D指针)。这样,大部分类的成员变量和函数都隐藏在这个私有实现类中,而公开的Qt类只包含一些基本的、稳定的接口。因此,Qt的版本升级中可以保证 在不破坏现有API的情况下添加新的功能或优化性能。
在Qt源代码中,D指针通常命名为d_ptr或d,并通过Q_D宏来访问。这个宏会检查D指针是否非空,并返回它的值。如果D指针为空(例如在对象的构造函数中),则Q_D宏会返回一个临时对象,该对象会触发断言,以防止访问未初始化的成员。 使用D指针是Qt实现其强大功能和稳定性的关键之一。这种设计模式并不只限于Qt,它在许多其他大型C++项目中也很常见。
3.2 D指针的使用举例
D指针涉及到的宏:
使用举例
可以看出Q纸质就是PIMPI的一种使用方式,只是通过宏进行包裹,在Qt中可以定式方便使用而已。