目标
将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 main
和func main()
必须有,导出的包必须是含有main的
2、导出的函数前面用//export +函数名
声明,表示需要导出该函数
3、引用包import "C"
二、编译golang导出包
1、生成中间.a和.h文件
go build -buildmode=c-archive hello.go
执行上述命令后,在hello.go
文件的同级目录下生成了hello.a
和hello.h
两个文件,如:
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.dll
和hello.lib
,如:
到此我们完成了dll的生成
在win32程序中使用golang生成的dll
使用vs2015(我机子上安装vs2015,其他的也是可以的)建立win32程序,将上一步生成的两个文件hello.h
和hello.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的显示加载的方法,提前声明了两个函数指针funcPtrSum
和funcPtrShow
,用于获取dll中的函数指针地址。需要注意的是函数指针funcPtrShow
的返回值,在hello.go
中并没有返回值,但是声明时必须有返回值,我这边写了void
,似乎写成GoInt32
也是没错的。
注意编译时的平台,因为我们前面都是在x64下编译生成的dll。然后使用vs内置的编译器直接编译代码,发现报了一大堆错误:
这个错误是因为在生成的hello.h
文件中需要屏蔽三行代码,屏蔽
typedef __SIZE_TYPE__ GoUintptr;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
之后,继续编译,成功~运行生成的可执行程序,如:
和我们在hello.go
代码中的结果期望是一致的。
后续
1、导出包引用了其他的包
当hello.go
引用了别的数据包时,并没有其他的差别,只需要编译最终导出的那个文件即可。
下图中我修改了hello.go
的文件,将具体的导出函数在另一个包comm.go
中实现了,如:
编译时的指令没有变化,内部的引用关系,go build会自己去找,并编译,无需人工干预。
2、为了方便用脚本来自动编译
脚本build-go.bat
生成dll,将脚本文件拷贝到.go
的目录下,在命令行中找到该目录,拖需要编译的文件到命令行中,回车开始编译:
编译过程:
生成了.lib
、.dll
和.h
文件:
@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://blog.csdn.net/rtduq/article/details/80340461
https://github.com/Baozisoftware/go-dll/wiki/%E7%94%9F%E6%88%90Go%E7%89%88DLL
http://www.bubuko.com/infodetail-2295164.html