生成脚本
这个是golang 生成动态库示例的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main
import "C"
//export hello
func hello() {
println("Hello from DLL")
}
//export add
func add(a, b int) int {
println("add a+b")
return a + b
}
func main() {} // 必须存在,但不会被调用
|
linux
系统版本是:linux/amd64
- 64位创建一个
build.sh
文件, 内容如下:
CGO_ENABLED=1 go build -buildmode=c-shared -o test.so main.go
- 32位创建一个
build-32.sh
文件, 内容如下
GOARCH=386 CGO_ENABLED=1 go build -buildmode=c-shared -o test-32.so main.go
C语言调用.so库的示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
typedef void (*HELLO_FUNC)();
typedef int (*ADD_FUNC)(int, int);
int main() {
void *handle = dlopen("test.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
char* error;
dlerror();
HELLO_FUNC hello_func = dlsym(handle, "hello");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
hello_func();
ADD_FUNC add_func = dlsym(handle, "add");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("add(1,2) = %d\n", add_func(1,2));
dlclose(handle);
}
|
编译:gcc -o test test.c -ldl
运行:./test
,结果:
1
2
|
Hello World!
add(1,2) = 3
|
windows
系统版本是:windows/amd64
- 64位 创建一个
build.bat
文件, 内容如下:
1
2
|
set CGO_ENABLED=1
go build -buildmode=c-shared -o test.dll main.go
|
- 32位 创建一个
build-32.bat
文件, 内容如下:
1
2
3
|
set GOARCH=386
set CGO_ENABLED=1
go build -buildmode=c-shared -o test.dll main.go
|
C语言调用.dll库的示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include <windows.h>
#include <stdio.h>
typedef void (*HELLO_FUNC)();
typedef int (*ADD_FUNC)(int, int);
int main() {
HMODULE hModule = LoadLibrary("test.dll");
if (hModule == NULL) {
// 错误处理
return 1;
}
printf("LoadLibrary success.\n");
HELLO_FUNC hello_func = (HELLO_FUNC)GetProcAddress(hModule, "hello");
if (hello_func == NULL) {
// 错误处理
return 1;
}
hello_func();
// printf("Result: %d\n", result);
ADD_FUNC add_func = (ADD_FUNC)GetProcAddress(hModule, "add");
if (add_func == NULL) {
// 错误处理
return 1;
}
int result = add_func(1,2);
printf("result: %d\n",result);
FreeLibrary(hModule);
return 0;
}
|
测试脚本编译方式:
gcc -o test test.c
运行:./test
,结果:
1
2
|
Hello World!
add(1,2) = 3
|
windows加载库卡死
在实际的项目中,发现使用golang调用cgo生成的动态库,有这样的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package main
//#include <string.h>
//#include <stdlib.h>
/*
void __attribute__ ((constructor)) ConfigLoad();
void __attribute__ ((destructor)) ConfUnLoad();
*/
import "C"
//export hello
func hello() {
println("Hello from DLL")
}
//export add
func add(a, b int) int {
println("add a+b")
return a + b
}
//export ConfigLoad
func ConfigLoad() {}
//export ConfUnLoad
func ConfUnLoad() {}
func main() {} // 必须存在,但不会被调用
|
void __attribute__ ((constructor)) ConfigLoad()
和 void __attribute__ ((destructor)) ConfUnLoad()
这两个函数在动态库被加载和卸载时调用,可以用来初始化和清理动态库。
但是在windows
调用生成的test.dll
的库时,会出现服务卡死的情况,卡死的位置在:LoadLibrary
,
但是Linux
环境是没有问题的。
windows
系统在加载动态库时,会调用DllMain
的构造函数,
golang
运行时的初始化(调度器,GC等)会同步执行在一个系统线程,构造函数调用Go代码时,可能触发未完全初始化状态的Go代码,导致死锁。
dll
在加载线程处于受限状态
,不允许创建新线程。
Linux
处理机制不一样,会先完成动态库的初始化,然后再调用构造函数,加载线程处于正常状态
,不会出现死锁。
- 解决办法:
- 在必须加载
ConfigLoad
的条件下,
去掉void __attribute__ ((constructor)) ConfigLoad();
, 改成使用golang 的init
:
1
2
3
|
func init() {
ConfigLoad()
}
|
- 最好避免使用
void __attribute__ ((constructor)) ConfigLoad();
这样的结构,改成主动调用。