18

したがって、この質問には多くのバリエーションがあり、いくつかを見た後でもまだ理解できません。

これはCコードです:

typedef struct
{
unsigned long Identifier;
char Name[128];
} Frame;

Frame GetFrame(int index);

これは C# コードです。

struct Frame
{
    public ulong Identifier;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 128)]
    public char[] Name;
}

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern Frame GetFrame(int index);

これは私が C# で試した最後の試みであり、かなり論理的に思えますが、「メソッドの署名は PInvoke と互換性がありません」というエラーが表示されます。だから、私は次に何をしようか迷っています。どんな助けでも大歓迎です。

ありがとう、ケビン

更新され たケビンは、これを編集として私の回答に追加しました

代わりに C コードを変更する必要があります。

void GetFrame(int index, Frame * f);

C# の代わりに使用します。

struct Frame
{
    public uint Identifier;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string Name;
}

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern void GetFrame(int index, ref Frame f);
4

3 に答える 3

23

選択したPInvoke署名には2つの問題があります。

1つ目は簡単に修正できます。の誤訳がありunsigned longます。Cでは、unsigned long通常、anはわずか4バイトです。long8バイトのC#タイプを選択しました。使用するC#コードを変更すると、uintこれが修正されます。

2番目は少し難しいです。Tergiverが指摘したように、CLRマーシャラーは、ブリット可能である場合にのみ、リターン位置の構造体をサポートします。Blittableは、ネイティブコードとマネージコードでまったく同じメモリ表現を持っていることを示すための素晴らしい方法です。選択した構造体定義は、ネストされた配列を持っているため、ブリット可能ではありません。

ただし、PInvokeが非常に単純なプロセスであることを覚えていれば、これは回避できます。CLRマーシャラーでは、実際には、タイプの署名とピンボークメソッドを使用して2つの質問に答える必要があります。

  • 何バイトコピーしていますか?
  • 彼らはどちらの方向に進む必要がありますか?

この場合、バイト数はですsizeof(unsigned long) + 128 == 132。したがって、必要なのは、blittableでサイズが132バイトのマネージドタイプを構築することだけです。これを行う最も簡単な方法は、配列部分を処理するBLOBを定義することです。

[StructLayout(LayoutKind.Sequential, Size = 128)]
struct Blob
{
   // Intentionally left empty. It's just a blob
}

これは、メンバーがない構造体であり、マーシャラーには128バイトのサイズであるように見えます(ボーナスとして、それはブリット可能です!)。これで、構造をとこのタイプFrameの組み合わせとして簡単に定義できます。uint

struct Frame
{
    public int Identifier;
    public Blob NameBlob;
    ...
}

これで、マーシャラーが132バイトと見なすサイズのblittableタイプができました。これは、GetFrame定義した署名で問題なく機能することを意味します

char[]残っているのは、名前の実際にアクセスできるようにすることだけです。これは少しトリッキーですが、マーシャルマジックで解決できます。

public string GetName()
{
    IntPtr ptr = IntPtr.Zero;
    try
    {
        ptr = Marshal.AllocHGlobal(128);
        Marshal.StructureToPtr(NameBlob, ptr, false);
        return Marshal.PtrToStringAnsi(ptr, 128);
    }
    finally
    {
        if (ptr != IntPtr.Zero) 
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

注:APIに慣れていないため、呼び出し規約の部分についてコメントすることはできませんGetFrameが、それは間違いなく確認したいことです。

于 2012-04-25T17:25:18.633 に答える
11

問題は、ネイティブ関数が blittable でない型を戻り値として返すことです。

http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx

P/Invoke は、戻り値として blittable でない型を持つことはできません。

そのメソッドを p/Invoke することはできません。[編集 実際には可能です。JaredParの回答を参照してください]

値で 132 バイトを返すのは悪い考えです。このネイティブ コードがあなたのものである場合は、修正します。132 バイトを割り当ててポインターを返すことで修正できます。次に、そのメモリを解放する FreeFrame メソッドを追加します。p/Invoked できるようになりました。

または、それが埋めるフレーム メモリへのポインタを受け入れるように変更することもできます。

于 2012-04-25T18:21:45.543 に答える
4

JaredPar のもう 1 つのオプションは、C# の固定サイズ バッファー機能を利用することです。ただし、これには安全でないコードを許可する設定をオンにする必要がありますが、2 つの構造体を持つことは回避されます。

class Program
{
    private const int SIZE = 128;

    unsafe public struct Frame
    {
        public uint Identifier;
        public fixed byte Name[SIZE];
    }

    [DllImport("PinvokeTest2.DLL", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private static extern Frame GetFrame(int index);

    static unsafe string GetNameFromFrame(Frame frame)
    {
        //Option 1: Use if the string in the buffer is always null terminated
        //return Marshal.PtrToStringAnsi(new IntPtr(frame.Name));

        //Option 2: Use if the string might not be null terminated for any reason,
        //like if were 128 non-null characters, or the buffer has corrupt data.

        return Marshal.PtrToStringAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0];
    }

    static void Main()
    {
        Frame a = GetFrame(0);
        Console.WriteLine(GetNameFromFrame(a));
    }
}
于 2012-04-25T22:22:00.637 に答える