11

简而言之,问题是:如何在托管代码中释放从 Native DLL 作为 ItrPtr 返回的内存?

详细信息:假设我们有一个简单的函数,有两个参数作为 OUTPUT,第一个是指向字节数组的引用指针,第二个是引用 Int。该函数将根据一些规则分配字节数,并返回内存指针、字节大小和返回值(1 表示成功,0 表示失败)。

下面的代码工作正常,我可以正确获取字节数组以及字节数和返回值,但是当我尝试使用指针 (IntPtr) 释放内存时出现异常:

Windows 在 TestCppDllCall.exe 中触发了一个断点。

这可能是由于堆损坏,这表明 TestCppDllCall.exe 或其已加载的任何 DLL 中存在错误。

这也可能是由于用户在 TestCppDllCall.exe 具有焦点时按 F12。

输出窗口可能有更多诊断信息。

为了清楚起见:

  1. 下一个 C# 代码与具有相同签名的其他 DLL 函数一起正常工作,并且释放内存工作没有任何问题。

  2. 如果您需要更改分配内存方法或添加任何其他代码,则接受 (C) 代码中的任何修改。

  3. 我需要的所有功能是 Native DLL 函数通过引用接受两个参数(字节数组和 int,在 c# [IntPtr of byte array and int])根据一些规则用一些值填充它们并返回函数结果(成功或失败) .


CppDLL.h

#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif

extern "C" CPPDLL_API int writeToBuffer(unsigned char *&myBuffer, int& mySize);

CppDll.cpp

#include "stdafx.h"
#include "CppDll.h"

extern "C" CPPDLL_API int writeToBuffer(unsigned char*& myBuffer, int& mySize)
{
    mySize = 26;

    unsigned char* pTemp = new unsigned char[26];
    for(int i = 0; i < 26; i++)
    {
        pTemp[i] = 65 + i;
    }
    myBuffer = pTemp; 
    return 1;
}

C#代码:

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace TestCppDllCall
{
    class Program
    {
        const string KERNEL32 = @"kernel32.dll";
        const string _dllLocation = @"D:\CppDll\Bin\CppDll.dll";
        const string funEntryPoint = @"writeToBuffer";

        [DllImport(KERNEL32, SetLastError = true)]
        public static extern IntPtr GetProcessHeap();
        [DllImport(KERNEL32, SetLastError = true)]
        public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
        [DllImport(_dllLocation, EntryPoint = funEntryPoint, CallingConvention = CallingConvention.Cdecl)]
        public static extern int writeToBuffer(out IntPtr myBuffer, out int mySize);

        static void Main(string[] args)
        {
            IntPtr byteArrayPointer = IntPtr.Zero;
            int arraySize;
            try
            {
                int retValue = writeToBuffer(out byteArrayPointer, out arraySize);
                if (retValue == 1 && byteArrayPointer != IntPtr.Zero)
                {
                    byte[] byteArrayBuffer = new byte[arraySize];
                    Marshal.Copy(byteArrayPointer, byteArrayBuffer, 0, byteArrayBuffer.Length);
                    string strMyBuffer = Encoding.Default.GetString(byteArrayBuffer);
                    Console.WriteLine("Return Value : {0}\r\nArray Size : {1}\r\nReturn String : {2}",
                        retValue, arraySize, strMyBuffer);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error calling DLL \r\n {0}", ex.Message);
            }
            finally
            {
                if (byteArrayPointer != IntPtr.Zero)
                    HeapFree(GetProcessHeap(), 0, byteArrayPointer);
            }
            Console.ReadKey();
        }
    }
}

当我调试此代码时,我在行中设置断点(返回 1),缓冲区的值为:

myBuffer = 0x031b4fc0 "ABCDEFGHIJKLMNOPQRSTUVWXYZ‎‎‎‎««««««««î‏&quot;

当函数调用返回并且值为:

52121536

结果我得到了正确的内存指针,我能够得到字节数组值,如何在 C# 中用这个指针释放这些内存块?

如果有什么不清楚或有任何错别字,请告诉我,我不是以英语为母语的人。

4

3 回答 3

13

简短的回答:您应该在 DLL 中添加一个单独的方法来为您释放内存。

长答案:有不同的方式可以在 DLL 实现中分配内存。释放内存的方式必须与分配内存的方式相匹配。例如,用new[](带方括号)分配的内存需要用delete[](而不是deleteor free)释放。C# 没有为您提供执行此操作的机制;您需要将指针发送回 C++。

extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) {
    delete[] myBuffer;
}
于 2012-09-05T03:11:44.263 回答
7

如果您在本机代码中分配自己的内存,请使用CoTaskMemAlloc并且您可以使用Marshal.FreeCoTaskMem. CoTaskMemAlloc被描述为“在基于 COM 的应用程序中共享内存的唯一方法”(参见 http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx

如果您需要使用CoTaskMemAlloc与本机 C++ 对象一起分配的内存,您可以使用placement new来初始化内存,就像使用了new运算符一样。例如:

void * p = CoTaskMemAlloc(sizeof(MyType));
MyType * pMyType = new (p) MyType;

这不会分配内存,new只需调用预分配内存上的构造函数。

调用Marshal.FreeCoTaskMem不会调用该类型的析构函数(如果您只需要释放内存,则不需要);如果您需要通过调用析构函数来做更多的事情,那么您必须提供一个执行此操作的本机方法并 P/Invoke 它。无论如何都不支持将本机类实例传递给托管代码。

如果您需要使用其他 API 分配内存,则需要通过 P/Invoke 在托管代码中公开它,以便在托管代码中释放它。

于 2012-09-05T03:44:50.073 回答
3
  HeapFree(GetProcessHeap(), 0, byteArrayPointer);

不,那行不通。堆句柄错误,CRT 使用 HeapCreate() 创建自己的堆。它隐藏在 CRT 数据中,您无法访问它。从技术上讲,您可以从 GetProcessHeaps() 中找到句柄,但您不知道它是哪一个。

指针也可能是错误的,CRT 可能从 HeapAlloc() 返回的指针中添加了一些额外的信息来存储调试数据。

您需要导出一个调用 delete[] 来释放缓冲区的函数。或者编写一个 C++/CLI 包装器,以便您可以在包装器中使用 delete[]。额外要求 C++ 代码和包装器使用完全相同版本的 CRT DLL(需要 /MD)。这几乎总是要求您可以重新编译 C++ 代码。

于 2012-09-05T03:27:36.923 回答