2017-08-04 111 views
1

我創建了一個C DLL,以便我可以在我的C#應用程序中使用它。
我在C++測試應用程序上測試了DLL,它工作正常,但它在C#應用程序中不起作用。
由於某些原因,我無法構建DLL的調試版本,因此我無法在調試模式下運行C#應用程序。
DLL調試配置不會找到include directories,在發佈模式,它工作得很好!
我需要說的是,我在下面給出了一個特定的方法,導致崩潰,調用DLL中的其他方法是正常的,並按預期工作。 這是主要的實現:
頭定義:從C#windows應用程序調用C DLL會導致svchost.exe崩潰

//use this function to classify an image 
CDLL_API const char* Classify(const char* img_path, int N = 2); 

的.cpp實現

CDLL_API const char* Classify(const char * img_path, int N) 
    { 
     auto classifier = reinterpret_cast<Classifier*>(GetHandle()); 
     std::vector<PredictionResults> result = classifier->Classify(std::string(img_path), N); 
     std::string str_info = ""; 
     std::stringstream ss; 
     for (size_t i = 0; i <result.size(); ++i) 
     { 
      auto label = result[i].label; 
      auto acc = result[i].accuracy; 
      ss << "label=" << label << ",acc=" << acc << "|"; 
     } 
     return ss.str().c_str(); 
    } 

C#代碼:

[DllImport(@"CDll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern string Classify([MarshalAs(UnmanagedType.LPStr)]string img_path,int N = 2); 

//... 
     var s = Classify(txtFilePath.Text, 2); 
     MessageBox.Show(s); 

所以我完全擺脫的想法什麼可能是真正的原因。

+0

您使用'CallingConvention.Cdecl'而不是'CallingConvention.StdCall'的任何特定原因? – stuartd

+0

@stuartd:這實際上來自我幾年前寫的代碼,我不記得爲什麼我選擇Cdecl而不是StdCall!順便說一句,當我測試它時,StdCall也有同樣的問題 – Breeze

回答

2

我看到您指定的調用約定在C#PInvoke的聲明CdeclCallingConvention = CallingConvention.Cdecl);因爲這是C++代碼中的默認調用約定,所以在這種情況下,不應該有任何調用約定不匹配。雖然,請注意,C接口DLL的通用調用約定是__stdcall

我看到的問題就是這樣,你從C接口的API返回字符串

CDLL_API const char* Classify(const char * img_path, int N) 
{ 
    ... 
    return ss.str().c_str(); 
} 

(順便說一句我認爲ss是像一個std::ostringstream對象。)

你使用輸出字符串流構造一個字符串(調用其str方法),然後獲得一個原始C風格的字符串指針,調用c_str。但是,當函數退出時,字符串對象被銷燬,所以C風格的原始字符串指針不再有效。

從C-DLL接口API的字符串返回到C#中,可以考慮這些選項之一:

  1. 返回一個字符串BSTR的C接口的DLL。使用SysAllocString從原始C風格字符串指針創建BSTR對象。請注意,BSTR的「自然」存儲Unicode UTF-16編碼的字符串,所以請確保將您的字符串轉換爲此編碼。 CLR能夠很好地管理BSTR字符串,因此您不必注意釋放字符串內存:這將是CLR的工作。

  2. 添加到該C接口的DLL功能的幾個參數:a 指向緩衝區的指針緩衝區大小。這將是由調用者(例如C#)分配的輸出字符串緩衝區,並且從DLL導出的C接口API將結果字符串寫入該調用方提供的緩衝區。這是例如(在C#端,輸出字符串緩衝區可以用StringBuilder對象表示)。

2

string型在C#中不與C. const char *兼容你必須使用StringBuilder

[DllImport("aCDLL.dll")] 
public extern static void getabuilder(StringBuilder abuilder); 

和C DLL:

extern "C" void __declspec(dllexport) __stdcall getabuilder(char *abuilder); 

如果你不喜歡的StringBuilder,您可以將字符串字符存儲在以C#初始化並傳遞給C函數的byte的數組中:

[DllImport("aCDLL.dll")] 
public extern static void getastring(byte[] data, ref int datalength); 

和C:

extern "C" void __declspec(dllexport) __stdcall getastring(const char *data, int *datalength);