在使用Marshal.AllocHGlobal()分配的内存上迭代
本文关键字:分配 内存 迭代 Marshal AllocHGlobal | 更新日期: 2023-09-27 18:29:57
我有一个第三方C库,它的一个导出方法如下:
#define MAX_INDEX 8
int GetStuff(IN char* index[MAX_INDEX], OUT char* buf, IN size_t size);
第一个参数由指向存储特定字符串的buf参数的指针填充。大小指示每个字符串在buf缓冲区中的预期长度。我的C#P/Invoke方法目前看起来是这样的:
[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr indecies, IntPtr buf, Int32 size);
C#p/Invoke方法是私有的,因为我将它的功能封装在一个公共的"getter"方法中,该方法处理调用方的内存分配/释放。当我在C++中使用这个方法时,迭代实际上非常简单。我只是做了一些类似的事情:
char* pIndecies[MAX_INDEX];
char* pBuffer = new char[MAX_INDEX * (256 + 1)]; // +1 for terminating NULL
GetStuff(pIndecies, pBuffer, 256);
// iterate over the items
for(int i(0); i < MAX_INDEX; i++) {
if(pIndecies[i]) {
std::cout << "String for index: " << i << " " << pIndecies[i] << std::endl;
}
}
由于这些在C++中的使用方式,我决定可能应该使用IntPtr对象,只从堆中分配所需的内存,调用本机代码,并像在C++中那样对其进行迭代。然后我想起了C#中的字符是unicode字符,而不是ASCII字符。即使我把迭代放在一个不安全的代码块中,C#中的迭代也能正常工作吗?我的第一个想法是做以下事情:
IntPtr pIndecies = Marshal.AllocHGlobal(MAX_INDEX * 4); // the size of a 32-pointer
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * (256 + 1)); // should be the same
NativeMethods.GetStuff(pIndecies, pBuffer, 256);
unsafe {
char* pCStrings = (char*)pIndecies.ToPointer();
for(int i = 0; i < MAX_INDEX; i++) {
if(pCStrings[i])
string s = pCStrings[i];
}
}
我的问题是,"我应该如何迭代这个本机代码方法返回的内容?"这是封送到此函数的正确方式吗?第二个参数应该使用StringBuilder对象吗?一个约束问题是,第一个参数是GetStuff()方法后面的结构的1:1映射。每个索引的值对于理解您在第二个缓冲区参数中看到的内容至关重要。
我感谢你的建议。
谢谢,Andy
我认为你走在了正确的轨道上,但我会在不使用不安全代码的情况下做到这一点。像这样:
[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr[] index, IntPtr buf, Int32 size);
....
IntPtr[] index = new IntPtr[MAX_INDEX];
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * 256 + 1);
try
{
int res = NativeMethods.GetStuff(index, pBuffer, 256);
// check res for errors?
foreach (IntPtr item in index)
{
if (item != IntPtr.Zero)
string s = Marshal.PtrToStrAnsi(item);
}
}
finally
{
Marshal.FreeHGlobal(pBuffer);
}
这是您的C++版本的一个非常直接的翻译。