golang cgo检查C函数是否存在

时间:2016-08-19 14:23:31

标签: c go cgo

我通过CGO链接一个库,并不是所有的实现或版本都有一个我想在可能的情况下使用的功能 - 即函数int feature(void)的存在。有没有办法在尝试通话之前检查这个符号是否已定义?

任何尝试使用C.feature()都会导致在具有不支持该功能的库版本的系统上出现构建失败。

如果不清楚,我想针对许多平台构建,这些平台可能有也可能没有该功能。我想我要么能够在运行时检查函数是否存在(更理想),要么使用go:generate进行检查并根据找到的内容更改代码(不太理想)。无论哪种方式,我都不太确定如何继续下去。

2 个答案:

答案 0 :(得分:0)

大多数图书馆都附带一个描述其版本的#define

例如,在1.0版中:

// simple.h
#define LIB_SIMPLE_VERSION 0x00010000

void hello();

在1.1版中:

// simple.h
#define LIB_SIMPLE_VERSION 0x00010001

void hello();
void bye();

您有很多选择:

  • 提供IsByeAvailable()功能来检查是否存在
  • 在Go中修改bye()的签名,使其返回errorbool ok
  • 如果实施不存在则恐慌

选择代码1

// simple.go

// #include <simple.h>
import "C"

func IsByeAvailable() bool {
    return C.LIB_SIMPLE_VERSION >= 0x00010001
}

func Hello() {
    C.hello()
}

func Bye() {
    C.bye()
}
// version_support.c
#include <simple.h>
#if LIB_SIMPLE_VERSION < 0x00010001
void bye() { /* Empty */ }
#endif

答案 1 :(得分:0)

在构建程序时,有几种方法可以做到这一点,但这将使您需要始终进行两次构建并维护程序的两个版本,这不方便。在运行时,你是非常有限的,因为C本身不是反射性的,Go运行时功能不会(实际上不能)给你任何好处。

但是,你可以做两个非便携式(但可能是最好的)黑客攻击。而且由于问题更多的是C问题,黑客也更多是C黑客。一个是直接动态链接器接口,即dlopen() / dlsym(),另一个是动态链接器中弱符号的使用。

让我们首先创建一些设置来测试:

$ tree
.
├── lib
│   ├── lib1.c
│   ├── lib1.h
│   └── lib2.c
└── some.go
$ cat lib/lib1.h 
int feature(void);
int fun(int a);
$ cat lib/lib1.c 
int feature(void)
{
        return 5;
}

int fun(int a)
{
        return a*a;
}
$ cat lib/lib2.c 
int fun(int a)
{
        return a*a;
}

这是一个非常简单的库,它声明了两个函数,并且有两个实现,一个有两个,另一个只有一个。建立它们很容易:

$ gcc -shared -o lib/lib1.so.featured lib/lib1.c 
$ gcc -shared -o lib/lib1.so.featureless lib/lib2.c

一个符号链接,可以在两个版本之间轻松切换:

$ ln -s lib1.so.featured lib/lib1.so

因此,对于dlopen() / dlsym(),您创建了一个包装器并使用动态链接器接口来获取这样的feature()指针(是的,您可以在不调用{{1在每次调用时,让我们使用最小的代码):

dlsym()

测试:

package main;

// #cgo LDFLAGS: -Llib -Wl,-rpath lib -l1 -ldl
// #include "lib/lib1.h"
// #include <stddef.h>
// #include <dlfcn.h>
//
// int feature_wrap(void)
// {
//      static void* dlhandle;
//      static int (*featurep)(void);
//
//      if (!dlhandle)
//          dlhandle = dlopen(NULL, RTLD_NOW);
//      if (!dlhandle) // error
//          return 0;
//      featurep = dlsym(dlhandle, "feature");
//      if (featurep)
//          return featurep();
//      else
//          return 3;
//}
import "C"
import "fmt"

func main() {
     r := C.feature_wrap()
     fmt.Println(r)
}

对于弱符号方法(因为它更易于IMO)你需要将$ go build some.go $ ln -sf lib1.so.featured lib/lib1.so $ ./some 5 $ ln -sf lib1.so.featureless lib/lib1.so $ ./some 3 函数重新定义为弱函数,并为它创建一个包装器,它将在两个实现之间提供运行时切换:

feature()

测试是一样的。

显然,一旦你有了package main; // #cgo LDFLAGS: -Llib -Wl,-rpath lib -l1 // #include "lib/lib1.h" // int feature(void) __attribute__((weak)); // int feature_wrap(void) // { // if (feature) // return feature(); // else // return 3; //} import "C" import "fmt" func main() { r := C.feature_wrap() fmt.Println(r) } ,你就可以做任何你需要的东西来代替丢失的if,包括对Go代码的回调。