[C++实现&C#调用] 如何遍历DLL导出函数
当我们想要使用动态链接库(DLL)提供的函数时.
首先需要知道这个DLL中都有哪些函数可以调用,在Windows中,我们可以使用PE文件格式的导出表来查看和调用DLL中的函数.
本文将介绍如何使用C++遍历一个DLL的导出函数.
并在不使用CPP/CLI编写中间层代码,二次封装DLL
A.std::vector<std::vectorstd::string> GetDllFunctionInfo(const char* moduleName)函数代码
std::vector<std::vector<std::string>> GetDllFunctionInfo(const char* moduleName)
{
HMODULE hModule = LoadLibraryA(moduleName);
if (hModule == nullptr)
return {};
const IMAGE_DOS_HEADER* pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(hModule);
const IMAGE_NT_HEADERS* pNtHeader = reinterpret_cast<IMAGE_NT_HEADERS*>(reinterpret_cast<BYTE*>(hModule) + pDosHeader->e_lfanew);
const IMAGE_EXPORT_DIRECTORY* pExportDirectory = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(reinterpret_cast<BYTE*>(
hModule) + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
const DWORD* pAddressOfFunctions = reinterpret_cast<DWORD*>(reinterpret_cast<BYTE*>(hModule) + pExportDirectory->
AddressOfFunctions);
const DWORD* pAddressOfNames = reinterpret_cast<DWORD*>(reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNames);
const WORD* pAddressOfNameOrdinals = reinterpret_cast<WORD*>(reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNameOrdinals);
// 返回格式 [[函数名, 函数地址, 函数序号], ...]
std::vector<std::vector<std::string>> result;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
const char* pName = reinterpret_cast<char*>(reinterpret_cast<BYTE*>(hModule) + pAddressOfNames[i]);
const DWORD dwAddress = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
const DWORD dwOrdinal = pAddressOfNameOrdinals[i] + pExportDirectory->Base;
// 根据自身需求,选择是否直接函数内打印
// std::cout << "Function Name: " << pName << std::endl;
// std::cout << "Function Address: " << dwAddress << std::endl;
// std::cout << "Function Ordinal: " << dwOrdinal << std::endl;
// std::cout << std::endl;
result.push_back({pName, std::to_string(dwAddress), std::to_string(dwOrdinal)});
}
return result;
}
A.函数解析
这个函数的作用是遍历一个给定模块(module)中的所有导出函数,并返回一个包含每个导出函数名称、地址和序号的二维vector.
函数接受一个指向模块名称的const char*类型参数,并使用LoadLibraryA函数加载指定模块.如果加载失败,则返回一个空vector。
接下来,函数使用PE文件格式的导出表来查找模块中的导出函数.具体来说,它首先通过模块句柄(HMODULE)获取PE文件的DOS头(IMAGE_DOS_HEADER)和NT头(IMAGE_NT_HEADERS),然后使用导出表数据目录中的地址(IMAGE_DIRECTORY_ENTRY_EXPORT)获取导出表结构(IMAGE_EXPORT_DIRECTORY).导出表结构包含有关导出函数的信息,例如函数数量、名称指针、地址表和序号表.
然后,函数使用名称指针和序号表来遍历导出函数.对于每个导出函数,它获取函数名称、地址和序号,并将它们存储在一个包含三个元素的vector中.最后,它将所有这些向量存储在一个二维vector中,并将其返回.
需要注意的是,此函数返回的所有字符串都是std::string类型,因此需要将地址和序号转换为字符串,以便可以将它们存储在vector中.此外,由于函数使用了C++标准库的vector和string,因此需要在源文件中包含和头文件.
A.例子
该函数不论是导出DLL,还是直接调用,都可以在C++项目中直接使用
例子:
// 获取指定模块中的所有导出函数信息并打印:
std::vector<std::vector<std::string>> functionInfo = GetDllFunctionInfo("kernel32.dll");
for (const auto& function : functionInfo)
{
std::cout << "Function Name: " << function[0] << std::endl;
std::cout << "Function Address: " << function[1] << std::endl;
std::cout << "Function Ordinal: " << function[2] << std::endl;
std::cout << std::endl;
}
// 获取指定模块中的所有导出函数信息并保存到文件中:
std::vector<std::vector<std::string>> functionInfo = GetDllFunctionInfo("user32.dll");
std::ofstream file("user32_functions.txt");
for (const auto& function : functionInfo)
{
file << "Function Name: " << function[0] << std::endl;
file << "Function Address: " << function[1] << std::endl;
file << "Function Ordinal: " << function[2] << std::endl;
file << std::endl;
}
file.close();
// 获取指定模块中的指定函数信息:
std::vector<std::vector<std::string>> functionInfo = GetDllFunctionInfo("my_module.dll");
std::string functionName = "MyFunction";
std::vector<std::string> result;
for (const auto& function : functionInfo)
{
if (function[0] == functionName)
{
result = function;
break;
}
}
if (result.empty())
std::cout << "Function not found." << std::endl;
else
{
std::cout << "Function Name: " << result[0] << std::endl;
std::cout << "Function Address: " << result[1] << std::endl;
std::cout << "Function Ordinal: " << result[2] << std::endl;
}
但请注意,该函数无法直接被C#使用,需要CLI二次封装.
B.void GetDllFunctionInfo(const char* moduleName, char* result) 函数源码
void GetDllFunctionInfo(const char* moduleName, char* result)
{
std::vector<std::vector<std::string>> info = GetDllFunctionInfo(moduleName);
std::string str;
for (const auto& i : info)
{
for (const auto& j : i)
str += j + " ";
str += "\n";
}
strcpy(result, str.c_str());
}
B.函数解析
这个函数的作用是获取给定模块中的所有导出函数信息,并将其以字符串形式返回给调用者。
函数接受一个指向模块名称的const char类型参数以及一个指向结果字符串的char类型参数。它首先调用GetDllFunctionInfo函数获取模块中的所有导出函数信息,并将其存储在一个二维vector中。然后,它将所有信息格式化为一个字符串,并将其复制到结果字符串中。最后,它将结果字符串返回给调用者。
需要注意的是,此函数使用了C++标准库的vector和string,因此需要在源文件中包含和头文件。此外,由于函数使用了strcpy函数,因此需要在源文件中包含<string.h>头文件。同时,调用者需要确保传递给result参数的字符串缓冲区足够大,以避免缓冲区溢出的问题。
如果开发环境为Windows,还请将strcpy函数替换为微软提供的strcpy_s,以确保安全性,或者使用其他方式.
该函数可以在不使用CLI二次封装代码的情况下,让C#直接调用.
B.例子
C++
获取指定模块中的所有导出函数信息并将其保存在一个字符串中:
char result[4096];
GetDllFunctionInfo("kernel32.dll", result);
std::cout << result << std::endl;
获取指定模块中的所有导出函数信息并将其保存到文件中:
char result[8192];
GetDllFunctionInfo("user32.dll", result);
std::ofstream file("user32_functions.txt");
file << result;
file.close();
获取指定模块中的指定函数信息并将其保存在一个字符串中:
char result[1024];
GetDllFunctionInfo("my_module.dll", result);
std::string functionName = "MyFunction";
std::string strResult(result);
size_t pos = strResult.find(functionName);
if (pos == std::string::npos)
std::cout << "Function not found." << std::endl;
else
{
pos += functionName.length() + 1;
size_t endPos = strResult.find("\n", pos);
std::string functionInfo = strResult.substr(pos, endPos - pos);
std::cout << functionInfo << std::endl;
}
在这个例子中,我们首先调用GetDllFunctionInfo函数获取指定模块中的所有导出函数信息,并将其保存在一个字符串中。
然后,我们查找指定函数名称在字符串中的位置,并提取相关的信息。
如果函数不存在,我们将返回错误信息。如果函数存在,我们将提取相关信息,并将其打印在控制台中。
C#
从DLL导入
internal static class Support
{
// void GetDllFunctionInfo(const char* moduleName, char* result)
// 可以直接使用这个函数但为了使用方便性,最好重载
[DllImport(@"C:\Users\balab\RiderProjects\Solution\Debug\Support.dll")]
private static extern void GetDllFunctionInfo(string moduleName, StringBuilder result);
public static List<List<string>> GetDllFunctionInfo(string moduleName)
{
StringBuilder resultBuilder = new StringBuilder(4096);
GetDllFunctionInfo(moduleName, resultBuilder);
string resultString = resultBuilder.ToString();
List<List<string>> resultList = new List<List<string>>();
string[] lines = resultString.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
string[] parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
resultList.Add(new List<string>(parts));
}
return resultList;
}
}
调用
// 遍历导出函数
var exportFunction = Support.GetDllFunctionInfo("Support.dll");
foreach (var function in exportFunction)
{
string functionName = function[0];
string functionAddress = function[1];
string functionOrdinal = function[2];
Console.WriteLine("Function Name: {0}", functionName);
Console.WriteLine("Function Address: {0}", functionAddress);
Console.WriteLine("Function Ordinal: {0}", functionOrdinal);
Console.WriteLine();
}