11

簡単に言えば、問題は次のとおりです。マネージ コードで ItrPtr としてネイティブ DLL から返されたメモリを解放する方法は?

詳細 : 単純な関数が OUTPUT として 2 つのパラメーターを受け取るとします。最初のパラメーターはバイト配列への参照ポインターであり、2 番目のパラメーターは Reference Int です。この関数は、いくつかの規則に基づいてバイト数を割り当て、メモリのポインターとバイトのサイズ、および戻り値 (成功の場合は 1、失敗の場合は 0) を返します。

以下のコードは正常に動作し、バイト配列とバイト数と戻り値を正しく取得できますが、ポインター (IntPtr) を使用してメモリを解放しようとすると、例外が発生します。

Windows は、TestCppDllCall.exe でブレークポイントをトリガーしました。

これは、ヒープの破損が原因である可能性があります。これは、TestCppDllCall.exe または読み込まれた DLL のバグを示しています。

これは、TestCppDllCall.exe にフォーカスがあるときにユーザーが F12 キーを押したことが原因である可能性もあります。

出力ウィンドウには、より多くの診断情報が表示される場合があります。

物事を明確にするために:

  1. 次の C# コードは、同じ署名を持つ他の DLL 関数で正しく動作し、メモリの解放は問題なく動作します。

  2. (C) コードの変更は、メモリの割り当て方法を変更したり、他のコードを追加したりする必要がある場合に受け入れられます。

  3. 私が必要とするすべての機能は、参照によって2つのパラメータを受け入れるネイティブDLL関数です(バイト配列とint、c#では[バイト配列とintのIntPtr])いくつかのルールに基づいていくつかの値を入力し、関数の結果を返します(成功または失敗) .


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;

そして、関数呼び出しが返され、値が次の場合、C# コードで同じ値を取得しました。

52121536

その結果、正しいメモリ ポインタを取得し、バイト配列値を取得できました。C# でこのポインタを使用してこれらのメモリ ブロックを解放する方法を教えてください。

不明な点やタイプミスがある場合はお知らせください。私は英語のネイティブ スピーカーではありません。

4

3 に答える 3

13

簡単な答え: メモリを解放する別のメソッドを DLL に追加する必要があります。

長い答え: DLL 実装内でメモリを割り当てる方法はいくつかあります。メモリを解放する方法は、メモリを割り当てた方法と一致する必要があります。たとえば、(角括弧で) で割り当てられたメモリは、 (またはではなく)new[]で解放する必要があります。C# には、それを行うためのメカニズムが用意されていません。ポインターを C++ に送り返す必要があります。delete[]deletefree

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

ネイティブ コードで独自のメモリを割り当てている場合は、 を使用し、 を使用CoTaskMemAllocしてマネージ コードでポインターを解放できますMarshal.FreeCoTaskMemCoTaskMemAlloc「COM ベースのアプリケーションでメモリを共有する唯一の方法」と説明されています ( http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspxを参照) 。

CoTaskMemAllocネイティブ C++ オブジェクトで割り当てられたメモリを使用する必要がある場合は、placement newnewを使用して、演算子が使用されたかのようにメモリを初期化できます。例えば:

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 に答える