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