跨语言字符串数组处理

在进行托管代码与非托管代码之间的交互时,字符串数组的处理是一个常见且复杂的问题。本文将探讨如何在C#和C++之间传递和管理字符串数组。

在处理跨语言调用时,字符串数组的传递是一个挑战,尤其是当涉及到内存管理和数组大小变化时。虽然微软提供了一些方法和属性来辅助这一过程,但在复杂情况下,如通过引用传递数组时,这些工具可能不足以满足需求。

示例代码

以下是一个基于微软示例的C#代码,用于调用非托管DLL中的函数,该函数接受一个字符串数组:

[DllImport("NativeCDll.dll", CharSet = CharSet.Unicode)] extern static int TakesArrayOfStrings([In][Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] str, ref int size);

如果不需要从DLL调用中返回或更改文本,上述方法是一个简单而优雅的解决方案。然而,如果需要在非托管代码中构建数组,就需要使用IntPtr数据类型,并进行更复杂的内存管理。

调用数组引用

为了在非托管代码中构建字符串数组,需要通过引用调用,这要求为数组和每个字符串分别构建内存块。这些内存块将在托管代码和非托管代码之间来回传递,以创建返回的字符串数组。

以下是C++代码示例,展示了如何在非托管代码中处理传入的数组,并构建一个新的数组:

int TakesRefArrayOfStrings(wchar_t**& ppArray, int* pSize) { // 检查传入的数组 wprintf(L"\nstrings received in native call:\n"); for (int i = 0; i < *pSize; i++) { wprintf(L"%s", ppArray[i]); CoTaskMemFree((ppArray)[i]); } CoTaskMemFree(ppArray); ppArray = NULL; *pSize = 0; // 使用CoTaskMemAlloc而不是new操作符 const int newsize = 5, newwidth = 20; wchar_t** newArray = (wchar_t**)CoTaskMemAlloc(sizeof(wchar_t*) * newsize); // 填充输出数组 wprintf(L"\nstrings created in native call:\n"); for (int j = 0; j < newsize; j++) { newArray[j] = (wchar_t*)CoTaskMemAlloc(sizeof(wchar_t) * newwidth); ::ZeroMemory(newArray[j], sizeof(wchar_t) * newwidth); swprintf(newArray[j], newwidth, L"unmanagstr %d", j); wprintf(L"%s", newArray[j]); } ppArray = newArray; *pSize = newsize; return 1; }

在托管代码中,需要构建一个指向内存区域的指针,该内存区域将被TakesRefArrayOfStrings函数使用。为了避免为宽字符和多字节字符串编写两种方法,使方法通用化。

以下是C#代码示例,用于将字符串数组转换为IntPtr,并从结果中构建新的字符串数组:

public static IntPtr StringArrayToIntPtr<GenChar>(string[] InputStrArray) where GenChar : struct { int size = InputStrArray.Length; IntPtr[] InPointers = new IntPtr[size]; int dim = IntPtr.Size * size; IntPtr rRoot = Marshal.AllocCoTaskMem(dim); Console.WriteLine("input strings in managed code:"); for (int i = 0; i < size; i++) { Console.Write("{0}", InputStrArray[i]); if (typeof(GenChar) == typeof(char)) { InPointers[i] = Marshal.StringToCoTaskMemUni(InputStrArray[i]); } else if (typeof(GenChar) == typeof(byte)) { InPointers[i] = Marshal.StringToCoTaskMemAnsi(InputStrArray[i]); } } Marshal.Copy(InPointers, 0, rRoot, size); return rRoot; }

调用完成后,需要做相反的操作,从内存块中创建字符串数组:

public static string[] IntPtrToStringArray<GenChar>(int size, IntPtr rRoot) where GenChar : struct { IntPtr[] OutPointers = new IntPtr[size]; Marshal.Copy(rRoot, OutPointers, 0, size); string[] OutputStrArray = new string[size]; for (int i = 0; i < size; i++) { if (typeof(GenChar) == typeof(char)) OutputStrArray[i] = Marshal.PtrToStringUni(OutPointers[i]); else OutputStrArray[i] = Marshal.PtrToStringAnsi(OutPointers[i]); Marshal.FreeCoTaskMem(OutPointers[i]); } Marshal.FreeCoTaskMem(rRoot); return OutputStrArray; }

在使用这些代码时,除了输入/输出字符串数组外,还需要指定如何使用泛型参数byte或char来处理字符串。

处理结构体数组

处理结构体数组与字符串数组类似,但它更适合使用泛型。设置Marshalling参数时仍然需要一些技巧:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class MyStruct { public String buffer; public int size; public MyStruct(String b, int s) { buffer = b; size = s; } public MyStruct() { buffer = ""; size = 0; } }

以下是创建IntPtr的代码示例:

public static IntPtr IntPtrFromStuctArray<T>(T[] InputArray) where T : new() { int size = InputArray.Length; T[] resArray = new T[size]; IntPtr[] InPointers = new IntPtr[size]; int dim = IntPtr.Size * size; IntPtr rRoot = Marshal.AllocCoTaskMem(Marshal.SizeOf(InputArray[0]) * size); for (int i = 0; i < size; i++) { Marshal.StructureToPtr(InputArray[i], (IntPtr)(rRoot.ToInt32() + i * Marshal.SizeOf(InputArray[i])), false); } return rRoot; }

使用这些方法应该很容易:

int size = 3; MyStruct[] inArray = { new MyStruct("struct 1", 1), new MyStruct("struct 2", 2), new MyStruct("struct 3", 3) }; IntPtr outArray = GenericMarshaller.IntPtrFromStuctArray<MyStruct>(inArray); TakesArrayOfStructsByRef(ref size, ref outArray); MyStruct[] manArray = GenericMarshaller.StuctArrayFromIntPtr<MyStruct>(outArray, size); Console.WriteLine(); for (int i = 0; i < size; i++) { Console.WriteLine("Element {0}: {1} {2}", i, manArray[i].buffer, manArray[i].size); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485