【问题标题】:golang cgo check if C function existsgolang cgo 检查C函数是否存在
【发布时间】:2016-12-26 18:08:46
【问题描述】:

我正在通过 CGO 链接一个库,但并非所有实现或版本都具有我想尽可能使用的功能 — 即存在 int feature(void) 函数。有没有办法在尝试调用之前检查此符号是否已定义?

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

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

【问题讨论】:

  • 如果你需要类似 shell 命令来检查 lib 符号,在 unix 系统上你可以使用 strings 和你要链接的 lib 的路径。在符号名称上加上 grep,您可以验证它。而且我认为 Windows 也可以。
  • 通过使用类似 c 预处理器指令的东西 #if def 看起来是不可能的,正如提到的here。
  • 如果一切都失败了,你可以更深一层,到dlsym/GetProcAddress。具有讽刺意味的是,每个平台的过程再次不同,但这次只有 Windows 与 Posix,这应该可以通过构建约束进行管理。

标签: c go cgo


【解决方案1】:

大多数库都带有描述其版本的#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() 的签名,使其返回一个 error,或为 ok 返回一个 bool
  • 如果实现不存在会出现恐慌

选择 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

【讨论】:

    【解决方案2】:

    在构建程序时有几种方法可以做到这一点,但这会让您需要始终进行两次构建并维护程序的两个版本,这很不方便。在运行时,您受到的限制非常有限,因为 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() 指针(是的,您可以在每次调用时不调用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)
    }
    

    测试:

    $ go build some.go
    $ ln -sf lib1.so.featured lib/lib1.so
    $ ./some 
    5
    $ ln -sf lib1.so.featureless lib/lib1.so
    $ ./some
    3
    

    对于弱符号方法(IMO 更可取,因为它更简单),您需要将 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,您就可以做任何您需要替换缺少的feature() 的事情,包括对 Go 代码的回调。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-07
      • 2012-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-27
      • 2018-05-22
      相关资源
      最近更新 更多