一个 Cgo 调用 C 函数指针的方法

696 阅读1分钟

通过 Cgo 可以从 Go 端访问 C 函数指针,但不能调用。这两天写 Go 代码玩,尝试用 Cgo 调用 OpenGL API,遇到一个需要调用大量函数指针的情形。在 OpenGL 中相当数量的函数仅定义原型,实现与否则取决于图形驱动程序,需要在运行时获取其地址,即函数指针。下面的glGenBuffers就是这样一个函数:

GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);

在 Go 代码中,C.glGenBuffers 是一个函数指针,不可直接调用。如果编译像下面这样的一个封装函数,在第 5 行代码处会报错,提示C.glGenBuffers不可作为函数调用:

func GlGenBuffers(n int, buffers []uint32) {
	var buffers_c = C.malloc(C.ulonglong(n * C.sizeof_GLuint))
	defer C.free(buffers_c)

	C.glGenBuffers(C.GLsizei(n), (*C.GLuint)(buffers_c))

	var p = uintptr(buffers_c)
	for i := 0; i < n; i++ {
		var p1 = (*C.GLuint)(unsafe.Pointer(p))
		buffers[i] = uint32(*p1)
		p += uintptr(C.sizeof_GLuint)
	} // for
}

解决此一问题的常规做法是,在 C 端定义一个封装函数,该函数调用C.glGenBuffers;而 Go 端的封装函数则调用 C 端的封装函数。也就是说,对于每一个待调用的函数指针,需定义一对封装函数,在 C 及 Go 端各一个。对于像 OpenGL 这样,存在数量庞大的函数指针时,写封装代码会成为一件非常繁琐的工作。

我把上面所说的这种方式稍微作了一点变通,对每一种函数签名(返回值+参数类型)定义一个 C 端封装函数。基于这一事实,即:函数签名的数量远远小于函数的数量,这样做,能够以有限规模的 C 端封装代码,适应/应对数量上高一个数量级的函数指针。

具体地,在一个 C 头文件中定义static inline的封装函数,例如:

// fn_bridge.h

#include <GL/gl.h>

// GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);
static inline void fn_bridge_void_GLsizei_GLuint_p(void (*fn)(GLsizei, GLuint *), GLsizei a1, GLuint *a2)
{
    fn(a1, a2);
}

进而,Cgo 封装代码对 C 封装函数进行调用,像这样:

...
// #include "fn_bridge.h"
// ...
import "C"

func GlGenBuffers(n int, buffers []uint32) {
	...
	C.fn_bridge_void_GLsizei_GLuint_p(C.glGenBuffers, C.GLsizei(n), (*C.GLuint)(buffers_c))
	...
}

La fin.