C语言调用Go生成的动态库中的函数

本文介绍了如何在Go语言中调用C函数以及反过来让C调用Go的代码示例。通过生成动态库和头文件,展示了GoSlice在C语言中的表示方式,并提到了C语言调用Go函数时需要注意的细节,如GoString的使用和资源清理。同时,讨论了Go的map类型在C中无法直接使用的限制及其可能的解决方案。
摘要由CSDN通过智能技术生成

工作中遇到的Go语言调用C函数的场景比较多,之前也写过一篇《cgo中将C函数返回的数组转为Go中的slice》。

目前在开发OpenSIPS的过程中,有些功能用C写起来麻烦,故第一次尝试了用C调用Go。

首先用Go实现功能,示例代码如下

package main

import "C"

import (
	"fmt"
	"sync"
	"time"
)

var notifyChan chan struct{} = make(chan struct{})
var wg sync.WaitGroup

//export Test
func Test(strs []string) string {
	for _, v := range strs {
		fmt.Println(v)
	}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(idx int) {
			defer func() {
				fmt.Printf("work %d cleaning\n",idx)
				wg.Done()
			}()
			for {
				select {
				case <-notifyChan:
					fmt.Printf("work %d should exit\n",idx)
					return
				default:
				}
				fmt.Printf("work %d working\n",idx)
				time.Sleep(time.Second)
			}
		}(i)
	}

	return "OK"
}

//export Close
func Close() {
	close(notifyChan)
	wg.Wait()
}

//export Test2
func Test2(input map[string]string) map[string]string {
	return nil
}

func main() {

}

用命令生成动态库和头文件:

go build -o libfunc.so -buildmode=c-shared func.go

有几个注意点:

1. 必须是package main并且包含main函数,否则go build会报错

2. main函数里即使有代码段,也不会执行,所以为空即可

3. "import C" 不要忘记,否则虽然能生成动态库,但里面不会有导出符号,头文件也不会生成

4. export和"//"之间不能有空格

生成的头文件:

/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package command-line-arguments */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */




/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif

extern GoString Test(GoSlice strs);
extern void Close();
extern GoMap Test2(GoMap input);

#ifdef __cplusplus
}
#endif

可以看到,Go函数的切片在头文件中是GoSlice类型,包含指向实际数据的指针、数据的长度和slice的容量。切片中元素的类型是GoString,包含一个指向字符串地址的指针和字符串的长度。

注意:GoString是返回类型的时候,const char* p指向的字符串不是以尾0结束的,需要根据长度n自己拷贝使用!

C语言调用的示例代码如下

#include "stdio.h"
#include "memory.h"
#include "stdlib.h"
#include "malloc.h"
#include "string.h"
#include "libfunc.h"
int main()
{
    GoString sa[2] = {{"hello",strlen("hello")},{"world",strlen("world")}};
    GoSlice strs;
    strs.data = sa;
    strs.len =  2;
    strs.cap = 2;
    GoString ret = Test(strs);
    printf("ret(%d) is: %.*s\n",ret.n,ret.n,ret.p);
    //printf("ret: %s\n",ret.p);
    sleep(10);
    Close();
    printf("exit.\n");
    return 0;
}

可以看到打印Test返回结果的时候,要用"%.*s"打印,用"%s"的话会打出来一些古怪的东西。

有两点说明的地方:

1. C语言main退出后,会直接结束掉Go里的所有线程,defer得不到执行(这其实在go里也是一样),一般可以设计一个Close函数做清理工作。

2. 对于Go的map类型,头文件中是void*,是没办法在C中构造一个Go的map传给动态库的,也没法从动态库获取一个Go的map。有一些workaround,比如把map转为json字符串,或者传递两个数组分别为key和value。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值