C#/C++混合編程一二事

C#/C++混合編程一二事

C#/C++混編的情形經(jīng)常會(huì)碰到反砌,下面就來講一講一些需要注意的點(diǎn)。廢話不多說膀藐,Let's get started. (時(shí)間有限屠阻,暫時(shí)沒有寫完,后續(xù)會(huì)持續(xù)更細(xì)额各。如果有寫的不嚴(yán)謹(jǐn)甚至錯(cuò)誤的地方歡迎大家指正)

一国觉、C++導(dǎo)出函數(shù)的聲明

在導(dǎo)出函數(shù)的頭文件,經(jīng)常見到如下的模板

#pragma once
#ifdef __cplusplus
extern "C" {
#endif

    #ifdef TEST_EXPORTS
    #define TEST_API __declspec(dllexport) 
    #else
    #define TEST_API __declspec(dllimport)
    #endif
    #define CALLINGCONVENTION __cdecl

    //導(dǎo)出的函數(shù)一
    TEST_API int CALLING_CONVENTION Add(int a, int b);
    //導(dǎo)出的函數(shù)二
    TEST_API int CALLING_CONVENTION Subtract(int a, int b);

#ifdef __cplusplus
};
#endif

下面就來給出解釋虾啦。

  1. #ifdef __cplusplus這條預(yù)編譯指令麻诀。如果項(xiàng)目是C++的項(xiàng)目痕寓,則用extern "C" {} 將所有的代碼包起來。這樣的好處是導(dǎo)出的函數(shù)名仍和代碼中定義的一致蝇闭,否則函數(shù)名前后會(huì)加上一些看起來很奇怪的符號(hào)呻率。這是因?yàn)镃++的函數(shù)有重載機(jī)制,同一個(gè)函數(shù)名呻引,可以有多種參數(shù)形式礼仗,不加extern "C"包起來的話记餐,編譯器就會(huì)在函數(shù)名前后加上一些符號(hào)汹桦,來具體到這個(gè)函數(shù)的具體的某一種形式既绕。

  2. #ifdef TEST_EXPORTS這條預(yù)編譯指令网缝。這段話的意思就是如果定義了TEST_EXPORTS這個(gè)宏肥哎,則把TEST_API這個(gè)宏定義成__declspec(dllexport)挽封,否則把TEST_API定義成__declspec(dllimport)墨林。這個(gè)設(shè)計(jì)是為了開發(fā)者和調(diào)用者的便利性設(shè)計(jì)的巍沙。因?yàn)檫@個(gè)頭文件饥伊,后來也會(huì)拷貝給調(diào)用者用慎恒,開發(fā)者的導(dǎo)出函數(shù)的這個(gè)項(xiàng)目中定義一個(gè)宏TEST_EXPORTS,而調(diào)用者那邊沒有這個(gè)宏撵渡。所以TEST_API對(duì)于開發(fā)者就是__declspec(dllexport)融柬,對(duì)于調(diào)用者就是__declspec(dllimport)。__declspec(dllexport)告訴編譯器這是一個(gè)需要導(dǎo)出的函數(shù)趋距,__declspec(dllimport)告訴編譯器這是一個(gè)從外部的庫文件中導(dǎo)入的函數(shù)粒氧。

  3. #define CALLINGCONVENTION _cdecl 這個(gè)宏,就是調(diào)用約定节腐,可以根據(jù)需要把它定義成__cdecl或者_(dá)_stdcall或者其他外盯。調(diào)用約定關(guān)系到函數(shù)參數(shù)入棧和出棧的清理方式。常見的有__cdecl翼雀、_stdcall饱苟、_fastcall. 最常用的就是_cdecl、_stdcall. 如果調(diào)用方用C#狼渊,則推薦用_cdecl. 因?yàn)開stdcall也會(huì)像沒加extern "C" 那樣導(dǎo)致導(dǎo)出的函數(shù)名和頭文件中定義的不一致箱熬。(__cdecl許多人記不住,其實(shí)它就是C Declaration的縮寫狈邑。

二城须、C++導(dǎo)出函數(shù)的實(shí)現(xiàn)

聲明寫好了,實(shí)現(xiàn)就簡(jiǎn)單了米苹。參考如下糕伐,不解釋。

