【问题标题】:iterating over memory allocated with Marshal.AllocHGlobal()迭代使用 Marshal.AllocHGlobal() 分配的内存
【发布时间】:2012-04-04 00:29:23
【问题描述】:

我有一个第 3 方 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 映射。每个索引的值对于理解您在第二个缓冲区参数中查看的内容至关重要。

感谢任何建议。

谢谢, 安迪

【问题讨论】:

    标签: c# pinvoke


    【解决方案1】:

    我认为您在正确的轨道上,但我会在不使用不安全代码的情况下这样做。像这样:

    [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++ 版本的相当直接的翻译。

    【讨论】:

    • 我喜欢这个提议,并会在一些硬件释放时试一试(这个第 3 方库适用于我和其他两个共享的硬件)。如果符合要求,我一定会标记为答案。我认为会的。你知道,我没想过要制作一个 IntPtr 数组。这很漂亮。
    • 非常感谢您的解决方案。在 foreach 循环中,我无法检查 item.Zero,因此条件必须是 if(item != IntPtr.Zero)。我已经编辑了代码,但需要同行评审(明智的预防措施)。再次感谢。
    • @AndrewFalanga 这个解决方案很好。这意味着同样的事情。我认为 .net 4 对 IntPtr 进行了一些更改。您使用的是较低版本吗?
    • 非常有趣,这个项目是一个 .NET 4 项目。我想知道为什么我会遇到问题?
    • 天哪。我只是愚蠢。 Zero 是一个包含零表示的字段。我对自己的一些代码感到困惑。对不起。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 2020-01-28
    • 2015-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多