2011-07-29 23 views
2

我正在爲C++ API編寫一個C#封裝器,但依賴於這個結構的特定結構和函數在調試時給出了非常奇怪的錯誤。來自PInvoke結構/函數的奇數錯誤

C++結構:

typedef struct 
{ 
    unsigned __int handle; 
    char name[80]; 
    unsigned int unique_ID; 
} DeviceInfo; 

跟此功能:

int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices); 

的結構和功能被導入爲,例如:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
public struct DeviceInfo 
{ 
    public UInt32 handle; 
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)] 
    public String name; 
    public UInt32 unique_ID; 
} 

[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)] 
public static extern int get_device_info(ref DeviceInfo di, int length_of_di_array, ref numValidDevices); 

預期用途這個結構和功能的只是爲了從電路板Im訪問獲取一些設備信息。目前我無法訪問C++中的函數體,所以我只能假定它工作在100%(在C++中工作正常)。

問題是,當我使用該函數來運行一個結構數組時,它輸出我正在查找的數據,但也會在運行時開始失敗,並給我提供各種錯誤窗口。

C#代碼:

static void Main() 
{  
    int numValidDevices = 0; //initialize variable 
    DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices 

    for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices 
    { 
     rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data 
     Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID); 
    } 
    Console.ReadLine(); //stops console from closing prematurely 
    API_close(); //custom close function from the C++ API 
} 

錯誤在調試(信息仍示出): 「類型 'System.Threading.ThreadStateException' 的未處理的異常發生在System.dll中

其他信息:線程尚未啓動。「

「類型‘System.ExecutionEngineException’的未處理的異常出現在mscorlib.dll」

錯誤在調試(信息不被示出,程序執行失敗): 「未處理的異常類型的「系統。 AccessViolationException'發生在mscorlib.dll中

附加信息:嘗試讀取或寫入受保護的內存,這通常表示其他內存已損壞。

關閉控制檯窗口時: 「'0x7c9113c0'處的指令引用內存'0x00000000',內存不能被'寫入'。 (有時會說'讀'而不是'寫')。

我已經做了很多研究的PInvoke和整個Microsoft InteropAssistant application來了,因爲這樣this one各種堆棧溢出文章,this post似乎更接近到什麼Im做,但我還是挖掘到了如何使用元帥.CoTaskMemAlloc/Free,看看它是否會做任何...

到目前爲止,我對我的結構和函數是正確的,我試着改變結構使用IntPtr,但不返回di.name值和di.unique_ID變成亂碼(奇怪的是,di。手柄保持有效)

C#代碼:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
public struct DeviceInfo 
{ 
    public UInt32 handle; 
    IntPtr p_name; 
    public String name { get { return Marshal.PtrToStringAnsi(p_name); } } 
    public UInt32 unique_ID; 
} 

預期輸出:

Handle: 3126770193 
Name: DEVICE_A 
Unique ID: 12345678 

IntPtr的輸出:

Handle: 3126770193 
Name: 
Unique ID: 1145128264 

奇怪的是,使用沒有錯誤的一個IntPtr結果以上,運行良好。 這使我相信問題在於將C++字符串編組爲字符串,但我不確定問題出在編組,內存管理(沒有?),還是我沒有完全捕獲。

任何和所有的反饋意見將非常感激,我已經難倒這個數週的現在......

+0

您正在傳遞一個引用,但它期望一個數組。 – leppie

+0

還要確保C中的'sizeof'大於C中的C#=='sizeof'我懷疑名稱是錯誤的。請嘗試使用byte []並應用值數組大小常量。 – leppie

+0

而不是編輯問題,你應該發佈一個答案。你願意這樣做嗎? –

回答

2

有什麼地方不加了,在這裏。我不清楚該功能是如何被調用的。

特別是,這種聲明:

int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices); 

不匹配,你如何使用它:

[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)] 
public static extern int get_device_info(ref DeviceInfo di, const int length_of_di_array, int* p_numValidDevices); 

... 

DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices 
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices 
{ 
    rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data 
} 

你告訴它數組的長度16,從索引i,這是錯誤的。你是否一次只傳遞一個數組元素?

DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices 
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices 
{ 
    rc = get_device_info(ref di[i], 1, ref numValidDevices); //accesses each device element and returns the data 
} 

還是你的意思是傳遞整個數組一次?

DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices 
rc = get_device_info(ref di[0], 16, ref numValidDevices); //accesses each device element and returns the data 
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices 
{ 
    Console.WriteLine(...); 
} 

P.S.我會考慮改變你的P/Invoke的聲明是:

[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)] 
public static extern int get_device_info(
    [In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di, 
    int length_of_di_array, 
    ref int p_numValidDevices); 
+0

你是完全正確的,我重新評估了我對函數的使用,並將我的DllImport改爲了你在那裏的東西,並且它實現了訣竅!謝謝! (澄清是作爲編輯添加到原來的帖子) – Calvin

2

你得到的例外表明您pinvoking非託管代碼,是消滅垃圾回收堆。這不是水晶的原因,但是你並沒有給編組人員很多機會去做正確的事情。它無法正確固定陣列。通過適當聲明函數開始,它需要一個數組,以便聲明一個:

[DllImportAttribute("MyC++API.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern int get_device_info(
    DeviceInfo[] di, 
    int length_of_di_array, 
    out int p_numValidDevices 
); 

你DeviceInfo的第一個聲明是正確的,第二個是不是因爲這個字符串不是一個指針。

+0

是的,我正在做一些黑客入侵的代碼,試圖強制某種輸出。 – Calvin

0

所以,正如在下面的回覆中指出的那樣,我有兩個問題:
1.我沒有正確調用我的DllImport,我的方式是試圖將輸出拼湊在一起,這樣做我搞砸了內存分配給結構數組。
2.我試圖一起破解一個輸出,並搞亂了代碼(試圖將DeviceInfo數組di作爲一個單獨的元素di [number]傳遞,而不是作爲一個整體)。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
public struct DeviceInfo 
{ 
    public UInt32 handle; 
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)] 
    public String name; 
    public UInt32 unique_ID; 
} 

[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)] 
public static extern int get_device_info(
    [In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di, 
    int length_of_di_array, 
    ref int p_numValidDevices); 

static void Main() 
{ 

    int numValidDevices = 0; 

    DeviceInfo[] di = new DeviceInfo[16]; 

    get_device_info(di, 16, ref numValidDevices); 

    for (int i = 0; i < numValidDevices; ++i) 
    { 
     Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID); 
    } 

    Console.ReadLine(); 
    API_close(); 
}