#include “相關(guān)的頭文件"
//加法
TEST_API int CALLING_CONVENTION Add(int a, int b)
{
    return a + b;
}

//減法
TESTT_API void CALLING_CONVENTION Subtract(int a, int b)
{
    return a - b;
}

三蘸嘶、C#導(dǎo)入函數(shù)的聲明

C#調(diào)用之前良瞧,先寫一個(gè)類陪汽,里面用來聲明從C++導(dǎo)入的函數(shù)

class TestApi
{
    private const string strDllName = "Test.dll";
    private const CallingConvention ccDef = CallingConvention.Cdecl;
    private const CharSet csDef = CharSet.Unicode;

    [DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "Add")]
     public static extern int Add(int a, int b);
     
     [DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "Add")]
     public static extern int Subtract(int a, int b);
}

說明:

  1. 記得添加命名空間using System.Runtime.InteropServices;

  2. 函數(shù)聲明前寫上DllImport屬性,包含導(dǎo)入的庫文件名褥蚯,調(diào)用約定掩缓,字符集,入口點(diǎn)遵岩。庫文件名可以寫相對(duì)路徑你辣,也可以寫絕對(duì)路徑。

  3. 調(diào)用約定和C++保持一致尘执,推薦用Cdecl方式舍哄,如果強(qiáng)行用Stdcall會(huì)直接報(bào)錯(cuò)。

  4. 字符集可寫可不寫誊锭,一般不會(huì)用上表悬。但是如果C++參數(shù)中有char或者wchart時(shí),就需要根據(jù)實(shí)際情況填寫字符集這個(gè)屬性丧靡。如果參數(shù)為wchar_t*時(shí)蟆沫,指定字符集為Unicode, C#的參數(shù)使用string即可(當(dāng)然也可以使用StringBuilder,具體不討論)温治。當(dāng)然也可以不指定字符集饭庞,在參數(shù)前加上如下聲明[MarshalAs(UnmanagedType.LPWStr)]也可。

  5. 入口點(diǎn)熬荆,就是dll中導(dǎo)出的函數(shù)的符號(hào)舟山,當(dāng)且僅當(dāng)調(diào)用約定為__cdecl、并且加了extern "C"包裝的情況下卤恳,入口點(diǎn)才和函數(shù)名相同累盗。導(dǎo)出的函數(shù)名可以通過軟件Dependency Walker查看。

四突琳、參數(shù)的傳遞

  1. 基本類型若债,比如int、float拆融、double這些直接傳就好蠢琳,不再贅述。

  2. 字符串類型冠息。舉個(gè)例子:

TEST_API void CALLING_CONVENTION DoSomthing(wchat_t* path);
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "DoSomthing")]
public static extern void DoSomthing(string path);
//說明:C++聲明用char*挪凑、wchart孕索,則C#端用string或者StringBuilder(可以自行嘗試逛艰,不贅述)類型傳遞,
//但是需要指定string傳遞給char或者wchar_t*的編碼方式搞旭,
//指定有兩種方式散怖。一種是在DllImport中寫Charset = Charset.Unicode(對(duì)應(yīng)wchar_t*), 
//Charset = Charset.Ansi(對(duì)應(yīng)char*)菇绵,
//另一種是在string前加屬性[MarshalAs(UnmanagedType.LPWStr)](對(duì)應(yīng)wchar_t*),
//或者[MarshalAs(UnmanagedType.LPStr)](對(duì)應(yīng)char*)
  1. 一般指針镇眷,比如void咬最、自定義類的指針YourClass、句柄如HANDLE欠动、HWND等永乌。舉個(gè)例子
typedef void* IDicomSCU;
TEST_API void CALLING_CONVENTION IDicomSCU_Close(IDicomSCU handle);
//C#:    
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "IDicomSCU_Close")] 
public static extern void IDicomSCU_Close(IntPtr handle);
//說明:既然這么多種東西都可以用C#的IntPtr來傳遞
//那在編碼時(shí)一定需要注意IntPtr具體指向的一個(gè)什么東西
//切不可混用。
  1. 函數(shù)指針具伍。這種情形翅雏,C++需要一個(gè)函數(shù)指針,C#傳委托即可人芽。舉個(gè)例子
