[C++实现&C#调用] 如何遍历DLL导出函数

10 篇文章 0 订阅
1 篇文章 0 订阅

当我们想要使用动态链接库(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();
}

C#调用DLL工作效果

C#调用C++DLL

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八宝咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值