C#与C++交互开发系列(十四):C++中STL容器与C#集合传递的形式
前言
在跨语言开发中,C++ 的 STL 容器(如 std::vector
, std::map
)和 C# 的集合类(如 List<T>
, Dictionary<TKey, TValue>
)之间的数据传递是一个常见需求。由于两者的内存布局和实现机制不同,直接传递并不现实,需要对容器进行转换、序列化或采用适当的桥接方式。本文将详细介绍如何在 C++ 和 C# 之间进行集合数据的传递,涵盖几种常见容器的处理方法,并提供完整的代码示例。
1. 使用 std::vector
与 List<T>
传递数组数据
std::vector
和 List<T>
都是动态数组,在内存上通常是连续存储的,这让我们可以更方便地在 C++ 与 C# 之间传递这些数据。
C++ 代码示例
我们可以通过 std::vector
存储整数数组,然后通过指针传递给 C#:
// CppLibrary.cpp
#include <vector>
extern "C" __declspec(dllexport)
int* GetIntArray(int* size) {
static std::vector<int> data = {1, 2, 3, 4, 5}; // 静态变量保证数据不会在函数返回后释放
*size = data.size();
return data.data(); // 返回指向数据起始位置的指针
}
C# 代码示例
在 C# 中,可以通过 DllImport
来调用此 C++ 函数,并将返回的指针数据复制到 List<int>
中:
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetIntArray(out int size);
static void Main()
{
int size;
IntPtr ptr = GetIntArray(out size);
List<int> result = new List<int>();
for (int i = 0; i < size; i++)
{
int value = Marshal.ReadInt32(ptr, i * sizeof(int));
result.Add(value);
}
Console.WriteLine("Received data:");
result.ForEach(Console.WriteLine);
}
}
执行结果
Received data:
1
2
3
4
5
2. 使用 std::map
与 Dictionary<TKey, TValue>
传递键值对数据
std::map
是一种键值对容器,与 C# 的 Dictionary<TKey, TValue>
类似。在将数据传递给 C# 时,可以使用中间数组格式或序列化的字符串传递方式。
C++ 代码示例
假设我们有一个 C++ std::map
,需要将其内容传递给 C#,可以将键值对放入平铺的数组中(键和值依次排列):
// CppLibrary.cpp
#include <map>
#include <vector>
extern "C" __declspec(dllexport)
int* GetMapData(int* size) {
static std::map<int, int> data = {{1, 100}, {2, 200}, {3, 300}};
static std::vector<int> flatData;
flatData.clear();
for (const auto& kv : data) {
flatData.push_back(kv.first);
flatData.push_back(kv.second);
}
*size = flatData.size();
return flatData.data();
}
C# 代码示例
在 C# 中,可以使用数组接收数据,并将其组织为 Dictionary<int, int>
:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
class Program
{
[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetMapData(out int size);
static void Main()
{
int size;
IntPtr ptr = GetMapData(out size);
Dictionary<int, int> result = new Dictionary<int, int>();
for (int i = 0; i < size; i += 2)
{
int key = Marshal.ReadInt32(ptr, i * sizeof(int));
int value = Marshal.ReadInt32(ptr, (i + 1) * sizeof(int));
result[key] = value;
}
Console.WriteLine("Received dictionary:");
foreach (var kv in result)
{
Console.WriteLine($"Key: {kv.Key}, Value: {kv.Value}");
}
}
}
执行结果
Received dictionary:
Key: 1, Value: 100
Key: 2, Value: 200
Key: 3, Value: 300
3. 复杂容器与序列化传递
对于更复杂的 STL 容器(如嵌套的 std::vector<std::vector<int>>
)或大规模的 std::unordered_map
,可以考虑使用序列化来传递数据。常见方法包括 JSON 序列化或 Protobuf 序列化:
C++ 使用 JSON 序列化传递数据
通过vcpkg 安装 nlohmann/json 库,
执行指令
vcpkg install nlohmann-json
MyNativeLib项目–>C/C+±->常规–>附加包含目录,添加vcpkage的include目录。
// 使用 nlohmann/json 库进行序列化
#include <nlohmann/json.hpp>
#include <string>
#include <map>
extern "C" __declspec(dllexport)
const char* GetSerializedMap() {
static std::map<int, std::string> data = {{1, "one"}, {2, "two"}};
nlohmann::json j = data;
static std::string serialized = j.dump();
return serialized.c_str();
}
C# 使用 JSON 反序列化接收数据
项目引用Newtonsoft.Json组件
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
class Program
{
[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetSerializedMap();
static void Main()
{
IntPtr ptr = GetSerializedMap();
string jsonString = Marshal.PtrToStringAnsi(ptr);
var dictionary = JsonConvert.DeserializeObject<List<List<object>>>(jsonString).ToDictionary(pair => Convert.ToInt32(pair[0]), pair => pair[1].ToString());
Console.WriteLine("Deserialized dictionary:");
foreach (var kv in dictionary)
{
Console.WriteLine($"Key: {kv.Key}, Value: {kv.Value}");
}
}
}
执行结果
Deserialized dictionary:
Key: 1, Value: one
Key: 2, Value: two
结论
在 C++ 和 C# 之间传递 STL 容器和集合数据时,需要考虑到两者在内存管理和容器实现方面的差异。本文介绍了几种常用容器的传递方法:
- 使用指针传递简单数组,如
std::vector
到List<T>
。 - 将键值对平铺为数组形式传递,实现
std::map
到Dictionary<TKey, TValue>
的转换。 - 对于复杂数据,使用 JSON 等序列化方式,可以方便地在 C++ 和 C# 之间传递复杂的数据结构。
在实际应用中,可以根据数据的复杂程度和性能要求选择合适的方法,以确保跨语言的高效数据传递。