写pbidea库时,有考虑把 sqlite3 作为一个功能封装进去,静态编译又觉得太大,所以决定把sqlite3.dll 当作插件动态加载使用。
官网下载的DLL,2个文件,sqlite3.dll 和sqlite3.def。通常是使用lib生成 sqlite3.lib,然后引入使用。这个方法不能动态加载作为插件使用,当sqlite3.dll不存在时,整个pbidea都会报错。所以,必须考虑LoadLibrary后作为插件使用。这就有个难题 :它有好几百个函数,需要自己一个一个去声明,太费劲了。而且不同版本,它函数也有变化。
下载了sqlite3的源码,研究了一下,发现其实它有个接口
struct sqlite3_api_routines {
void * (*aggregate_context)(sqlite3_context*,int nBytes);
int (*aggregate_count)(sqlite3_context*);
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
int (*bind_double)(sqlite3_stmt*,int,double);
int (*bind_int)(sqlite3_stmt*,int,int);
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
int (*bind_null)(sqlite3_stmt*,int);
…………..
static const sqlite3_api_routines sqlite3Apis = {
sqlite3_aggregate_context,
#ifndef SQLITE_OMIT_DEPRECATED
sqlite3_aggregate_count,
…………..
sqlite3Apis是可以直接导出所有函数指针的。那这个接口怎么取得呢?进一步研究,它这个接口是给它的扩展库使用的,具体的sqlite3_load_extension函数里面。sqlite3_load_extension函数的第一个参数是sqlite3 *db。那么问题来了:我还没加载它呢,哪来的sqlite3 *db,这就成了先有鸡还是先有蛋的问题了。看来,走扩展库这条路也行不通。而我又不想几百个函数去折腾声明一遍。
于是,编译源码,在源码里添加了一个导出函数:
SQLITE_API const sqlite3_api_routines* get_sqlite3_interface()
{
return &sqlite3Apis;
}
然后然后这个函数,果然可以取得一个接口,需要时LoadLibrary(“sqlite3.dll”),用完后FreeLibrary。挺爽的。
可是,又遇到一个新问题,不小心用官网下载的DLL覆盖后我编译的DLL后,整个接口消失了。所以,自己编译的DLL不能再命名为sqlite3.dll,需要改名。作为有点小洁癖的人来说,还是不够爽。
貌似到此处,已经没有办法了!
看到
static const sqlite3_api_routines sqlite3Apis = {}
忽然灵光一现,好象还能想点办法。能不能从DLL里查找到它呢?如果直接查找到它,就能直接取得这个接口,不必使用函数导出。
说干就干!(这个方法要求你对PE格式有一定了解)
//加载DLL
HMODULE m_dll_sqlite3 = LoadLibraryW(L"sqlite3.dll");
//取PE头信息
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)m_dll_sqlite3;
IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((unsigned long)pDosHeader + pDosHeader->e_lfanew);
IMAGE_OPTIONAL_HEADER* pOption = (IMAGE_OPTIONAL_HEADER*)&pNtHeader->OptionalHeader;
DWORD dwImageBase = pOption->ImageBase;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)m_dll_sqlite3 + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)m_dll_sqlite3 + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
DWORD* pAddressOfFunction = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (DWORD)m_dll_sqlite3);
DWORD* pAddressOfNames = (DWORD*)(pImageExportDirectory->AddressOfNames + (DWORD)m_dll_sqlite3);
DWORD dwNumberOfNames = (DWORD)(pImageExportDirectory->NumberOfNames);
WORD* pAddressOfNameOrdinals = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (DWORD)m_dll_sqlite3);
//获取所有导出函数
std::map<DWORD, char*> funcs;
for (int i = 0; i < (int)dwNumberOfNames; i++)
{
char* strFunction = (char*)(pAddressOfNames[i] + (DWORD)m_dll_sqlite3);
funcs[pAddressOfFunction[i] + dwImageBase] = strFunction;
}
unsigned long dwSectionNumber = pNtHeader->FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER* pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
IMAGE_DATA_DIRECTORY* pDataDirectory = pOption->DataDirectory;
for (DWORD i = 0; i < dwSectionNumber; i++)
{
DWORD dwBeginVA = pSectionHeader[i].VirtualAddress + dwImageBase;
DWORD dwEndVA = pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData + dwImageBase;
//static const sqlite3_api_routines* sqlite3_api; 所在 section,想知道为什么它在这里?经验很重要
if (memcmp(pSectionHeader[i].Name, ".rdata", 6) == 0)
{
DWORD* p = (DWORD*)dwBeginVA;
while (p < (DWORD*)dwEndVA)
{
//根据sqlite3_api_routines结构,成员是指向函数的指针,判断是不是 sqlite3_api
bool find = true;
for (int i = 0; i < 32; i++)
{
if (HIWORD(p[i]) && funcs.find(p[i]) == funcs.end())
{
find = false;
break;
}
}
if (find)
{
sqlite3_api = (const sqlite3_api_routines*)p;
break;
}
p++;
}
}
if (sqlite3_api)
break;
}
代码写好,编译完运行,果然取得了接口指针,完全实现了sqlite3.dll的动态加载使用了。
爽!爽!爽!
大自在,QQ:781770213
2023/9/19