HIHI,大家好,這個標題和講的東西很特別,我現在也想不到什麼時候會需要用到它,甚至之前根本不知道.NET不能直接參考C++寫的dll-.-,不過為了將來有一天又有什麼意外還是先把這次實做的結果記錄下來,以免接下來又有需要類似的需求。
最近做到一個案子,簡單說我的網頁必須介接他提供API來讀取我需要的資訊,但是偏偏提供API的廠商只釋出兩種版本,一個是C++另一個是JAVA,而我最後選擇了ASP.NET可以直接用的C++來實做。
首先我們先用C++做一個簡單的dll:
從檔案>>新增>>專案>>之後選擇Visual C++>>Win32>>Win32主控台應用程式
確定後會跳出個Win32應用程式精靈,點選下一步,需要選擇應用程式類型為DLL
建立完的空專案會長這樣子:
上圖打開dllmain.app,可以發現裡頭已經有寫一些程式碼了,那裡是dll的進入點,可以不管他,直接在FirstCPP.cpp(會依每個人的專案名稱有所不同)中寫我們需要的function就行了。
先寫個簡單的function:
寫好後就可以把他建置成dll檔了,選擇建置>>建置方案,下方的輸出會顯示建置結果,並將dll放置專案資料夾中的debug裡。
接著到Web上來參考該dll,正常來說我們如果要參考dll都會在專案上右鍵>>加入>>參考,但是如果以這種方式去參考C++的dll會出現下方的錯誤:
所以我們必須以其他方式來參考Win32的dll:
先在Bin資料夾上點右鍵>>加入>>現有項目>>選擇剛才建置好的dll進專案內。
再來用以下方式調用該dll檔的function:
好了之後直接執行,沒意外的話應該會出現以下的錯誤:
我的解決方式是到偵錯>>例外狀況把Managed Debugging Assistants內的PInvokeStackImbalance的擲回勾勾點掉,VS2015是在偵錯>>視窗>>例外狀況設定。(不過這部分真的想請問版上大大這樣關掉會不會有其他影響):
點掉後重新執行就大功告成了!!
雖然實做出來了,但是還是有一些問題想要請教版上的各位,
1.
[DllImport(@"E:\GQSM\visual studio\Webform\Inout\Bin\FirstCPP.dll")]
關於C#內的這一行,我看網路上的文章都直接打上要參考的dll,但是我如果只留下FirstCPP.dll就會出現無法參考到模組的錯誤,想請問是不是少做了什麼。
2.就是剛剛我關掉例外處理那個部分,雖然程式跑起來沒問題,但會不會造成什麼影響。
3.其實我本來在C++有多實做一個範例,但不論如何都成功不了,想請教說問題出在哪裡(下圖)。
在C#方面我是使用string傳過去,但無論我傳什麼字串過去他只會回傳true回來,也有嘗試著把char*換成string但依舊回傳true,是不是遺漏了什麼。
參考文章:
1.http://gundambox.github.io/2015/12/01/%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%94%A8-C-%E5%AF%AB-DLL-%E5%B0%B1%E4%B8%8A%E6%89%8B/
2.https://stackoverflow.com/questions/19450783/how-to-use-dllimport-in-c
我試了一下,PInvokeStackImbalance 錯誤的問題可以在
DllImport 加上 CallingConvention = CallingConvention.Cdecl
解決。
[DllImport(@"xxx路徑", CallingConvention = CallingConvention.Cdecl)]
bool 的問題還在研究中 XD
哇嗚 沒想到你馬上實做了
太感動了吧
我現在也還在奮鬥中
問題1:
有找到問題點
http://hant.ask.helplib.com/c-Sharp/post_467056
http://www.itread01.com/articles/1478598005.html
主要是說 web 在執行程式時,不會把 bin 中的 unmanaged dll 複製到程式真正執行的地方,所以把 c++ dll 放在 bin 中還是會讀取不到。
文章有提供幾個解法:
我有測試第3個方法,程式:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int add(int a, int b);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate byte contrast(string a, string b);
protected void Page_Load(object sender, EventArgs e)
{
//取得dll位置
var dllPath = Server.MapPath("~/bin/test.dll");
//取得 dll 指標
IntPtr pDll = DllInvoke.LoadLibrary(dllPath);
//取得函數指標
IntPtr pAdd = DllInvoke.GetProcAddress(pDll, "add");
IntPtr pContrast = DllInvoke.GetProcAddress(pDll, "contrast");
//將函數指標轉為 delegate
add add = (add)Marshal.GetDelegateForFunctionPointer(pAdd, typeof(add));
contrast contrast = (contrast)Marshal.GetDelegateForFunctionPointer(pContrast, typeof(contrast));
int n = add(10, 5); //15
byte a1 = contrast("abc", "abc"); //1
byte a2 = contrast("abc", "bcd"); //0
//釋放 dll
DllInvoke.FreeLibrary(pDll);
}
新增一個獨立 class
public static class DllInvoke
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
問題2:
我有找到相關問題,差別好像是參數傳遞的方式,沒有更深入研究,所以不知道何種情形會出錯 XD,最保險就是加上 Cdecl
。
https://stackoverflow.com/questions/5602645/why-do-i-get-pinvokestackimbalance-was-detected-for-this-simple-example
問題3:
給你看看程式 XD
C++ 和 C# 的 bool 不一樣大,所以 C# 用 byte 去接,
指標那邊 ==
會比位址,所以改成 strcmp
比較字串。
C#部分
[DllImport(@"XXX路徑", CallingConvention = CallingConvention.Cdecl)]
public static extern byte contrast(string a, string b);
[DllImport(@"XXX路徑", CallingConvention = CallingConvention.Cdecl)]
public static extern int add(int a, int b);
protected void Page_Load(object sender, EventArgs e)
{
int n = add(10, 5); //15
byte a1 = contrast("abc", "abc"); //1
byte a2 = contrast("abc", "bcd"); //0
}
C++部分
extern "C" _declspec(dllexport) int add(int a, int b);
extern "C" _declspec(dllexport) bool contrast(char* a, char *b);
bool _declspec(dllexport) contrast(char* a, char *b)
{
if (strcmp(a, b) == 0)
{
return true;
}
else
{
return false;
}
}
int _declspec(dllexport) add(int a, int b)
{
return a + b;
}
哇哇哇
你之前有碰過這個嗎?
還是第一次實作啊
覺得非常厲害0_0
我有寫過 C++,
不過沒有用 C# 引用過 C++ 所以覺得蠻好玩的 XD
然後狂爬文了一個下午 @@
第一個問題也找到了
你真的是我的偶像
找到一個我覺得最完美的解法
https://dotblogs.com.tw/calm/2014/07/21/146025
在 Global.asax 內加入下面程式
protected void Application_Start(object sender, EventArgs e)
{
var path = String.Concat(Environment.GetEnvironmentVariable("PATH"), ";", Server.MapPath("~/bin"));
//將 bin 路徑加入網站的運行環境中
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
}
[DllImport(@"test.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern byte contrast(string a, string b);
[DllImport(@"test.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int add(int a, int b);
protected void Page_Load(object sender, EventArgs e)
{
int n = add(10, 5); //15
byte a1 = contrast("abc", "abc"); //1
byte a2 = contrast("abc", "bcd"); //0
}
上面 dll 不能放在 bin 的問題,原來是我的 dll 名稱和專案一樣,所以被覆蓋過去,然後在測試中另外發現 dll 名稱可以用中文,但不能有空格。
一直覺得上面的方法都有缺點,終於找到一個我覺得零缺點的方法。
哇這個非常棒!!
馬上來實做看看