簡體   English   中英

通過C#調用Win32 api失敗!

[英]Win32 api call via C# fails!

我有一個C ++函數導出為api像這樣:

#define WIN322_API __declspec(dllexport)
WIN322_API char* Test(LPSTR str);
WIN322_API char* Test(LPSTR str)
{
 return "hello";
}

該函數由.DEF文件正確導出為API,因為我可以在Dependency Walker工具中看到它。 現在我有一個C#測試程序:

[DllImport("c:\\win322.dll")]

public static extern string Test([MarshalAs(UnmanagedType.LPStr)] String str);

private void Form1_Load(object sender, EventArgs e)
        {
string _str = "0221";
Test(_str); // runtime error here!

}

在調用Test()方法時我得到錯誤:

“調用PInvoke函數'MyClient!MyClient.Form1 :: Test'使堆棧失衡。這很可能是因為托管PInvoke簽名與非托管目標簽名不匹配。請檢查PInvoke簽名的調用約定和參數是否與目標非托管簽名。“

我嘗試了很多其他數據類型和編組,但什么也沒得到! 請幫助我!

它是由調用約定不匹配引起的,[DllImport]的默認值是Stdcall,但C編譯器的默認值是Cdecl。 在聲明中使用CallingConvention屬性。

這不是唯一的問題,這個代碼會在Vista和Win7上崩潰。 從C函數返回一個字符串非常麻煩,存在內存管理問題。 目前尚不清楚誰負責釋放字符串緩沖區。 你現在正在返回一個文字,但這很快就會停止有用。 下一站是使用malloc()作為返回字符串,其意圖是調用者調用free()。 這不起作用,pinvoke marshaller無法調用它,因為它不知道C代碼使用的是什么堆。

它將調用Marshal.FreeCoTaskMem()。 這是錯的,字符串不是由CoTaskMemAlloc()分配的。 這在XP及更早版本中沒有被注意到,除了這很難診斷內存泄漏。 在Vista和Win7上使用kaboom,他們有更嚴格的內存管理器。

你需要像這樣重寫C函數:

extern "C" __declspec(dllexport) 
void __stdcall Test(const char* input, char* output, int outLen);

現在調用者通過輸出參數提供緩沖區,不再猜測誰擁有內存。 您在C#聲明中使用StringBuilder。

[DllImport("foo.dll")]
private static extern void Test(string input, StringBuilder output, int outLen);
...
    var sb = new StringBuilder(666);
    test("bar", sb, sb.Capacity);
    string result = sb.ToString();

注意在C代碼中使用outLen參數,以確保不會溢出緩沖區。 這會破壞垃圾收集堆,使應用程序崩潰,導致致命執行引擎錯誤。

將宏定義更改為

#define WIN322_API __declspec(dllexport) __stdcall

作為替代方案,在導入時使用CallingConvention.Cdecl

例如,請閱讀此處了解有關調用約定的更多信息。

確保您擁有正確的調用約定。 您的DLL可能使用Cdecl,而C#默認使用StdCall。 總是明確定義調用約定更好。

特別是當使用存在於ANSI和寬字符版本(具有A或W前綴的那些)的Windows函數時,顯式指定CharSet以便使用正確的版本。

當函數返回一個值時,顯式封送返回值。 否則,編譯器會選擇默認值,這可能是錯誤的。 我懷疑這也是(問題)這里的問題。

所以改為這個,例如:

[DllImport("c:\\win322.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string Test([MarshalAs(UnmanagedType.LPStr)] String str);

嘗試返回LPSTR,而不是char *。 您可能還需要指定stdcall調用約定,這在.NET中是默認的,但我不確定您的非托管項目。

使用Unmanagedtype.LPTStr作為輸入。 注意額外的T

[MarshalAs(UnmanagedType.LPStr)] String str  // your current code
[MarshalAs(UnmanagedType.LPTStr)] String str // try this code

從.Net傳遞StringBuilder作為參數而不是返回一個字符串(在這種情況下,它將像一個out參數)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM