6

次のプロトタイプを持つC++関数があるとしましょう。

int myFunction(int someNumber, int &arraySize, signed char *&array)
// Extra function to free allocated memory:
int freePointer(void* myPointer)

この関数はいくつかの数値を取り、その数値に応じて配列を作成します。したがって、数値を渡して配列を取得します。C#でそれを呼び出すための最良の方法は何ですか?

私の最初のアプローチ:

[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] out byte[] array)

// ...

byte[] myArray;
int discard;
int myNumber = 100;

myFunction(myNumber, discard, myArray);

myArrayしかし、問題は、アンマネージライブラリによって割り当てられたメモリを解放する必要があり、のアドレスをに渡す方法がわからないことfreePointer()です。myArrayまた、のアドレスがアンマネージライブラリによって作成されたアレイのアドレスと等しいかどうかもわかりません。

そして、私の2番目のアプローチは次のとおりです。

[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, out IntPtr array)

// ...

byte[] myArray;
int myArraySize
IntPtr pointerToArray;
int myNumber = 100;

myFunction(myNumber, myArraySize, pointerToArray);

Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
FreePointerHandler(pointerToArray.ToPointer());

IntPtrしかし、繰り返しになりますが、メソッドを使用してのアドレスを送信することによって、それが正しく行われているかどうかはわかりません.ToPointer()

メモリリークを導入したくないのですが、どうすればよいですか?(安全でないコードを使用せずに。)

編集:パラメータはポインタへの参照であるため、refではなく使用する必要がありますか?out

4

1 に答える 1

5

あなたの2番目のアプローチは私には正しいように見えます。あなたが持っている場所:

FreePointerHandler(pointerToArray.ToPointer());

私はあなたがただできると思います:

FreePointerHandler(pointerToArray);

私はテストを書いていて、編集を投稿します。

ref対。out

refvs.に関してoutは、これはC++参照に正確にマップされていません。C#がC#を呼び出す場合out、呼び出された関数によってパラメーターを設定する必要があります。相互運用呼び出しでは、これを強制することはできません。ドキュメントの価値のためにout、この場合は私が好みます。

本当の問題

本当の問題は、.Net pinvokeの経験がない場合、すぐに目に見える副作用なしに関数を呼び出すときに自信を持てないことです。

@Hans Passantは、メモリリークを明らかにするために、「100万回呼び出すことでテストする」ことを推奨しました。この場合、このアプローチはおそらく最も単純ですが、関連付けられたメモリリソースがない場合には一般化されず、失敗しても追加情報は提供されません。.dllのソースコードがある場合はpinvoke呼び出しを介してデバッグするか、ない場合はモックAPI実装を作成することを好みます。そうすれば、新しいピンボーク機能を試しているときに、何が起こっているのかを正確に確認できます。また、問題がある場合は、デバッガーでトレースすると、どこが間違っているかを確認するのに役立ちます。

完全なピンボークの例

これは、元の質問に基づく完全な例です。戻り値も、​​myFunctionの詳細も文書化されていません。この例では、割り当てられた長さ3の配列を(0、1、2)で埋めるだけです。

DLLのヘッダーファイルは次のとおりです。

// testlib.h

#ifdef TESTLIB_EXPORTS
#define TESTLIB_API __declspec(dllexport)
#else
#define TESTLIB_API __declspec(dllimport)
#endif

extern "C" {
    TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array);
    TESTLIB_API int freePointer(void* myPointer);
}

.DLLの.cppソースファイル:

// testlib.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "testlib.h"
#include <iostream>

using namespace std;


TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array)
{
    #ifdef _DEBUG
        cout << "myFunction enter " << someNumber << ", " << arraySize << ", 0x"  << &array << endl;
    #endif
    arraySize = 3;

    array = (signed char*)malloc(arraySize);

    #ifdef _DEBUG
        cout << "myFunction array: 0x" << (void *)array << endl;
    #endif

    for (int i = 0; i < arraySize; ++i)
    {
        array[i] = i;
    }

    #ifdef _DEBUG
        cout << "myFunction exit " << someNumber << ", " << arraySize << ", 0x"  << &array << endl;
    #endif

    return 0;
}

TESTLIB_API int freePointer(void* myPointer)
{
    #ifdef _DEBUG
        cout << "freePointer: 0x" << myPointer << endl;
    #endif

    free(myPointer);
    return 0;
}

C#ソース:

using System;
using System.Runtime.InteropServices;

namespace InteropTest
{
    class InteropTest
    {
        [DllImport("testlib.dll")]
        private static extern int myFunction(int someNumber, out int arraySize, out IntPtr array);

        [DllImport("testlib.dll")]
        public static extern int freePointer(IntPtr pointer);

        public static byte[] myFunction(int myNumber)
        {
            int myArraySize;
            IntPtr pointerToArray;
            int iRet = myFunction(myNumber, out myArraySize, out pointerToArray);
            // should check iRet and if needed throw exception

            #if (DEBUG)
                Console.WriteLine();
                Console.WriteLine("InteropTest.myFunction myArraySize: {0}", myArraySize);
                Console.WriteLine();
            #endif

            byte[] myArray = new byte[myArraySize];
            Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
            freePointer(pointerToArray);

            #if (DEBUG)
               Console.WriteLine();
            #endif

            return myArray;
        }

        static void Main(string[] args)
        {
            #if (DEBUG)
                Console.WriteLine("Start InteropTest");
                Console.WriteLine();
            #endif
            int myNumber = 123;

            byte[] myArray = myFunction(myNumber);

            for (int i = 0; i < myArray.Length; ++i)
            {
                Console.WriteLine("InteropTest myArray[{0}] = {1}", i, myArray[i]);
            }
        }
    }
}

デバッグビルドはステータスメッセージを出力します。それらは、myFunctionarray値がに渡されるものであることを示していfreePointerます。

のC#インターフェイス関数も作成しましたmyFunction。pinvoke呼び出しが簡単ではない場合、通常は詳細を1回計算し、それを関数にカプセル化して再利用する必要があります。

于 2013-02-11T17:44:32.700 に答える