C++11 DllParser

本文介绍了一个DLL解析器的设计与实现,通过C++代码展示了如何加载和卸载DLL,获取DLL中的函数地址,并安全地调用这些函数。解析器使用了模板和函数对象来提高代码的通用性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <string>
#include <map>
#include <functional>

using namespace std;
class DllParser
{
public:
	DllParser():m_hMod(nullptr)
	{
			
	}
	~DllParser()
	{
		UnLoad();
	}

	bool Load(const std::string & dllPath)
	{
		m_hMod = LoadLibraryA(dllPath.data());
		if (nullptr == m_hMod)
		{
			cout << "LoadLibrary failed" << endl;
		}
		return true;
	}

	bool UnLoad()
	{
		if (nullptr == m_hMod)
			return true;
		auto b = FreeLibrary(m_hMod);
		if (!b)
			return false;
		m_hMod = nullptr;
		return true;
	}

	template<typename T>
	function<T> GetFunction(const string& funcName)
	{
		auto it = m_map.find(funcName);
		if (it == m_map.end())
		{
			auto addr = GetProcAddress(m_hMod, funcName.c_str());
			if (!addr)
				return nullptr;
			m_map.insert(make_pair(funcName, addr));
			it = m_map.find(funcName);
		}
		return function<T>((T*)(it->second));
	}

	template<typename T,typename... Args>
	typename result_of<function<T>(Args...)>::type ExcecuteFunc(string& funcName,Args&&... args)
	{
		auto f = GetFunction<T>(funcName);
		if (nullptr==f)
		{
			string s = "can not find this function "+funcName;
			throw exception(s.c_str());
		}
		return f(forward<Args>(args)...);
	}
private:
	HMODULE m_hMod;
	map<string, FARPROC> m_map;
};

int main()
{
	/*
		类似于如下调用
		auto max = ExcecuteFunc<int(int,int)>("Max",5,8);
		auto ret = ExecuteFunc<int(int)><"Get",5);	
	*/
}

 