//聲明函數(shù)指針
typedef bool(*IDriverCommandEvent)(int command, wchar_t *val);

TEST_API bool CALLING_CONVENTION IDriver_Open(HWND hWnd, IDriverCommandEvent event);
//先聲明一個(gè)委托望几,注意委托的參數(shù)中有string類型,我就在前方加上了[MarshalAs(UnmanagedType.LPWStr)]以和C++的wchar_t*對(duì)應(yīng)
//并且聲明這個(gè)委托的調(diào)用約定是Cdecl還是Stdcall萤厅,這個(gè)很重要橄抹,必須和C++端的調(diào)用約定保持一致。
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate bool DriverCommandEventHandler(int command, [MarshalAs(UnmanagedType.LPWStr)] string val);

//導(dǎo)出函數(shù)聲明     
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "IDriver_Open")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool IDriver_Open(HWND hWnd, DriverCommandEventHandler driverCommandEvent);
//說明:注意C#在傳遞形參的時(shí)候惕味,形參不要是一個(gè)臨時(shí)對(duì)象楼誓,因?yàn)榕R時(shí)對(duì)象的引用計(jì)數(shù)為0后會(huì)被GC回收,
//導(dǎo)致C++調(diào)用這個(gè)函數(shù)指針的時(shí)候找不到而報(bào)錯(cuò)
  1. 結(jié)構(gòu)體指針名挥。參照C++端定義的結(jié)構(gòu)體也在C#端定義同樣的結(jié)構(gòu)體慌随,具體參見第六節(jié)。
    然后C#的形參用ref YourStruct類型躺同,C++端用YourStruct*接收阁猜。

五、返回值的傳遞

基本類型基本可以直接聲明蹋艺,這里就把特殊的返回值拿出來提一下

  1. bool類型:如果按照常規(guī)的寫法剃袍,返回值總是錯(cuò)誤的。我搜索了stackoverflow捎谨,發(fā)現(xiàn)了這個(gè)問題的解決方案民效,
    參考鏈接如下:
    https://stackoverflow.com/questions/4608876/c-sharp-dllimport-with-c-boolean-function-not-returning-correctly
    因此,聲明就應(yīng)該這樣寫:
TEST_API bool CALLING_CONVENTION DoSomething();
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint ="DoSomething")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool DoSomething();

六涛救、復(fù)雜的數(shù)據(jù)類型的定義(結(jié)構(gòu)體)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畏邢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子检吆,更是在濱河造成了極大的恐慌舒萎,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹭沛,死亡現(xiàn)場(chǎng)離奇詭異臂寝,居然都是意外死亡章鲤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門咆贬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來败徊,“玉大人,你說我怎么就攤上這事掏缎≈灞模” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵眷蜈,是天一觀的道長(zhǎng)根欧。 經(jīng)常有香客問我,道長(zhǎng)端蛆,這世上最難降的妖魔是什么凤粗? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮今豆,結(jié)果婚禮上嫌拣,老公的妹妹穿的比我還像新娘。我一直安慰自己呆躲,他們只是感情好异逐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著插掂,像睡著了一般灰瞻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辅甥,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天酝润,我揣著相機(jī)與錄音,去河邊找鬼璃弄。 笑死要销,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夏块。 我是一名探鬼主播疏咐,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脐供!你這毒婦竟也來了浑塞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤政己,失蹤者是張志新(化名)和其女友劉穎酌壕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仅孩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年托猩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了印蓖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辽慕。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赦肃,靈堂內(nèi)的尸體忽然破棺而出溅蛉,到底是詐尸還是另有隱情,我是刑警寧澤他宛,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布船侧,位于F島的核電站,受9級(jí)特大地震影響厅各,放射性物質(zhì)發(fā)生泄漏镜撩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一队塘、第九天 我趴在偏房一處隱蔽的房頂上張望袁梗。 院中可真熱鬧,春花似錦憔古、人聲如沸遮怜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锯梁。三九已至,卻和暖如春焰情,著一層夾襖步出監(jiān)牢的瞬間陌凳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工内舟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冯遂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓谒获,卻偏偏與公主長(zhǎng)得像蛤肌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子批狱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348