8

cで記述されたdllから関数を呼び出すためにc#相互運用機能を使用したいと考えています。ヘッダーファイルがあります。これを見てください:

enum CTMBeginTransactionError {
    CTM_BEGIN_TRX_SUCCESS = 0,
    CTM_BEGIN_TRX_ERROR_ALREADY_IN_PROGRESS,
    CTM_BEGIN_TRX_ERROR_NOT_CONNECTED
};

#pragma pack(push)
#pragma pack(1)
struct CTMBeginTransactionResult {
    char *                        szTransactionID;
    enum CTMBeginTransactionError error;
};

struct CTMBeginTransactionResult ctm_begin_customer_transaction(const char * szTransactionID);

C# から ctm_begin_customer_transaction を呼び出す方法を教えてください。const char * は文字列に適切にマップされますが、さまざまな試み (stackoverflow や他のサイトを参照) にもかかわらず、戻り構造をマーシャリングできません。IntPtr を返す関数を定義すると、問題なく動作します...

編集 戻り値の型を IntPtr に変更して使用します。しかし、それは AccessViolationException をスローします

私も試しました:

IntPtr ptr = Transactions.ctm_begin_customer_transaction("");
int size = 50;
byte[] byteArray = new byte[size];
Marshal.Copy(ptr, byteArray, 0, size);
string stringData = Encoding.ASCII.GetString(byteArray);

stringData == "70e3589b-2de0-4d1e-978d-55e22225be95\0\"\0\0\a\0\0\b\b?" この時点で、"70e3589b-2de0-4d1e-978d-55e22225be95" は構造体からの szTransactionID Enum はどこにありますか?次のバイトですか?

4

2 に答える 2

5

この構造体にはメモリ管理の問題が隠されています。C 文字列ポインタの所有者は誰ですか? pinvoke マーシャラーは常に、呼び出し元がそれを所有していると想定するため、文字列を解放しようとします。そして、Marshal.FreeCoTaskMem() によって呼び出される関数と同じ関数である CoTaskMemFree() にポインターを渡します。これらの関数は、Windows のユニバーサル相互運用メモリ マネージャーである COM メモリ アロケーターを使用します。

これで良い結果が得られることはめったにありません。プログラマーが相互運用性を念頭に置いてコードを設計しない限り、C コードは通常、そのアロケーターを使用しません。その場合、戻り値として構造体を使用したことは一度もありませんでしたが、呼び出し元がバッファーを提供した場合、interop は常に問題なく動作します。

したがって、マーシャラーに通常の義務を負わせることはできません。文字列を解放しようとしないように、戻り値の型を IntPtr として宣言する必要があります。また、Marshal.PtrToStructure() を使用して自分でマーシャリングする必要があります。

しかし、それはまだ質問に答えていないままです。誰が弦を所有しているのでしょうか? 文字列バッファーを解放するためにできることは何もありません。C コードで使用されているアロケーターにアクセスすることはできません。唯一の希望は、文字列が実際にはヒープに割り当てられていないことです。C プログラムが文字列リテラルを使用している可能性があります。その推測を検証する必要があります。テスト プログラムで関数を 10 億回呼び出します。それがプログラムを爆発させないなら、あなたは大丈夫です。そうでない場合、問題を解決できるのは C++/CLI だけです。文字列の性質を考えると、「トランザクション ID」は大きく変わるはずですが、問題があると思います。

于 2013-03-08T13:18:46.223 に答える
1

私は自分の質問に答えるのは嫌いですが、結果の構造体をマーシャリングするための解決策を見つけました。構造体の長さは8バイトです(char *の場合は4バイト、enumの場合は4バイト)。文字列のマーシャリングは自動的には機能しませんが、以下は機能します。

// Native (unmanaged)
public enum CTMBeginTransactionError
{
    CTM_BEGIN_TRX_SUCCESS = 0,
    CTM_BEGIN_TRX_ERROR_ALREADY_IN_PROGRESS,
    CTM_BEGIN_TRX_ERROR_NOT_CONNECTED
};

// Native (unmanaged)
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
internal struct CTMBeginTransactionResult
{
    public IntPtr szTransactionID;
    public CTMBeginTransactionError error;
};

// Managed wrapper around native struct
public class BeginTransactionResult
{
    public string TransactionID;
    public CTMBeginTransactionError Error;

    internal BeginTransactionResult(CTMBeginTransactionResult nativeStruct)
    {
        // Manually marshal the string
        if (nativeStruct.szTransactionID == IntPtr.Zero) this.TransactionID = "";
        else this.TransactionID = Marshal.PtrToStringAnsi(nativeStruct.szTransactionID);

        this.Error = nativeStruct.error;
    }
}

[DllImport("libctmclient-0.dll")]
internal static extern CTMBeginTransactionResult ctm_begin_customer_transaction(string ptr);

public static BeginTransactionResult BeginCustomerTransaction(string transactionId)
{
    CTMBeginTransactionResult nativeResult = Transactions.ctm_begin_customer_transaction(transactionId);
    return new BeginTransactionResult(nativeResult);
}

コードは機能しますが、アンマネージコードを呼び出すとメモリリークが発生するかどうかを調査する必要があります。

于 2013-03-11T13:37:29.870 に答える