学无先后达者为师!
不忘初心,砥砺前行。

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

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

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

基本的传参与返回值

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

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

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

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

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

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 语言类型托管类型描述
VOIDvoidSystem.Void应用于不返回值的函数。
HANDLEvoid *System.IntPtr 或 System.UIntPtr在 32 位 Windows 操作系统上为 32 位、在 64 位 Windows 操作系统上为 64 位。
BYTEunsigned charSystem.Byte8 位
SHORTshortSystem.Int1616 位
WORDunsigned shortSystem.UInt1616 位
INTintSystem.Int3232 位
UINTunsigned intSystem.UInt3232 位
LONGlongSystem.Int3232 位
BOOLlongSystem.Boolean 或 System.Int3232 位
DWORDunsigned longSystem.UInt3232 位
ULONGunsigned longSystem.UInt3232 位
CHARcharSystem.Char使用 ANSI 修饰。
WCHARwchar_tSystem.Char使用 Unicode 修饰。
LPSTRchar *System.String 或 System.Text.StringBuilder使用 ANSI 修饰。
LPCSTRconst char *System.String 或 System.Text.StringBuilder使用 ANSI 修饰。
LPWSTRwchar_t *System.String 或 System.Text.StringBuilder使用 Unicode 修饰。
LPCWSTRconst wchar_t *System.String 或 System.Text.StringBuilder使用 Unicode 修饰。
FLOATfloatSystem.Single32 位
DOUBLEdoubleSystem.Double64 位

Go语言中数值类型和C语言数据类型基本上是相似的,以下是它们的对应关系表:

C语言类型CGO类型Go语言类型
charC.charbyte
singed charC.scharint8
unsigned charC.ucharuint8
shortC.shortint16
unsigned shortC.shortuint16
intC.intint32
unsigned intC.uintuint32
longC.longint32
unsigned longC.ulonguint32
long long intC.longlongint64
unsigned long long intC.ulonglonguint64
floatC.floatfloat32
doubleC.doublefloat64
size_tC.size_tuint

需要注意的是,虽然在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 类型:

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

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

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

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

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

增加代码进行性能测试:

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

赞(4) 打赏
未经允许不得转载:码农很忙 » 【C# 调用 Go 语言】0x2 参数、返回值与类型转换

评论 1

  1. #1

    大佬,有没有转换数组的方法?就是go里面的[]byte转换成c#里的byte[]

    帅帅2年前 (2021-12-04)

给作者买杯咖啡

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册