10

私のC宣言は次のとおりです。

int myData(uint myHandle, tchar *dataName, long *Time, uint *maxData, DATASTRUCT **data);

typedef struct {
  byte Rel;
  __int64 Time;
  char Validated;
  unsigned char Data[1];
} DATASTRUCT ;

私のC#宣言は次のとおりです。

[DllImport("myData.dll", EntryPoint = "myData")]
public static extern int myData(uint myHandle, [MarshalAs(UnmanagedType.LPTStr)] string dataName, out long Time, out uint maxData, ref DATASTRUCT[] data);

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DATASTRUCT
{
    public sbyte Rel;
    public long Time;
    public byte Validated;
    public double Data;
}

次に、マネージ関数を次のように呼び出します。

string dataToShow = "description";
long Time;
uint maxData; // How many structs will be returned, i.e. how much data is available
uint myHandle = 1;

DATASTRUCT[] dataInformation = new DATASTRUCT[3]; // doesn't matter what I specify as the array size?

myData(myHandle, dataToShow, out Time, out maxData, ref dataInformation);

上記の関数を実行すると、返される構造が 3 つあるにもかかわらず、構造が 1 つだけで正常に返されます。これはなぜですか?

追加情報; 次の方法で、ポインターを構造体の配列のポインターに渡そうとしました。

- ref DATASTRUCT[] data; // Works but only returns one struct
- [Out, MarshalAs(UnmanagedType.LPArray)] DATASTRUCT[] data; // returns the number of defined structs with garbage

を使用して手動でマーシャリングを行う必要があるかもしれないことは理解していますIntPtrが、これを実装する方法がわからないため、アドバイスをいただければ幸いです。

4

2 に答える 2

9

ネイティブ ライブラリが割り当てを行うように見えるので、実際に行う必要があるのは、割り当てられたデータにアクセスできるポインターを提供することだけです。

API 定義を次のように変更します (maxData パラメータを uint に変更したことに注意してください。long は .NET では 64 ビット、ネイティブでは 32 ビットです。

[DllImportAttribute("myData.dll", EntryPoint = "myData")]
public static extern int myData(uint myHandle, [MarshalAs(UnmanagedType.LPTStr)] string dataName, out uint Time, out uint maxData, out IntPtr pData);

最終パラメーターに out キーワードが必要かどうかは、はっきりとは覚えていませんが、必要だと思います。

次に、myData を呼び出します。

uint nAllocs = 0, time = 0;
IntPtr pAllocs = IntPtr.Zero;
myData(1, "description", out time, out nAllocs, out pAllocs);

ここで、pAllocs はアンマネージド メモリを指す必要があります。これらをマネージド メモリにマーシャリングすることはそれほど難しくありません。

[StructLayoutAttribute(LayoutKind.Sequential, Pack = 1)]
public struct DATASTRUCT
{
    public byte Rel;
    public long Time;
    public byte Validated;
    public IntPtr Data; //pointer to unmanaged string.
}


int szStruct = Marshal.SizeOf(typeof(DATASTRUCT));
DATASTRUCT[] localStructs = new DATASTRUCT[nAllocs];
for(uint i = 0; i < nallocs; i++)
    localStructs[i] = (DATASTRUCT)Marshal.PtrToStructure(new IntPtr(pAllocs.ToInt32() + (szStruct * i)), typeof(DATASTRUCT));

これで、ローカル構造体の配列ができたはずです。

注意点 プロジェクトを x86 としてコンパイルするように設定して、IntPtr のサイズを AnyCPU のデフォルトの 8 バイトではなく 4 バイト (DWORD) に標準化する必要がある場合があります。

于 2012-06-22T04:35:38.713 に答える
1

ポインターへのポインターは、dllimport 宣言で ref IntPtr データとして表すことができるため、宣言は次のようになります。

[DllImportAttribute("myData.dll", EntryPoint = "myData")]
public static extern int myData(uint myHandle, [MarshalAs(UnmanagedType.LPTStr)] string dataName, out long Time, out uint maxData, ref IntPtr data);

(余談ですが、C の long は C# の int と同等だと思います。C# の Long は Int64 であり、C では long long になります)

DATASTRUCT[] を IntPtr にマーシャリングするには、GCHandle クラスを使用します。

DATASTRUCT [] dataInformation = new DATASTRUCT[3];
GCHandle gch = GCHandle.Alloc(dataInformation , GCHandleType.Pinned);
IntPtr ptr = gch.AddrOfPinnedObject();
myData(myHandle, dataToShow, out Time, out maxData, ref ptr);
//It's absolutely essential you do this next bit so the object can be garbage collected again, 
//but it should only be done once the unmanaged code is definitely done with the reference.    
gch.Free(); 

Marshal クラスとその StructureToPtr または Copy メソッドを使用することもオプションですが、概念を証明する目的で、少なくとも GCHandle がトリックを行う必要があります。アンマネージ コードが長時間実行される操作を行うシナリオには理想的ではありません。このオブジェクトを所定の位置に固定したので、解放するまで GC はオブジェクトを移動できません。

于 2012-06-21T08:52:40.263 に答える