2013-11-03 109 views
1

對於我的問題,我非常接近解決方案,但我需要一些關於最終結果的指導以使所有工作都成功。在過去的一週中,我學到了很多關於數組結構和IntPtr數組元素

作爲一個參考,上週我提了一個類似的問題關於同一個話題,但由於我身邊的巨大監督,我提出了錯誤的問題。

我想使用一個非託管的C++ dll,這是與連接的設備通信的API。我已經成功創建了包裝器,以及其他大部分函數調用,但是最後一個讓我瘋狂。

對於一些背景信息(可能不需要回答這個問題 - 並牢記我的基本思維過程當時有缺陷)是在這裏:Calling un-managed code with pointer (Updated)

在我原來的問題,我是問有關創建IntPtr到包含struct(2)的數組的結構(1)....實際上,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++的聲明是無符號長,那麼UInt32的是正確的類型在C#中?

回答

4

你的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; 
} 
+0

非常感謝,這非常有幫助,我可以在你的幫助下完成並運行。由於輸入和輸出指針與正在傳遞的實際Ioctl值的關係不同,我還沒有實現您的想法來爲C函數提供更好的聲明。在某些情況下,它們都是無效的(例如只發送一個明確的緩衝區命令,因此兩個指針都是空的。在另一個Ioctl場景中,一個指針指向一個Bytes數組。因此,我仍然在Set_Config方法中使用Marshaling - 但它工作100%。 – mleega