【C# 调用 Go 语言】0x2 参数、返回值与类型转换
在上篇文章【C# 調(diào)用 Go 語(yǔ)言】0x1 Hello Golang ?中,我們將 Golang 源碼編譯為動(dòng)態(tài)鏈接庫(kù)(dll),用 C# 調(diào)用 Golang 導(dǎo)出的方法并成功的看到了控制臺(tái)的輸出。本篇文章將對(duì) C# 調(diào)用 Golang 方法做更詳細(xì)的介紹,涉及如何對(duì) Golang 方法進(jìn)行傳參、獲取返回值以及處理調(diào)用過(guò)程中的類(lèi)型轉(zhuǎn)換。
本文源代碼可以在? https://gitee.com/coderbusy/golang-with-csharp 找到。
基本的傳參與返回值
使用 Golang 編寫(xiě)一個(gè)名為 Check 的方法,該方法接收兩個(gè)整型的參數(shù)(i1,i2)并返回一個(gè)布爾值,當(dāng) i1 > i2 時(shí)返回值為 True,否則為 False :
需要一個(gè) make.bat 文件,用于生成動(dòng)態(tài)鏈接庫(kù):
同上篇,將 C# 項(xiàng)目 Golang.Ioc 的目標(biāo)平臺(tái)設(shè)置為 x86 ,將生成的 Golang.Ioc.Interop.dll 復(fù)制到項(xiàng)目中并設(shè)置為始終復(fù)制:
使用 P/Invoke 調(diào)用導(dǎo)出的方法:
運(yùn)行之后,程序?qū)?huì)產(chǎn)生如下輸出,程序行為符合我們的預(yù)期:
C、CGO、Golang 與 P/Invoke
C/C++ 經(jīng)過(guò)幾十年的發(fā)展,已經(jīng)積累了龐大的軟件資產(chǎn),它們很多久經(jīng)考驗(yàn)而且性能已經(jīng)足夠優(yōu)化。Go 語(yǔ)言必須能夠站在 C/C++ 這個(gè)巨人的肩膀之上,有了海量的 C/C++ 軟件資產(chǎn)兜底之后,我們才可以放心愉快地用 Go 語(yǔ)言編程。C 語(yǔ)言作為一個(gè)通用語(yǔ)言,很多庫(kù)會(huì)選擇提供一個(gè) C 兼容的 API,然后用其他不同的編程語(yǔ)言實(shí)現(xiàn)。Go 語(yǔ)言通過(guò)自帶的一個(gè)叫 CGO 的工具來(lái)支持 C 語(yǔ)言函數(shù)調(diào)用,同時(shí)我們可以用 Go 語(yǔ)言導(dǎo)出 C 動(dòng)態(tài)庫(kù)接口給其它語(yǔ)言使用。
Go語(yǔ)言高級(jí)編程?》?第二章 CGO編程P/Invoke 的全稱(chēng)是 Platform Invoke (平臺(tái)調(diào)用) 它實(shí)際上是一種函數(shù)調(diào)用機(jī)制,通過(guò) P/Invoke 我們就可以調(diào)用非托管 DLL 中的函數(shù)。實(shí)際上很多 NET 基類(lèi)庫(kù)中定義的類(lèi)型內(nèi)部調(diào)用了從 Kernel32.dll,User32.dll,gdi32.dll 等非托管 DLL 中導(dǎo)出的函數(shù)。
之所以可以在 C# 中調(diào)用 Golang 程序集是因?yàn)?CGO 在中間充當(dāng)了橋梁。我們的調(diào)用順序應(yīng)該是 C# -> C -> Golang 。
下表列出了 Windows API 和 C 樣式函數(shù)中使用的數(shù)據(jù)類(lèi)型。許多非托管庫(kù)包含將這些數(shù)據(jù)類(lèi)型作為參數(shù)和返回值傳遞的函數(shù)。第三列列出了相應(yīng)的 .NET Framework 內(nèi)置值類(lèi)型或可在托管代碼中使用的類(lèi)。
| Windows API 中的非托管類(lèi)型 | 非托管 C 語(yǔ)言類(lèi)型 | 托管類(lèi)型 | 描述 |
| VOID | void | System.Void | 應(yīng)用于不返回值的函數(shù)。 |
| HANDLE | void * | System.IntPtr?或?System.UIntPtr | 在 32 位 Windows 操作系統(tǒng)上為 32 位、在 64 位 Windows 操作系統(tǒng)上為 64 位。 |
| BYTE | unsigned char | System.Byte | 8 位 |
| SHORT | short | System.Int16 | 16 位 |
| WORD | unsigned short | System.UInt16 | 16 位 |
| INT | int | System.Int32 | 32 位 |
| UINT | unsigned int | System.UInt32 | 32 位 |
| LONG | long | System.Int32 | 32 位 |
| BOOL | long | System.Boolean?或?System.Int32 | 32 位 |
| DWORD | unsigned long | System.UInt32 | 32 位 |
| ULONG | unsigned long | System.UInt32 | 32 位 |
| CHAR | char | System.Char | 使用 ANSI 修飾。 |
| WCHAR | wchar_t | System.Char | 使用 Unicode 修飾。 |
| LPSTR | char * | System.String?或?System.Text.StringBuilder | 使用 ANSI 修飾。 |
| LPCSTR | const char * | System.String?或?System.Text.StringBuilder | 使用 ANSI 修飾。 |
| LPWSTR | wchar_t * | System.String?或?System.Text.StringBuilder | 使用 Unicode 修飾。 |
| LPCWSTR | const wchar_t * | System.String?或?System.Text.StringBuilder | 使用 Unicode 修飾。 |
| FLOAT | float | System.Single | 32 位 |
| DOUBLE | double | System.Double | 64 位 |
Go語(yǔ)言中數(shù)值類(lèi)型和C語(yǔ)言數(shù)據(jù)類(lèi)型基本上是相似的,以下是它們的對(duì)應(yīng)關(guān)系表:
| C語(yǔ)言類(lèi)型 | CGO類(lèi)型 | Go語(yǔ)言類(lèi)型 |
| char | C.char | byte |
| singed char | C.schar | int8 |
| unsigned char | C.uchar | uint8 |
| short | C.short | int16 |
| unsigned short | C.short | uint16 |
| int | C.int | int32 |
| unsigned int | C.uint | uint32 |
| long | C.long | int32 |
| unsigned long | C.ulong | uint32 |
| long long int | C.longlong | int64 |
| unsigned long long int | C.ulonglong | uint64 |
| float | C.float | float32 |
| double | C.double | float64 |
| size_t | C.size_t | uint |
需要注意的是,雖然在C語(yǔ)言中int、short等類(lèi)型沒(méi)有明確定義內(nèi)存大小,但是在CGO中它們的內(nèi)存大小是確定的。在CGO中,C語(yǔ)言的int和long類(lèi)型都是對(duì)應(yīng)4個(gè)字節(jié)的內(nèi)存大小,size_t類(lèi)型可以當(dāng)作Go語(yǔ)言u(píng)int無(wú)符號(hào)整數(shù)類(lèi)型對(duì)待。
在編寫(xiě)完 Golang 代碼后,如果不確定對(duì)應(yīng)的 C# 類(lèi)型,那么可以查看在編譯后與 DLL 同時(shí)生成的 .h 頭文件,對(duì)應(yīng)上面兩張表應(yīng)該就可以找到正確的類(lèi)型 。
字符串類(lèi)型參數(shù)
如果一個(gè)方法需要導(dǎo)出并且參數(shù)或返回值涉及到字符串,通常使用 *C.char 來(lái)代替 Golang 內(nèi)置的 string 類(lèi)型對(duì)外導(dǎo)出。可以調(diào)用 C.CString 方法將 Golang 的字符串類(lèi)型轉(zhuǎn)為 *C.char 類(lèi)型:
需要注意的是:C string 在 C 的堆上使用 malloc 申請(qǐng)。調(diào)用者有責(zé)任在合適的時(shí)候?qū)υ撟址M(jìn)行釋放,釋放方式可以是調(diào)用C.free(調(diào)用C.free需包含stdlib.h)。
在 Golang 源碼中新增 GetSlogan 方法,該方法接受一個(gè)名為 name 的字符串參數(shù),并返回一句為武漢加油的口號(hào)。為了可以在返回值使用完成后釋放掉由 C.CString 申請(qǐng)的內(nèi)存,再增加一個(gè) Free 方法:
C# 提供一個(gè) ICustomMarshaler 接口,可以用它來(lái)對(duì)托管內(nèi)存和非托管內(nèi)存進(jìn)行轉(zhuǎn)換。添加一個(gè) CStringMarshaler ?實(shí)現(xiàn) ICustomMarshaler 接口,幫我們處理 C# string 和 C.CString 之間的轉(zhuǎn)換過(guò)程,并保證內(nèi)存被正確釋放:
測(cè)試一下對(duì) GetSlogan 方法的調(diào)用:
運(yùn)行代碼后將產(chǎn)生以下輸出:
增加代碼進(jìn)行性能測(cè)試:
調(diào)用 52 萬(wàn) 1 千次后,內(nèi)存占用仍在 20M 以?xún)?nèi),可以證明沒(méi)有發(fā)生內(nèi)存泄漏問(wèn)題:
總結(jié)
以上是生活随笔為你收集整理的【C# 调用 Go 语言】0x2 参数、返回值与类型转换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .Net Core 认证系统之基于Ide
- 下一篇: 用 C# 写一个 Redis 数据同步小