关于 sqlite3.dll 动态加载使用问题

写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 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

你可能感兴趣的:(sqlite,数据库)