在跨语言调用中,如何确保传入的参数正确,尤其是字符串和结构体指针,避免崩溃或内存错误。
现在需要分步骤处理:
设计学生和课程的结构体,确保学生包含课程表。可能每个学生有一个课程数组,或者指向课程表的指针。
实现C++类来管理学生和课程,比如StudentManager类,内部使用容器存储学生,每个学生包含课程列表。
将C++类封装为C风格的API,使用句柄(void*)来隐藏对象实例,防止直接暴露C++对象。
定义导出函数,包括创建/销毁句柄、增删改查学生、遍历学生和课程的回调。
实现回调函数类型,比如学生回调和课程回调,允许用户传递自定义函数处理数据。
编译生成DLL,确保导出符号正确,可供其他语言调用。
在WPF中使用C#通过DllImport调用DLL函数,处理结构体和回调。需要注意结构体的内存布局和编码。
在Java中使用JNA定义接口,映射结构体和回调函数,处理嵌套的数据结构。
以下是分步骤的完整实现方案,涵盖 C++ 动态库封装、WPF (C#) 和 Java 的调用示例:
步骤 1:C++ 动态库实现(嵌套结构 + 回调)
1.1 头文件 StudentCourseDll.h
#ifndef STUDENT_COURSE_DLL_H
#define STUDENT_COURSE_DLL_H
#ifdef __cplusplus
extern "C" {
#endif
// 定义课程结构体
typedef struct {
int courseId;
char courseName;
} Course;
// 定义学生结构体(嵌套课程表)
typedef struct {
int studentId;
char studentName;
Course* courses; // 课程数组指针
int courseCount; // 课程数量
} Student;
// 定义句柄类型(隐藏 C++ 对象)
typedef void* StudentManagerHandle;
// ------------------ 回调函数类型定义 ------------------
// 学生信息回调
typedef void (*StudentCallback)(const Student* student, void* userData);
// 课程信息回调
typedef void (*CourseCallback)(const Course* course, void* userData);
// ------------------ 导出函数声明 ------------------
// 创建和销毁管理器
__declspec(dllexport) StudentManagerHandle create_manager();
__declspec(dllexport) void destroy_manager(StudentManagerHandle handle);
// 学生操作
__declspec(dllexport) void add_student(StudentManagerHandle handle, const Student* student);
__declspec(dllexport) bool delete_student(StudentManagerHandle handle, int studentId);
__declspec(dllexport) Student* find_student(StudentManagerHandle handle, int studentId);
__declspec(dllexport) void update_student(StudentManagerHandle handle, int studentId, const Student* newData);
// 遍历学生和课程(使用回调)
__declspec(dllexport) void foreach_student(StudentManagerHandle handle, StudentCallback studentCallback, void* userData);
__declspec(dllexport) void foreach_course(StudentManagerHandle handle, int studentId, CourseCallback courseCallback, void* userData);
#ifdef __cplusplus
}
#endif
#endif // STUDENT_COURSE_DLL_H
1.2 实现文件 StudentCourseDll.cpp
#include "StudentCourseDll.h"
#include <vector>
#include <algorithm>
// C++ 学生管理类
class StudentManager {
private:
std::vector<Student*> students;
public:
void addStudent(const Student* s) {
Student* newStudent = new Student(*s);
newStudent->courses = new Course[s->courseCount];
std::copy(s->courses, s->courses + s->courseCount, newStudent->courses);
students.push_back(newStudent);
}
bool deleteStudent(int id) {
auto it = std::find_if(students.begin(), students.end(),
[id](const Student* s) { return s->studentId == id; });
if (it != students.end()) {
delete[] (*it)->courses;
delete *it;
students.erase(it);
return true;
}
return false;
}
Student* findStudent(int id) {
auto it = std::find_if(students.begin(), students.end(),
[id](const Student* s) { return s->studentId == id; });
return (it != students.end()) ? *it : nullptr;
}
void updateStudent(int id, const Student* newData) {
Student* s = findStudent(id);
if (s) {
s->studentId = newData->studentId;
strncpy(s->studentName, newData->studentName, 50);
// 更新课程(此处简化逻辑)
delete[] s->courses;
s->courses = new Course[newData->courseCount];
s->courseCount = newData->courseCount;
std::copy(newData->courses, newData->courses + newData->courseCount, s->courses);
}
}
void foreachStudent(StudentCallback callback, void* userData) {
for (const auto& s : students) {
callback(s, userData);
}
}
void foreachCourse(int studentId, CourseCallback callback, void* userData) {
Student* s = findStudent(studentId);
if (s) {
for (int i = 0; i < s->courseCount; i++) {
callback(&s->courses[i], userData);
}
}
}
~StudentManager() {
for (auto s : students) {
delete[] s->courses;
delete s;
}
}
};
// ------------------ C 风格导出函数实现 ------------------
extern "C" {
StudentManagerHandle create_manager() {
return new StudentManager();
}
void destroy_manager(StudentManagerHandle handle) {
delete static_cast<StudentManager*>(handle);
}
void add_student(StudentManagerHandle handle, const Student* student) {
static_cast<StudentManager*>(handle)->addStudent(student);
}
bool delete_student(StudentManagerHandle handle, int studentId) {
return static_cast<StudentManager*>(handle)->deleteStudent(studentId);
}
Student* find_student(StudentManagerHandle handle, int studentId) {
return static_cast<StudentManager*>(handle)->findStudent(studentId);
}
void update_student(StudentManagerHandle handle, int studentId, const Student* newData) {
static_cast<StudentManager*>(handle)->updateStudent(studentId, newData);
}
void foreach_student(StudentManagerHandle handle, StudentCallback callback, void* userData) {
static_cast<StudentManager*>(handle)->foreachStudent(callback, userData);
}
void foreach_course(StudentManagerHandle handle, int studentId, CourseCallback callback, void* userData) {
static_cast<StudentManager*>(handle)->foreachCourse(studentId, callback, userData);
}
}
1.3 编译 DLL
使用 MinGW 编译命令:
g++ -shared -o StudentCourseDll.dll StudentCourseDll.cpp -I. -Wl,--out-implib,libStudentCourseDll.a -static-libgcc -static-libstdc++
步骤 2:WPF (C#) 调用示例
2.1 C# 结构体定义和 P/Invoke
using System;
using System.Runtime.InteropServices;
using System.Windows;
public partial class MainWindow : Window {
// 定义结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Course {
public int courseId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string courseName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Student {
public int studentId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string studentName;
public IntPtr courses; // Course 数组指针
public int courseCount;
}
// 定义回调委托
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void StudentCallback(IntPtr student, IntPtr userData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CourseCallback(IntPtr course, IntPtr userData);
// DLL 函数声明
[DllImport("StudentCourseDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr create_manager();
[DllImport("StudentCourseDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void destroy_manager(IntPtr handle);
[DllImport("StudentCourseDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void add_student(IntPtr handle, ref Student student);
[DllImport("StudentCourseDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void foreach_student(IntPtr handle, StudentCallback callback, IntPtr userData);
private IntPtr managerHandle;
public MainWindow() {
InitializeComponent();
managerHandle = create_manager();
// 添加学生和课程
Course[] courses = {
new Course { courseId = 1, courseName = "Math" },
new Course { courseId = 2, courseName = "Physics" }
};
Student student = new Student {
studentId = 101,
studentName = "Alice",
courseCount = courses.Length,
courses = Marshal.AllocHGlobal(Marshal.SizeOf<Course>() * courses.Length)
};
// 复制课程数据
IntPtr current = student.courses;
foreach (var course in courses) {
Marshal.StructureToPtr(course, current, false);
current = IntPtr.Add(current, Marshal.SizeOf<Course>());
}
add_student(managerHandle, ref student);
Marshal.FreeHGlobal(student.courses);
// 遍历学生
StudentCallback studentCallback = (studentPtr, userData) => {
Student s = Marshal.PtrToStructure<Student>(studentPtr);
Console.WriteLine($"Student: {s.studentName}");
};
foreach_student(managerHandle, studentCallback, IntPtr.Zero);
}
protected override void OnClosed(EventArgs e) {
destroy_manager(managerHandle);
base.OnClosed(e);
}
}
步骤 3:Java 调用示例(JNA)
3.1 Java 接口定义
import com.sun.jna.*;
import java.util.*;
public class TestStudentDll {
public interface StudentCourseDll extends Library {
StudentCourseDll INSTANCE = Native.load("StudentCourseDll", StudentCourseDll.class);
// 定义结构体
class Course extends Structure {
public int courseId;
public byte[] courseName = new byte;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("courseId", "courseName");
}
}
class Student extends Structure {
public int studentId;
public byte[] studentName = new byte;
public Pointer courses;
public int courseCount;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("studentId", "studentName", "courses", "courseCount");
}
}
// 回调接口
interface StudentCallback extends Callback {
void invoke(Pointer student, Pointer userData);
}
// 导出函数
Pointer create_manager();
void destroy_manager(Pointer handle);
void add_student(Pointer handle, Student.ByReference student);
void foreach_student(Pointer handle, StudentCallback callback, Pointer userData);
}
public static void main(String[] args) {
Pointer manager = StudentCourseDll.INSTANCE.create_manager();
// 添加学生
StudentCourseDll.Student.ByReference student = new StudentCourseDll.Student.ByReference();
student.studentId = 101;
student.studentName = "Alice".getBytes();
student.courseCount = 2;
// 创建课程数组
StudentCourseDll.Course[] courses = new StudentCourseDll.Course;
courses = new StudentCourseDll.Course();
courses.courseId = 1;
courses.courseName = "Math".getBytes();
courses = new StudentCourseDll.Course();
courses.courseId = 2;
courses.courseName = "Physics".getBytes();
// 分配内存并写入课程数据
int courseSize = new StudentCourseDll.Course().size();
student.courses = new Memory(courseSize * 2);
for (int i = 0; i < 2; i++) {
courses[i].write();
byte[] bytes = courses[i].getPointer().getByteArray(0, courseSize);
student.courses.write(i * courseSize, bytes, 0, bytes.length);
}
StudentCourseDll.INSTANCE.add_student(manager, student);
// 遍历学生
StudentCourseDll.StudentCallback callback = (studentPtr, userData) -> {
StudentCourseDll.Student s = new StudentCourseDll.Student();
s.setAutoRead(false);
s.getPointer().share(0).write(studentPtr.getByteArray(0, s.size()), 0, s.size());
s.read();
System.out.println("Student: " + new String(s.studentName).trim());
};
StudentCourseDll.INSTANCE.foreach_student(manager, callback, null);
StudentCourseDll.INSTANCE.destroy_manager(manager);
}
}
关键点说明
嵌套结构处理:
在 C++ 中,学生结构体通过指针动态管理课程数组。
C# 使用 Marshal.AllocHGlobal 分配非托管内存,Java 使用 JNA 的 Memory 类。
回调机制:
C# 通过 UnmanagedFunctionPointer 定义委托。
Java 通过 JNA 的 Callback 接口实现回调。
内存管理:
C++ 动态库负责内部对象的内存分配和释放。
C# 和 Java 需手动释放非托管内存(如 Marshal.FreeHGlobal)。
跨语言兼容性:
结构体的内存布局必须严格匹配(LayoutKind.Sequential)。
字符串使用 ByValTStr 或 byte 确保兼容性。
调试建议:
在 C++ 中添加日志输出,验证回调触发顺序。
使用工具(如 Dependency Walker)检查 DLL 导出函数。
领取专属 10元无门槛券
私享最新 技术干货