2012-07-08 45 views
2

我目前正在探索C#中的DLL導出函數和P /調用。 我創建非常簡單的.dll文件:從DLL調用方法時C#應用程序塊

Test.h

#ifndef TEST_DLL_H 
#define TEST_DLL_H 
extern "C" __declspec(dllexport) const char * __cdecl hello(); 
extern "C" __declspec(dllexport) const char * __cdecl test();  
#endif // TEST_DLL_H 

Test.cpp的

#include <stdlib.h> 
#include "test.h" 
#include <string.h> 

const char* hello() 
{ 
    char *novi = (char *)malloc(51); 
    strcpy(novi, "Test."); 

    return novi; 
} 

const char * test() 
{ 
    return "Test."; 
} 

我編譯它,並在C#項目中使用這樣的:

[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)] 
    public static extern IntPtr hello(); 

    [DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)] 
    public static extern string test(); 

    private void button1_Click(object sender, EventArgs e) 
    { 
     MessageBox.Show(test()); 
     IntPtr a = hello(); 
     MessageBox.Show(Marshal.PtrToStringAnsi(a)); 
    } 

但它不工作。 test()成功調用並返回正確的字符串。但hello()只是掛起程序。如果我從hello()定義中除去malloc行並返回常量,那麼一切正常,所以我想現在我知道malloc存在一個問題。

此外,我已經看到,當返回類型是char *時不應該使用字符串。如果這是真的,爲什麼我們應該使用IntPtr?

+0

您是否嘗試過調試它?爲此,在C#項目屬性Debug頁面中,可以啓用非託管代碼調試。然後在C#項目中打開test.cpp,在hello中放置一個斷點並從visual studio啓動C#項目。 – 2012-07-09 10:39:34

回答

3

通過DLL邊界返回字符串的函數很難從C或C++可靠地調用,但從C#執行時不會更好。問題在於調用者將如何釋放字符串緩衝區。這需要完成hello()而不是test()。一些很難猜測的東西。 hello()函數需要使用free()函數,使用用於調用malloc()的相同分配器。這隻有在DLL和調用者共享相同的CRT實現時才能工作。其中的機率很渺茫。

pinvoke編組器也釋放字符串緩衝區,它必須。只有合理的選擇CoTaskMemFree()纔會這樣做。它使用COM使用的默認分配器。這不會很好,你的C代碼沒有使用CoTaskMemAlloc()。這可能的結果取決於操作系統。在Vista及更高版本中,您的程序將會隨着AccessViolation而死亡,這些Windows版本使用嚴格的堆分配器來設計用於防止行爲不當的程序。在XP上,你會得到一些內存泄漏和堆損壞之間的東西,聽起來像是第二種選擇。

將返回值聲明爲IntPtr將會達到良好的效果。那麼,你的程序不會崩潰,你仍然有一個內存泄漏,你不能插入。沒有辦法可靠地呼叫free()。或者在你的C代碼中使用CoTaskMemAlloc(),這樣pinvoke編組器的釋放調用就可以工作。

但現實地說,就是不要這樣寫C代碼。總是使用由調用者分配的內存,所以永遠不會猜測誰擁有內存。這需要一個類似於此的功能簽名:

extern "C" __declspec(dllexport) 
int hello(char* buffer, int bufferSize); 
+1

+1優秀的職位。您可能已經提到,大多數(所有?)Windows API都使用該調用方提供緩衝區方法(並獨立於是否用於字符串或結構)並非巧合。 – Lucero 2012-07-08 19:55:31

+1

你的建議使用客戶端分配的緩衝區有很大的意義,但我仍然看不出爲什麼打個招呼應該掛起。我同意你好,但是從技術上講,這不能成爲'掛'的原因。 Marshaller和'爲什麼'它叫CoTaskMemFree?恕我直言,這是一個'事實上'的約定,誰分配內存應該釋放它而不是CLR。例如,如果hello返回一個指向靜態緩衝區的指針,會發生什麼? – 2012-07-09 10:15:11

+1

pinvoke編組調用者釋放test()返回的字符串。但字符串沒有分配在任何堆上。接下來發生的事情是完全不可預測的,但由於堆損壞而導致的掛起並不是不可能的。你的'事實上'的約定是準確的,當函數返回一個字符串時,它就不能這樣工作,除非你導出一個釋放它的函數。你沒有這樣做。自定義編組人員將被要求使用它。你沒有這樣做。建議的解決方案使用「事實上的」慣例,調用者分配和釋放。 – 2012-07-09 13:05:22