目标

将golang编写的包以dll的形式导出,供windows平台下的应用程序使用。

环境

  • golang:go version go1.11.4 windows/amd64 用于生成中间文件和c++库的头文件
  • vs2015:编译windows平台上的应用程序
  • TDM-gcc-x64:编译过程中需要将go build生成的.a中间根据导出函数定义文件生成dll,在 此处下载 TDM-gcc-x64

因为我这边是在x64的平台上,所以我这边golang和gcc都是x64平台,按照自己的需要,若是需要在x86平台上安装对应的版本即可。

生成golang编写的dll文件

一、go导出包
这里编写一个名为hello.go的测试golang导出包,这个测试包并没有嵌套或者引用其他的包:

package main

import "C"
import (
	"fmt"
)

//export Sum
func Sum(a int, b int) int { //最简单的计算和
	return a + b
}

//export Show
func Show(str string) { //显示传入的字符串
	fmt.Print(str)
}

func main() {
}

说明:
1、package mainfunc main()必须有,导出的包必须是含有main的
2、导出的函数前面用//export +函数名声明,表示需要导出该函数
3、引用包import "C"

二、编译golang导出包

1、生成中间.a和.h文件

go build -buildmode=c-archive hello.go

执行上述命令后,在hello.go文件的同级目录下生成了hello.ahello.h两个文件,如:

go生成so库 java调用 类型 go语言生成dll_windows平台


2、生成dll

在生成dll和lib文件的时候,我们需要编写一个hello.def的文件才能生成dll文件,def文件中描述的是导出的函数列表,这个同windows平台是一致的。hello.def文件内容:

EXPORTS
    Sum
    Show

对应了在hello.go导出包中的两个导出函数,注意函数名字一定要一致,对大小写敏感。

gcc hello.def hello.a -shared -lwinmm -lWs2_32 -o hello.dll -Wl,--out-implib,hello.lib

执行后在同级目录下回多出两个文件hello.dllhello.lib,如:

go生成so库 java调用 类型 go语言生成dll_go生成so库 java调用 类型_02


到此我们完成了dll的生成

在win32程序中使用golang生成的dll

使用vs2015(我机子上安装vs2015,其他的也是可以的)建立win32程序,将上一步生成的两个文件hello.hhello.dll拷贝大main.cpp的目录下,其中main.cpp的代码内容如下:

#include <stdio.h>
#include <Windows.h>
#include "hello.h"

typedef GoInt32(*funcPtrSum)(GoInt32 p0, GoInt32 p1);
typedef void(*funcPtrShow)(GoString p0);

int main() 
{
	HINSTANCE hInstance = LoadLibrary("hello.dll");
	if (NULL == hInstance)
	{
		printf("load dll failed.\n");
		return 0;
	}
	funcPtrSum pFnSum = (funcPtrSum)GetProcAddress(hInstance, "Sum");
	if (pFnSum)
	{
		GoInt32 result = pFnSum(5, 4);
		printf("Add(5,4) = %d\n", result);
	}

	funcPtrShow pFnShow = (funcPtrShow)GetProcAddress(hInstance, "Show");
	if (pFnShow)
	{
		const char* str = "this is a test strings from c++ main function\n" ;
		GoString gstr{ str,(ptrdiff_t)strlen(str) };//就是go中的string类型
		pFnShow(gstr);
	}
	FreeLibrary(hInstance);
	return 0;
}

上述代码中使用了dll的显示加载的方法,提前声明了两个函数指针funcPtrSumfuncPtrShow,用于获取dll中的函数指针地址。需要注意的是函数指针funcPtrShow的返回值,在hello.go中并没有返回值,但是声明时必须有返回值,我这边写了void,似乎写成GoInt32也是没错的。

注意编译时的平台,因为我们前面都是在x64下编译生成的dll。然后使用vs内置的编译器直接编译代码,发现报了一大堆错误:

go生成so库 java调用 类型 go语言生成dll_lib_03


这个错误是因为在生成的hello.h文件中需要屏蔽三行代码,屏蔽

typedef __SIZE_TYPE__ GoUintptr;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

之后,继续编译,成功~运行生成的可执行程序,如:

go生成so库 java调用 类型 go语言生成dll_go生成so库 java调用 类型_04


和我们在hello.go代码中的结果期望是一致的。

后续

1、导出包引用了其他的包

hello.go引用了别的数据包时,并没有其他的差别,只需要编译最终导出的那个文件即可。

下图中我修改了hello.go的文件,将具体的导出函数在另一个包comm.go中实现了,如:

go生成so库 java调用 类型 go语言生成dll_dll_05


编译时的指令没有变化,内部的引用关系,go build会自己去找,并编译,无需人工干预。2、为了方便用脚本来自动编译

脚本build-go.bat生成dll,将脚本文件拷贝到.go的目录下,在命令行中找到该目录,拖需要编译的文件到命令行中,回车开始编译:

go生成so库 java调用 类型 go语言生成dll_dll_06


编译过程:

go生成so库 java调用 类型 go语言生成dll_golang_07


生成了.lib.dll.h文件:

go生成so库 java调用 类型 go语言生成dll_dll_08

@echo off

REM 设置源文件的目录
set filepath=%1
REM 设置def文件的目录
set defpath=%~dp1%~n1.def
REM 设置生成的目标dll的路径
set dllpath=%~dp1%~n1.dll
set libpath=%~dp1%~n1.lib

IF NOT EXIST "%filepath%" (
	goto err
) ELSE (
	goto make
)

:err
echo 文件不存在!
set/p exepath=请拖拽文件这里或输入文件路径然后按下回车
IF NOT EXIST "%filepath%" (
	goto err
) ELSE (
	goto make
)

:make
echo 编译 %~nx1 -^> %~n1.dll
echo 正在编译 %~nx1 -^> %~n1.a
call go build -buildmode=c-archive %filepath%
echo 生成%~n1.a和%~n1.h文件

echo 正在编译 %~nx1.a -^> %~n1.dll
call gcc %defpath% %~dp1%~n1.a -shared -lwinmm -lWs2_32 -o %dllpath% -Wl,--out-implib,%libpath%
echo 生成%~n1.dll和%~n1.lib文件

REM 清除中间文件
for /r %%f in (*.a,*.o) do del %%f
echo 编译完成!
:end

最后一个脚本是使用了gcc编译生成了.exe文件,在windows平台已经完成了所有的工作,生成的dll文件已经可以使用。下面这个脚本用于调用dll的编译,只是看到了,顺手写下来:

@echo off
call g++ -c main.cpp -o main.o -g -std=c++11
call g++ main.o test.dll -o main.exe -g -std=c++11
echo 编译完成!
pause

更多cgo的知识点:

golang学习笔记-golang调用c实现的.so接口细节golang学习笔记-golang调用c实现的dll接口细节golang学习笔记-golang调用c实现的dll接口细节(二)

参考:
https://github.com/Baozisoftware/go-dll/wiki/%E7%94%9F%E6%88%90Go%E7%89%88DLL