15

C# 结合 Golang 开发

 4 years ago
source link: https://www.tuicool.com/articles/rEBFn2n
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.

1. 实现方式与语法形式

基本方式:将 Go 程序编译成 DLL 供 C# 调用。

1.1 Go代码

注意:代码中 export 的注释是定义的入口描述不能省略

package main

import "C"
import "fmt"

func main() {
    fmt.Println(Test())
}

var _count = 0

//Test :
//export Test
func Test() int {
    _count++
    return _count
}

在 LiteIDE 中将编译配置的 BUILDARGS 自定义值为 --buildmode=c-shared -o Test.dll ,从而形成以下编译语句。

go build --buildmode=c-shared -o Test.dll

1.2 C# 代码

[DllImport("Test.dll", EntryPoint = "Test")]
extern static int Test();

2. Windows 下编译依赖的环境

生成 DLL 依赖于 gcc,没有 gcc 环境时,会报以下错误:

"gcc": executable file not found in %PATH%

GCC下载: Windows 64位版本 || Windows 32位版本 ,也可以从从 云盘下载

下载之后,解压后确保 gcc 命令在搜索路径(Path)中。

更多信息可参考: https://www.cnblogs.com/ghj1976/p/3540257.html

3. 操作系统 64 位与 32 的编译

在 LiteIDE 中,可以通过配置 win32.envwin64.env 来指定不同的 gcc 环境路径达到生成指定系统的 DLL 文件。

4. c# 中操作系统 64 位与 32 的适配

在 c# 中判断操作系统是否 64 位,可以使用以下语句。

bool is64 = Environment.Is64BitOperatingSystem;

为了在不同的操作系统下,加载不同的 DLL,采取以下步骤来进行组织。

(1)将 32 位的版本命名为 Test32.dll,将 64 位的版本命名为 Test64.dll

(2)定义 ITest 接口,将 DLL 将要调用的方法定义为接口方法

(3)分别为ITest接口实现 Test32 与 Test64 类,在类中加载相应的 DLL

(4)通过判断操作系统类型,实例化一个 ITest 的具体实现类实例来使用

具体接口与类实现代码如下:

public interface ITest
{
    int Test();
}

public class Test32 : ITest
{
    class TestDLL
    {
        const string DLL_NAME = "Test32.dll";

        [DllImport(DLL_NAME, EntryPoint = "Test")]
        public extern static int Test();
    }

    public int Test()
    {
        return TestDLL.Test();
    }
}

public class Test64 : ITest
{
    class TestDLL
    {
        const string DLL_NAME = "Test64.dll";

        [DllImport(DLL_NAME, EntryPoint = "Test")]
        public extern static int Test();
    }

    public int Test()
    {
        return TestDLL.Test();
    }
}

实例化与调用:

ITest test = Environment.Is64BitOperatingSystem ? (ITest)new Test64() : (ITest)new Test32();
int result = test.Test();

还有一种方式:

[DllImport("kernel32")]
private static extern IntPtr LoadLibraryA([MarshalAs(UnmanagedType.LPStr)] string fileName);
-- DllImport 会先在加载的里边找名称,可以预先加载。
LoadLibraryA((Environment.Is64BitOperatingSystem ? "x64" : "x86") + "/Test.dll");

5. 其它一些问题

5.1 字符串转换

  • 传入字符串,C#: byte[] -> GO: *C.char
  • 接收字符串,GO: string -> C#: GoString struct
    GO 定义示例
//Hello :
//export Hello
func Hello(name *C.char) string {
    return fmt.Sprintf("hello %s", C.GoString(name))
}

C# GoString struct 定义

public struct GoString
{        
    public IntPtr p; 
    public int n;
    public GoString(IntPtr n1, int n2)
    {
        p = n1; n = n2;
    }
}

C# DllImport 声明

[DllImport(DLL_NAME, EntryPoint = "Hello", CallingConvention = CallingConvention.Cdecl)]
public extern static GoString Hello(byte[] name);

C# GoString struct 转 String

public string GoStringToCSharpString(GoString goString)
{
    byte[] bytes = new byte[goString.n];
    for (int i = 0; i < goString.n; i++)
    {
        bytes[i] = Marshal.ReadByte(goString.p, i);
    }
    string result = Encoding.UTF8.GetString(bytes);
    return result;
}

C# 调用示例

GoString goResult = test.Hello(Encoding.UTF8.GetBytes("张三"));
Debug.WriteLine(GoStringToCSharpString(goResult));

5.2 调试

  • CallingConvention

    在声明中加入 CallingConvention = CallingConvention.Cdecl 避免未知异常。

[DllImport("Test.dll", CallingConvention = CallingConvention.Cdecl)]

程序崩溃甚至异常提示都没有,可在加载 DLL 之前:

Environment.SetEnvironmentVariable("GODEBUG", "cgocheck=0");

6. 相关参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK