19

【C# 调用 Go 语言】0x2 参数、返回值与类型转换

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAwNTMxMzg1MA%3D%3D&%3Bmid=2654077470&%3Bidx=3&%3Bsn=2fea15dace3cd261f11ef2910d9510ae
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在上篇文章 【C# 调用 Go 语言】0x1 Hello Golang 中,我们将 Golang 源码编译为动态链接库(dll),用 C# 调用 Golang 导出的方法并成功的看到了控制台的输出。本篇文章将对 C# 调用 Golang 方法做更详细的介绍,涉及如何对 Golang 方法进行传参、获取返回值以及处理调用过程中的类型转换。

本文源代码可以在  https://gitee.com/coderbusy/golang-with-csharp 找到。

基本的传参与返回值

使用 Golang 编写一个名为 Check 的方法,该方法接收两个整型的参数(i1,i2)并返回一个布尔值,当 i1 > i2 时返回值为 True,否则为 False :

bYjMN3A.png!web

需要一个 make.bat 文件,用于生成动态链接库:

jaqMVbu.png!web

同上篇,将 C# 项目 Golang.Ioc 的目标平台设置为 x86 ,将生成的 Golang.Ioc.Interop.dll 复制到项目中并设置为始终复制:

RreUzyJ.png!web

使用 P/Invoke 调用导出的方法:

eUvARnr.png!web

运行之后,程序将会产生如下输出,程序行为符合我们的预期:

aQvUbiU.png!web

C、CGO、Golang 与 P/Invoke

C/C++ 经过几十年的发展,已经积累了庞大的软件资产,它们很多久经考验而且性能已经足够优化。Go 语言必须能够站在 C/C++ 这个巨人的肩膀之上,有了海量的 C/C++ 软件资产兜底之后,我们才可以放心愉快地用 Go 语言编程。C 语言作为一个通用语言,很多库会选择提供一个 C 兼容的 API,然后用其他不同的编程语言实现。Go 语言通过自带的一个叫 CGO 的工具来支持 C 语言函数调用,同时我们可以用 Go 语言导出 C 动态库接口给其它语言使用。

Go语言高级编程 》 第二章 CGO编程

P/Invoke 的全称是 Platform Invoke (平台调用) 它实际上是一种函数调用机制,通过 P/Invoke 我们就可以调用非托管 DLL 中的函数。实际上很多 NET 基类库中定义的类型内部调用了从 Kernel32.dll,User32.dll,gdi32.dll 等非托管 DLL 中导出的函数。

之所以可以在 C# 中调用 Golang 程序集是因为 CGO 在中间充当了桥梁。我们的调用顺序应该是 C# -> C -> Golang 。

下表列出了 Windows API 和 C 样式函数中使用的数据类型。许多非托管库包含将这些数据类型作为参数和返回值传递的函数。第三列列出了相应的 .NET Framework 内置值类型或可在托管代码中使用的类。

Windows API 中的非托管类型 非托管 C 语言类型 托管类型 描述 VOID void System.Void 应用于不返回值的函数。 HANDLE void * System.IntPtr 或 System.UIntPtr 在 32 位 Windows 操作系统上为 32 位、在 64 位 Windows 操作系统上为 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语言中数值类型和C语言数据类型基本上是相似的,以下是它们的对应关系表:

C语言类型 CGO类型 Go语言类型 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语言中int、short等类型没有明确定义内存大小,但是在CGO中它们的内存大小是确定的。在CGO中,C语言的int和long类型都是对应4个字节的内存大小,size_t类型可以当作Go语言uint无符号整数类型对待。

在编写完 Golang 代码后,如果不确定对应的 C# 类型,那么可以查看在编译后与 DLL 同时生成的 .h 头文件,对应上面两张表应该就可以找到正确的类型 。

字符串类型参数

如果一个方法需要导出并且参数或返回值涉及到字符串,通常使用 *C.char 来代替 Golang 内置的 string 类型对外导出。可以调用 C.CString 方法将 Golang 的字符串类型转为 *C.char 类型:

Y7fIRnE.png!web

需要注意的是:C string 在 C 的堆上使用 malloc 申请。调用者有责任在合适的时候对该字符串进行释放,释放方式可以是调用C.free(调用C.free需包含stdlib.h)。

在 Golang 源码中新增 GetSlogan 方法,该方法接受一个名为 name 的字符串参数,并返回一句为武汉加油的口号。为了可以在返回值使用完成后释放掉由 C.CString 申请的内存,再增加一个 Free 方法:

qyMZjaz.png!web

C# 提供一个 ICustomMarshaler 接口,可以用它来对托管内存和非托管内存进行转换。添加一个 CStringMarshaler  实现 ICustomMarshaler 接口,帮我们处理 C# string 和 C.CString 之间的转换过程,并保证内存被正确释放:

yiQRBbF.jpg!web

测试一下对 GetSlogan 方法的调用:

EzAFzy7.jpg!web

运行代码后将产生以下输出:

zqQzaiF.png!web

增加代码进行性能测试:

y6RnaaN.jpg!web

调用 52 万 1 千次后,内存占用仍在 20M 以内,可以证明没有发生内存泄漏问题:

zq6Z3aB.png!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK