描述
- 将dll作为资源插入到程序中,程序运行过程中将资源加载到内存,作为dll释放,这种方式配合延迟加载dll,可以降低被发现的风险
- 原书里的程序用mfc写了个界面,我替换成了qt的版本,但是qt打包出来后程序过大,所以又用vs写了个不带界面版本的
win32 api原生版本
工具链
- 使用vs工具库编写,vc++的msvc编译器,调用win32原生api
- 去掉mfc界面部分的代码,去掉其预编译头文件stdafx.h
资源插入
- 折腾了好久才搞清楚,第一次点击添加资源时会报错
- 不用管,再次点击添加资源,此时成功进入添加面板,选择自定义类型,输入类型名
- 输入类型名确定后窗口会关闭,此时再次点击添加资源,点击导入,选择资源文件和刚才的类型名
- 成功导入文件后,点击resourse.h文件,查看插入资源文件的编号,下图中第二个编号即IDR_MYAPP2,就可以用来定位到插入的文件
代码
#include "ResourceFree.h"
void ShowError(const char* pszText)
{
char szErr[200] = { 0 };
::wsprintf(szErr, "%s Error [%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}
BOOL FreeResource(UINT ResourceName, const char* ResourceType, const char* FileName)
{
HRSRC hRsrc = ::FindResource(NULL, MAKEINTRESOURCE(ResourceName), ResourceType);
if (NULL == hRsrc)
{
ShowError("FindResource");
return FALSE;
}
DWORD dwSize = ::SizeofResource(NULL, hRsrc);
if (0 >= dwSize)
{
ShowError("SizeofResource");
return FALSE;
}
HGLOBAL hGlobal = ::LoadResource(NULL, hRsrc);
if (NULL == hGlobal)
{
ShowError("LoadResource");
return FALSE;
}
LPVOID lpVoid = ::LockResource(hGlobal);
if (NULL == lpVoid)
{
ShowError("LockResource");
return FALSE;
}
FILE* fp = NULL;
fopen_s(&fp, FileName, "wb+");
if (NULL == fp)
{
ShowError("WriteResource");
return FALSE;
}
fwrite(lpVoid, sizeof(char), dwSize, fp);
fclose(fp);
return TRUE;
}
int main(int argc, char* argv[])
{
char fileName[200] = "520.txt";
BOOL bRet = FreeResource(IDR_MYRES2, "MYRES", fileName);
if (bRet == FALSE)
{
::MessageBox(NULL, "Free Resource Error!", "ERROR", MB_OK);
}
else
{
::MessageBox(NULL, "Free Resource OK!", "OK", MB_OK);
}
}
运行结果
- 可以看到exe只有12.5KB,双击运行exe后,成功在当前目录释放资源文件520.txt
qt界面版
工具链
- 使用qt库来编写界面程序,qt库是对win32 api的上层封装,由于qt库不是系统自带的,打包后需要将整个程序连带库文件放置到目标机器,占据磁盘空间较大
- IDE使用Qt Creator,编译器使用mingW
- 代码中字符串转换报错的话,需要在pro文件中添加下面这行代码:
DEFINES += QT_DEPRECATED_WARNINGS
- 如果要使用原生win32 api,需要在pro文件中添加库支持
LIBS += "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64\User32.Lib"
资源插入
- 新建Resources文件夹,右键点击添加资源文件
- 为资源文件添加前缀
- 右键资源文件,选择导入现有文件520
- 成功插入资源后的界面,之后在代码中就可以用
:/res/520
来访问该资源文件了
代码
- 界面用Qt Designer设计,拖拽一个按钮控件,绑定释放资源的槽函数,槽函数中,用qt库中的
file.copy
函数,可将资源文件释放到本地
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::OnButtonClicked);
}
void MainWindow::OnButtonClicked()
{
QString resProfix = "res";
QString resFileNmae = "520";
QString destFile = "./520.txt";
ReleaseSource(resProfix, resFileNmae, destFile);
}
void MainWindow::ReleaseSource(QString resProfix, QString resFileName, QString destFullPathFileName)
{
QString resFile;
resFile = ":"+resProfix+"/"+resFileName;
QFile file;
file.copy(resFile, destFullPathFileName);
}
MainWindow::~MainWindow()
{
delete ui;
}
结果
- 构建程序后,把生成的exe放到一个新文件夹,运行qt desktop命令行工具,进入新目录下,运行
windeployqt ResourceFree.exe
,即可完成程序打包
- 程序打包后的文件夹有47M,其中包含了QtCore等核心库文件,要移植到目标机器使用需要将整个文件夹都复制过去
- 运行程序,点击释放资源按钮,在本目录下释放出520.txt文件
总结
- Qt:写界面很方便,上层的API用起来更简单舒适,但是打包后的程序很大,适合写通信程序的本地服务端
- win32 api: 使用系统自带的dll,打包后的程序很小,可以使用强大的vs来编写,且底层api能实现更细腻的功能,适用于编写在目标机器上运行的控制台程序