iT邦幫忙

1

[筆記][ASP.NET]如何在.NET環境下使用C++的dll檔

HIHI,大家好,這個標題和講的東西很特別,我現在也想不到什麼時候會需要用到它,甚至之前根本不知道.NET不能直接參考C++寫的dll-.-,不過為了將來有一天又有什麼意外還是先把這次實做的結果記錄下來,以免接下來又有需要類似的需求。

最近做到一個案子,簡單說我的網頁必須介接他提供API來讀取我需要的資訊,但是偏偏提供API的廠商只釋出兩種版本,一個是C++另一個是JAVA,而我最後選擇了ASP.NET可以直接用的C++來實做。

首先我們先用C++做一個簡單的dll:
從檔案>>新增>>專案>>之後選擇Visual C++>>Win32>>Win32主控台應用程式
https://ithelp.ithome.com.tw/upload/images/20180113/20106935Ni9CmTFe1K.jpg
確定後會跳出個Win32應用程式精靈,點選下一步,需要選擇應用程式類型為DLL
https://ithelp.ithome.com.tw/upload/images/20180113/20106935xwSLI4CWLe.jpg
建立完的空專案會長這樣子:
https://ithelp.ithome.com.tw/upload/images/20180113/20106935qQDRI2Syuu.jpg
上圖打開dllmain.app,可以發現裡頭已經有寫一些程式碼了,那裡是dll的進入點,可以不管他,直接在FirstCPP.cpp(會依每個人的專案名稱有所不同)中寫我們需要的function就行了。

先寫個簡單的function:
https://ithelp.ithome.com.tw/upload/images/20180114/20106935l456uUy8J6.jpg
寫好後就可以把他建置成dll檔了,選擇建置>>建置方案,下方的輸出會顯示建置結果,並將dll放置專案資料夾中的debug裡。
https://ithelp.ithome.com.tw/upload/images/20180113/20106935f1HxEp4YLb.jpg
https://ithelp.ithome.com.tw/upload/images/20180113/20106935kjYQZYpvn1.jpg

接著到Web上來參考該dll,正常來說我們如果要參考dll都會在專案上右鍵>>加入>>參考,但是如果以這種方式去參考C++的dll會出現下方的錯誤:
https://ithelp.ithome.com.tw/upload/images/20180113/20106935mYhQpp2P2A.jpg

所以我們必須以其他方式來參考Win32的dll:
先在Bin資料夾上點右鍵>>加入>>現有項目>>選擇剛才建置好的dll進專案內。
https://ithelp.ithome.com.tw/upload/images/20180113/201069354nf1U5HUWK.jpg
再來用以下方式調用該dll檔的function:
https://ithelp.ithome.com.tw/upload/images/20180114/201069354moxvua1Fb.jpg
好了之後直接執行,沒意外的話應該會出現以下的錯誤:
https://ithelp.ithome.com.tw/upload/images/20180113/20106935hIMDunPUbv.jpg
我的解決方式是到偵錯>>例外狀況把Managed Debugging Assistants內的PInvokeStackImbalance的擲回勾勾點掉,VS2015是在偵錯>>視窗>>例外狀況設定。(不過這部分真的想請問版上大大這樣關掉會不會有其他影響):
https://ithelp.ithome.com.tw/upload/images/20180114/20106935ikOrqUzuTz.jpg
點掉後重新執行就大功告成了!!
https://ithelp.ithome.com.tw/upload/images/20180114/20106935oaPfw1c0UE.jpg

雖然實做出來了,但是還是有一些問題想要請教版上的各位,
1.

[DllImport(@"E:\GQSM\visual studio\Webform\Inout\Bin\FirstCPP.dll")]

關於C#內的這一行,我看網路上的文章都直接打上要參考的dll,但是我如果只留下FirstCPP.dll就會出現無法參考到模組的錯誤,想請問是不是少做了什麼。
2.就是剛剛我關掉例外處理那個部分,雖然程式跑起來沒問題,但會不會造成什麼影響。
3.其實我本來在C++有多實做一個範例,但不論如何都成功不了,想請教說問題出在哪裡(下圖)。
https://ithelp.ithome.com.tw/upload/images/20180114/20106935FSlXeLf10D.jpg
在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


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
小碼農米爾
iT邦高手 1 級 ‧ 2018-01-14 14:57:44

我試了一下,PInvokeStackImbalance 錯誤的問題可以在
DllImport 加上 CallingConvention = CallingConvention.Cdecl 解決。

[DllImport(@"xxx路徑", CallingConvention = CallingConvention.Cdecl)]

bool 的問題還在研究中 XD

看更多先前的回應...收起先前的回應...
神Q超人 iT邦研究生 5 級 ‧ 2018-01-14 15:49:24 檢舉

哇嗚 沒想到你馬上實做了
太感動了吧/images/emoticon/emoticon02.gif
我現在也還在奮鬥中

問題1:
有找到問題點
http://hant.ask.helplib.com/c-Sharp/post_467056
http://www.itread01.com/articles/1478598005.html
主要是說 web 在執行程式時,不會把 bin 中的 unmanaged dll 複製到程式真正執行的地方,所以把 c++ dll 放在 bin 中還是會讀取不到。
文章有提供幾個解法:

  1. 將 dll 放在 Windows System32 資料夾內
  2. 將 dll 路徑加入環境變數 Path
  3. 使用動態的方式載入dll

我有測試第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;
}
神Q超人 iT邦研究生 5 級 ‧ 2018-01-14 19:27:02 檢舉

哇哇哇
你之前有碰過這個嗎?
還是第一次實作啊
覺得非常厲害0_0

我有寫過 C++,
不過沒有用 C# 引用過 C++ 所以覺得蠻好玩的 XD
然後狂爬文了一個下午 @@
第一個問題也找到了
/images/emoticon/emoticon35.gif

神Q超人 iT邦研究生 5 級 ‧ 2018-01-14 20:58:58 檢舉

你真的是我的偶像/images/emoticon/emoticon24.gif

找到一個我覺得最完美的解法
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 名稱可以用中文,但不能有空格。

一直覺得上面的方法都有缺點,終於找到一個我覺得零缺點的方法。
/images/emoticon/emoticon39.gif

神Q超人 iT邦研究生 5 級 ‧ 2018-01-15 06:07:29 檢舉

哇這個非常棒!!
馬上來實做看看/images/emoticon/emoticon14.gif

我要留言

立即登入留言