3

問題の解決に非常に近づいていますが、すべてを機能させるための最後の仕上げについて少しガイダンスが必要です。先週、私は多くのことを学びました

参考までに、先週同じトピックについて同様の質問をしましたが、私の側の大きな見落としにより、間違った質問をしていました.

接続されたデバイスとの通信用 API であるアンマネージ C++ dll を使用しようとしています。ラッパーと他のほとんどの関数呼び出しを正常に作成しましたが、この最後の関数呼び出しは私を夢中にさせています。

背景情報については(おそらくこの質問に答える必要はありません-当時の私の基本的な思考プロセスに欠陥があったことを覚えておいてください)はここにあります:Calling un-managed code with pointer (Updated)

私の最初の質問では、struct(2) の配列を含む struct(1) への IntPtr の作成について尋ねていました。実際、struct(1) には配列がまったく含まれておらず、へのポインターが含まれています。配列。

参照として実装しようとしている API のドキュメントを次に示します。

extern “C” long WINAPI PassThruIoctl
(
    unsigned long ChannelID,
    unsigned long IoctlID,
    void *pInput,
    void *pOutput
)


// *pInput Points to the structure SCONFIG_LIST, which is defined as follows:
// *pOutput is not used in this function and is a null pointer

typedef struct
{
    unsigned long NumOfParams; /* number of SCONFIG elements */
    SCONFIG *ConfigPtr; /* array of SCONFIG */
} SCONFIG_LIST

// Where:
// NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array pointed to by ConfigPtr.
// ConfigPtr is a pointer to an array of SCONFIG structures.

// The structure SCONFIG is defined as follows:
typedef struct
{
    unsigned long Parameter; /* name of parameter */
    unsigned long Value; /* value of the parameter */
} SCONFIG

現在定義されている構造体定義は次のとおりです

[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig 
{
   public UInt32 Parameter;
   public UInt32 Value;
}



[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig_List
{
    public UInt32 NumOfParams;
    public IntPtr configPtr;

    public SConfig_List(UInt32 nParams, SConfig[] config)
    {
        this.NumOfParams = nParams;

        //  I have tried these 2 lines together
        IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);
        this.configPtr = new IntPtr(temp.ToInt32());

        // I have tried this by itself
        // this.configPtr  = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);


        // and this line
        // this.configPtr = Marshal.AllocHGlobal(sizeof(SConfig)*(int)nParams);  // this only complies with unsafe struct
    }
}

これらを変数に設定し、API とやり取りする関数を呼び出すコードのスニペットを次に示します。

SConfig[] arr_sconfig;
arr_sconfig = new SConfig[1];

arr_sconfig[0].Parameter = 0x04;
arr_sconfig[0].Value = 0xF1;
SConfig_List myConfig = new SConfig_List(1, arr_sconfig);

m_status = m_APIBox.SetConfig(m_channelId, ref myConfig);

最後に、この情報を dll に渡す関数を次に示します。

public APIErr SetConfig(int channelId, ref SConfig_List config)
{
    unsafe
    {
        IntPtr output = IntPtr.Zero; // Output not used, just a null pointer for this function

        //   These 2 lines of code cause API dll to yell about invalid pointer (C# is happy but it doesnt work with dll)
        //   IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(J_2534_API.SConfig_List)));
        //   IntPtr input = new IntPtr(temp.ToInt32());

        //  The following 2 lines only compile with unsafe - but API dll "likes" the pointer - but I am not getting desired results
        //  The dll is properly getting the Number of Parameters (NumOfParams), but the data within the array is not being
        //  referenced correctly
        IntPtr input = Marshal.AllocHGlobal(sizeof(SConfig_List)); // Only works with unsafe
        Marshal.StructureToPtr(config, input, true);

        APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);

        return returnVal;
    }
}

基本的な考え方の大きな見落としに気付く前に、構文が間違っていてコードがコンパイルされないか、コンパイルしても実行時エラーが発生したかのいずれかで、C# を満足させることさえできませんでした (外部 dll を呼び出しても)

これらの問題は私の背後にあります。コードは正常にコンパイルされ、実行時エラーなしで実行されます。また、使用しているdllにはロギング機能があるので、実際に正しい関数を呼び出していることがわかります。一部のデータを正しく渡しています。NumOfParams 変数は関数によって適切に読み取られていますが、構造体の配列はガベージ データのようです。

ここで非常に役立つ記事を読んでいます:http://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/

そして、私は MSDN を読んでいますが、これまでのところ、このことを機能させるコードの魔法の組み合わせに出くわすことができなかったので、もう一度助けを求めています.

私の問題は、IntPtr 変数を正しく設定しておらず、メモリ内の正しい領域を指していないことだと確信しています。

安全でないコードと安全なコードのさまざまな組み合わせを試しました。また、この時点でメモリを明示的に解放していないこともわかっているので、そのポインタも役立ちます。私の研究では、ここにうまくいくかもしれないいくつかのアイデアがあります。

[MarshalAs(UnmanagedType.LPWStr)]

[MarshalAs(UnmanagedType.ByValArray, SizeConst=...)]

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst=100)]

最後の質問: C++ の宣言は unsigned long であるため、C# では UInt32 が適切な型であると思いますか?

4

1 に答える 1

6

スニペットの SConfig_List コンストラクターには多くの問題があります。最大の問題は、配列にメモリを割り当てますが、構造体もコピーするのを完全に忘れていることです。したがって、ネイティブ コードはポインタを正常に取得しますが、初期化されていないメモリを調べます。次のように修正できます。

    public SConfig_List(SConfig[] config) {
        this.NumOfParams = config.Length;
        int size = Marshal.SizeOf(config[0]);
        IntPtr mem = this.configPtr = Marshal.AllocHGlobal(size * config.Length);
        for (int ix = 0; ix < config.Length; ++ix) {
            Marshal.StructureToPtr(config[ix], mem, false);
            mem = new IntPtr((long)mem + size);
        }
    }

呼び出しが完了した後に Marshal.FreeHGlobal() を再度呼び出すことを忘れないでください。そうしないと、メモリ リークが発生します。

SConfig_List のマーシャリングを回避する最も簡単な方法は、C 関数のより適切な宣言を提供することです。

[DllImport(...)]
private static extern ApiErr PassThruIoctl(
    int channelID, 
    uint ioctlID,
    ref SConfig_List input,
    IntPtr output);

これにより、まともなラッパーメソッドは次のようになります。

public APIErr SetConfig(int channelId, SConfig[] config) {
    var list = new SConfig_List(config);
    var retval = PassThruIoctl(channelId, Ioctl.SET_CONFIG, ref list, IntPtr.Zero);
    Marshal.FreeHGlobal(list.configPtr);
    return retval;
}
于 2013-11-03T15:39:27.307 に答える