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%
3. 操作系统 64 位与 32 的编译
在 LiteIDE 中,可以通过配置 win32.env 和 win64.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");
补充一下 LoadLibrary 的查找路径说明
LoadLibrary的查找路径,可以参见MSDN上的文章:Dynamic-Link Library Search Order。
默认情况如下:
The directory from which the application loaded. (应用程序所在的目录)
The system directory. Use the GetSystemDirectory function to get the path of this directory. (system32目录)
The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. (System目录)
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory. (Windows目录)
The current directory. (当前目录,执行命令的发起目录)
The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. (PATH路径)
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. 相关参考