<think>由于问题涉及在Java中解析DLL返回的指针数据,且不依赖DLL库(这里可能指的是不依赖特定的第三方库),我们可以使用Java Native Access (JNA) 或 Java Native Interface (JNI) 来实现。JNA相对JNI来说更为简单,因为它允许直接调用本地方法而无需编写C/C++代码。但是,解析指针数据需要谨慎处理内存布局和数据类型。 问题中要求“不依赖dll库”,我理解为不依赖额外的第三方库(除了JNA这样的通用桥接库)。因此,我们可以使用JNA,它是一个开源的库,提供了访问本地共享库(如Windows的DLL)的简便方法。 步骤: 1. 添加JNA依赖到项目中(如果使用Maven等构建工具)。 2. 定义一个接口,映射DLL中的函数。 3. 调用DLL函数并处理返回的指针数据。 注意:解析指针数据需要知道数据的结构。假设DLL返回一个指向结构体的指针,我们需要在Java中定义一个对应的结构体类。 示例:假设DLL中有一个函数返回一个指向以下结构体的指针: ```c typedef struct { int id; char name[20]; double value; } MyStruct; ``` 在Java中使用JNA的步骤: 1. 添加JNA依赖(以Maven为例): ```xml <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.10.0</version> </dependency> ``` 2. 定义Java中的结构体类,继承自`com.sun.jna.Structure`: ```java import com.sun.jna.Structure; import java.util.Arrays; import java.util.List; public class MyStruct extends Structure { public int id; public byte[] name = new byte[20]; public double value; @Override protected List<String> getFieldOrder() { return Arrays.asList("id", "name", "value"); } } ``` 3. 定义接口,映射DLL中的函数: ```java import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Pointer; public interface MyDll extends Library { MyDll INSTANCE = Native.load("MyDll", MyDll.class); // 假设DLL中有一个函数:MyStruct* getData(); Pointer getData(); } ``` 4. 调用DLL函数并解析返回的指针: ```java public class Main { public static void main(String[] args) { // 调用DLL函数,返回一个指针 Pointer ptr = MyDll.INSTANCE.getData(); // 将指针映射到MyStruct结构体 MyStruct data = new MyStruct(); data.useMemory(ptr); // 使用指针指向的内存 data.read(); // 从内存中读取数据 // 将name从byte数组转换为字符串(假设是ASCII编码) String name = new String(data.name).trim(); System.out.println("id: " + data.id); System.out.println("name: " + name); System.out.println("value: " + data.value); } } ``` 注意:实际使用中需要确保DLL的函数签名和调用约定(如stdcall或cdecl)正确。如果DLL函数使用了stdcall,在加载库时可以使用`Native.loadLibrary`的另一个重载方法指定调用约定。 如果返回的指针是一个数组,则需要使用`Structure`的`toArray`方法。 例如,如果函数返回一个指向MyStruct数组的指针,并且数组长度为n: ```java // 假设函数为:MyStruct* getArray(int* length); // 在Java中,可以这样定义: public interface MyDll extends Library { // 注意:参数是一个IntByReference,用于返回数组长度 Pointer getArray(IntByReference length); } // 调用时: IntByReference ref = new IntByReference(); Pointer ptr = MyDll.INSTANCE.getArray(ref); int length = ref.getValue(); MyStruct[] array = (MyStruct[]) new MyStruct().toArray(length, ptr); for (MyStruct struct : array) { struct.read(); // 读取每个结构体的数据 // ... 处理数据 } ``` 重要:使用指针时,需要确保内存管理正确。如果DLL返回的指针是由DLL分配的内存,那么也应该由DLL提供释放内存的函数,并在Java中调用该函数来释放,以避免内存泄漏。 以上是使用JNA的解决方案。如果不想使用JNA,也可以使用JNI,但JNI需要编写C/C++代码,并且配置更复杂。 因此,推荐使用JNA,因为它简化了本地代码访问。 注意:JNA只能调用DLL中导出的函数,并且需要知道确切的函数签名和数据结构。 如果DLL返回的指针指向的数据类型不是结构体,而是基本类型数组或其他复杂类型,需要相应地处理。 相关问题:</think>### 在 Java 中解析 DLL 返回的指针数据(不依赖额外 DLL 库) 在 Java 中解析 DLL 返回的指针数据,推荐使用 **Java Native Access (JNA)** 库。它无需编写本地代码(JNI)或依赖额外 DLL,直接在 Java 中映射本地内存数据。以下是完整解决方案: #### 步骤 1:添加 JNA 依赖(Maven) ```xml <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.13.0</version> </dependency> ``` #### 步骤 2:定义 DLL 函数接口 假设 DLL 函数为:`void* getData()`,返回一个结构体指针: ```c typedef struct { int id; char name[32]; double value; } DataStruct; ``` Java 映射代码: ```java import com.sun.jna.*; import java.util.*; // 1. 定义结构体 public class DataStruct extends Structure { public int id; public byte[] name = new byte[32]; // 对应 char[32] public double value; @Override protected List<String> getFieldOrder() { return Arrays.asList("id", "name", "value"); } // 将字节数组转为字符串 public String getName() { return new String(name).trim(); } } // 2. 定义 DLL 接口 public interface MyDll extends Library { MyDll INSTANCE = Native.load("MyLibrary", MyDll.class); // MyLibrary.dll // 映射函数:void* getData(); Pointer getData(); } ``` #### 步骤 3:解析指针数据 ```java public class DllParser { public static void main(String[] args) { // 调用 DLL 函数获取指针 Pointer ptr = MyDll.INSTANCE.getData(); // 将指针映射到结构体 DataStruct data = new DataStruct(); data.useMemory(ptr); // 绑定指针内存 data.read(); // 从本地内存读取数据 // 打印解析结果 System.out.println("ID: " + data.id); System.out.println("Name: " + data.getName()); // 转换字节为字符串 System.out.println("Value: " + data.value); } } ``` #### 关键代码解释: 1. **结构体映射**: - `DataStruct` 继承 `Structure`,字段与 C 结构体严格对应 - `getFieldOrder()` 定义字段顺序(必须与 C 一致) - `byte[]` 映射 `char[]`,通过 `trim()` 处理字符串 2. **指针处理**: - `Pointer ptr` 接收 DLL 返回的指针 - `useMemory(ptr)` 将 Java 对象绑定到本地内存 - `read()` 从内存中复制数据到 Java 对象 3. **内存管理**: - JNA 自动管理内存映射,无需手动释放 - 若 DLL 返回堆内存,需在 DLL 中提供释放函数并在 Java 调用 #### 处理复杂情况 **案例 1:解析指针数组** ```java // DLL 函数:DataStruct** getDataArray(int* size); Pointer getDataArray(IntByReference sizeRef); // 调用示例 IntByReference sizeRef = new IntByReference(); Pointer arrayPtr = MyDll.INSTANCE.getDataArray(sizeRef); int size = sizeRef.getValue(); // 遍历指针数组 DataStruct[] dataArray = new DataStruct[size]; for (int i = 0; i < size; i++) { Pointer elemPtr = arrayPtr.getPointer(i * Pointer.SIZE); DataStruct elem = new DataStruct(); elem.useMemory(elemPtr); elem.read(); dataArray[i] = elem; } ``` **案例 2:处理字符串指针** ```c // C 函数:const char* getErrorMessage(); ``` Java 映射: ```java // 接口方法 String getErrorMessage(); // 直接返回字符串(JNA 自动转换 char* 为 String) ``` #### 注意事项: 1. **数据类型映射**: - `int` ↔ `int` - `char*` ↔ `String` - `double*` ↔ `Pointer` + `getDoubleArray()` 2. **跨平台**: - 结构体内存对齐需一致(使用 `Structure.ALIGN_NONE` 调整) 3. **错误处理**: - 捕获 `LastErrorException` 获取系统错误码 4. **性能**: - 频繁调用时考虑 `Structure.ByReference` 避免重复内存复制 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小蚂蚁_CrkRes

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

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

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

打赏作者

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

抵扣说明:

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

余额充值