Featured image of post golang 使用cgo 生成动态库

golang 使用cgo 生成动态库

生成脚本

这个是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

  1. 64位创建一个build.sh文件, 内容如下:

CGO_ENABLED=1 go build -buildmode=c-shared -o test.so main.go

  1. 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

  1. 64位 创建一个build.bat文件, 内容如下:
1
2
set CGO_ENABLED=1
go build -buildmode=c-shared -o test.dll main.go
  1. 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处理机制不一样,会先完成动态库的初始化,然后再调用构造函数,加载线程处于正常状态,不会出现死锁。

  • 解决办法:
    1. 在必须加载ConfigLoad的条件下, 去掉void __attribute__ ((constructor)) ConfigLoad();, 改成使用golang 的init:
    1
    2
    3
    
    func init() {
        ConfigLoad()
    }
    
    1. 最好避免使用void __attribute__ ((constructor)) ConfigLoad();这样的结构,改成主动调用。
本博客已稳定运行
发表了26篇文章 · 总计45.09k字
本站总访问量 次 · 您是本站第 位访问者
粤ICP备2025368587号-1| 使用 Hugo 构建
主题 StackJimmy 设计