帮酷LOGO
  • 显示原文与译文双语对照的内容
Calling Go Functions from Other Languages using C Shared Libraries

  • 源代码名称:go-cshared-examples
  • 源代码网址:http://www.github.com/vladimirvivien/go-cshared-examples
  • go-cshared-examples源代码文档
  • go-cshared-examples源代码下载
  • Git URL:
    git://www.github.com/vladimirvivien/go-cshared-examples.git
  • Git Clone代码到本地:
    git clone http://www.github.com/vladimirvivien/go-cshared-examples
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/vladimirvivien/go-cshared-examples
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
  • 使用C 共享库从其他语言调用函数

    这个respository包含文章 使用 -buildmode=c-shared build的标准共享对象二进制文件(. so ),编译器输出一个标准共享对象二进制文件,公开go函数。 这让程序员可以创建可以从其他语言( 包括 python 。ruby 。node 和 Java ( 参见为Lua提供的示例) ) 调用的go库,就像在这个库。

    代码

    首先,让我们写代码。 假设我们已经编写了一个 awesome 去库,我们想让其他语言可以使用它。 将代码编译到共享库之前,需要遵循以下四个要求:

    • 软件包必须是amain软件包。 编译器将编译包及其所有依赖项到单个共享对象二进制文件中。
    • 源必须导入伪软件包"c"。
    • 使用//export 注释注释你希望访问其他语言的函数。
    • 必须声明空的主函数。

    以下输出源输出 AddCosineSortLog 四个函数。 不可否认的是awesome库并没有那么令人印象深刻。 但是,它的不同功能签名将帮助我们探索类型映射。

    文件 awesome.go

    package mainimport"C"import (
     "fmt""math""sort""sync")varcountintvarmtx sync.Mutex//export AddfuncAdd(a, bint) int {
     return a + b
    }//export CosinefuncCosine(xfloat64) float64 {
     return math.Cos(x)
    }//export SortfuncSort(vals []int) {
     sort.Ints(vals)
    }//export LogfuncLog(msgstring) int {
     mtx.Lock()
     defer mtx.Unlock()
     fmt.Println(msg)
     count++
     return count
    }funcmain() {}

    使用 -buildmode=c-shared 生成标志编译包,以创建共享对象二进制文件:

    go build -o awesome.so -buildmode=c-shared awesome.go

    完成后,编译器输出两个文件: awesome.h,一个C 头文件和 awesome.so,共享目标文件,如下所示:

    -rw-rw-r - 1362 Feb 11 07:59 awesome.h
    -rw-rw-r - 1997880 Feb 11 07:59 awesome.so

    注意,.so 文件大约是 2 Mb,对于这样一个小型库来说,它是相当大的。 这是因为整个运行时机器和依赖包被填充到单个共享对象二进制( 与编译单个 static 可执行文件类似) 中。

    头文件

    头文件定义映射到兼容类型的C 类型,使用cgo语义。

    /* Created by"go tool cgo" - DO NOT EDIT. */...typedefsignedchar GoInt8;typedefunsignedchar GoUint8;typedefshort GoInt16;typedefunsignedshort GoUint16;typedefint GoInt32;typedefunsignedint GoUint32;typedeflonglong GoInt64;typedefunsignedlonglong GoUint64;typedef GoInt64 GoInt;typedef GoUint64 GoUint;typedef __SIZE_TYPE__ GoUintptr;typedeffloat GoFloat32;typedefdouble GoFloat64;typedeffloat_Complex GoComplex64;typedefdouble_Complex GoComplex128;/* static assertion to make sure the file is being used on architecture at least with matching size of GoInt.*/typedefchar _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8? 1:-1];typedefstruct { constchar *p; GoInt n; } GoString;typedefvoid *GoMap;typedefvoid *GoChan;typedefstruct { void *t; void *v; } GoInterface;typedefstruct { void *data; GoInt len; GoInt cap; } GoSlice;
    #endif/* End of boilerplate cgo prologue. */#ifdef __cplusplusextern"C" {
    #endifextern GoInt Add(GoInt p0, GoInt p1);extern GoFloat64 Cosine(GoFloat64 p0);externvoidSort(GoSlice p0);extern GoInt Log(GoString p0);
    #ifdef __cplusplus
    }
    #endif

    共享目标文件

    编译器生成的其他文件是一个 64位 ELF共享对象二进制文件。 我们可以使用 file 命令来验证它的信息。

    $> file awesome.so
    awesome.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=1fcf29a2779a335371f17219fffbdc47b2ed378a, not stripped

    我们可以使用 nmgrep 命令确保在共享目标文件中导出的go函数。

    $> nm awesome.so | grep -e "T Add" -e "T Cosine" -e "T Sort" -e "T Log"00000000000d0db0 T Add
    00000000000d0e30 T Cosine
    00000000000d0f30 T Log
    00000000000d0eb0 T Sort

    来自

    使用共享对象库从调用转到函数有两种方法。 首先,我们可以在编译时静态绑定共享库,但是在运行时动态链接它。 或者,在运行时动态加载和绑定goto函数符号。

    动态链接

    这种方法使用头文件来静态引用共享对象文件中导出的类型和函数。 代码简单且干净,如下所示:

    文件 client1.c

    #include<stdio.h>#include"awesome.h"intmain() {
     printf("Using awesome lib from C:n");
     //Call Add() - passing integer params, interger result GoInt a = 12;
     GoInt b = 99;
     printf("awesome.Add(12,99) = %dn", Add(a, b)); 
     //Call Cosine() - passing float param, float returnedprintf("awesome.Cosine(1) = %fn", (float)(Cosine(1.0)));
     //Call Sort() - passing an array pointer GoInt data[6] = {77, 12, 5, 99, 28, 23};
     GoSlice nums = {data, 6, 6};
     Sort(nums);
     printf("awesome.Sort(77,12,5,99,28,23): ");
     for (int i = 0; i <6; i++){
     printf("%d,", ((GoInt *)nums.data)[i]);
     }
     printf("n");
     //Call Log() - passing string value GoString msg = {"Hello from C!", 13};
     Log(msg);
    }

    接下来,编译C 代码,指定共享对象库:

    $> gcc -o client client1.c./awesome.so

    执行生成的二进制文件时,将链接到 awesome.so 库,调用从go导出的函数,如下所示。

    $>./client
    awesome.Add(12,99) = 111
    awesome.Cosine(1) = 0.540302
    awesome.Sort(77,12,5,99,28,23): 5,12,23,28,77,99,
    Hello from C!

    动态加载

    这种方法使用动态链接加载器库( libdl.so ) 来动态加载和绑定导出的符号。 它使用在 dhfcn.h 中定义的函数,如 dlopen 打开库文件,dlsym 查找符号,dlerror 检索错误,dlclose 关闭共享库文件。

    因为绑定和链接是在源代码中完成的,所以这个版本是 lengthier 。 但是,它与以前的事情相同,如下面的代码片段( 省略了一些打印语句和错误处理) 所示。

    文件 client2.c

    #include<stdlib.h>#include<stdio.h>#include<dlfcn.h>// define types neededtypedeflonglong go_int;typedefdouble go_float64;typedefstruct{void *arr; go_int len; go_int cap;} go_slice;typedefstruct{constchar *p; go_int len;} go_str;intmain(int argc, char **argv) {
     void *handle;
     char *error;
     // use dlopen to load shared object handle = dlopen ("./awesome.so", RTLD_LAZY);
     if (!handle) {
     fputs (dlerror(), stderr);
     exit(1);
     }
     // resolve Add symbol and assign to fn ptrgo_int (*add)(go_int, go_int) = dlsym(handle, "Add");
     if ((error = dlerror())!= NULL) {
     fputs(error, stderr);
     exit(1);
     }
     // call Add() go_int sum = (*add)(12, 99); 
     printf("awesome.Add(12, 99) = %dn", sum);
     // resolve Cosine symbolgo_float64 (*cosine)(go_float64) = dlsym(handle, "Cosine");
     if ((error = dlerror())!= NULL) {
     fputs(error, stderr);
     exit(1);
     }
     // Call Cosine go_float64 cos = (*cosine)(1.0);
     printf("awesome.Cosine(1) = %fn", cos);
     // resolve Sort symbolvoid (*sort)(go_slice) = dlsym(handle, "Sort");
     if ((error = dlerror())!= NULL) {
     fputs(error, stderr);
     exit(1);
     }
     // call Sort go_int data[5] = {44,23,7,66,2};
     go_slice nums = {data, 5, 5};
     sort(nums);
     printf("awesome.Sort(44,23,7,66,2): ");
     for (int i = 0; i <5; i++){
     printf("%d,", ((go_int *)data)[i]);
     }
     printf("n");
     // resolve Log symbolgo_int (*log)(go_str) = dlsym(handle, "Log");
     if ((error = dlerror())!= NULL) {
     fputs(error, stderr);
     exit(1);
     }
     // call Log go_str msg = {"Hello from C!", 13};
     log(msg);
     // close file handle when donedlclose(handle);
    }

    在前面的代码中,我们定义了自己的go兼容C 类型 go_intgo_floatgo_slicego_str 。 我们使用 dlsym 加载符号 AddCosineSortLog,并将它们分配给各自的函数指针。 接下来,我们编译与 dl 库( 不是 awesome.so ) 相关的代码,如下所示:

    $> gcc -o client client2.c -ldl

    执行代码时,C 二进制加载和指向共享库 awesome.so的链接将生成以下输出:

    $>./client
    awesome.Add(12, 99) = 111
    awesome.Cosine(1) = 0.540302
    awesome.Sort(44,23,7,66,2): 2,7,23,44,66,
    Hello from C!

    来自 python的

    在 python 中,事情变得简单一点。 我们使用 ctypes外部函数库从 awesome.so 共享库调用go函数,如下面的Fragment ( 省略某些打印语句) 所示。

    文件 client.py

    from ctypes import*lib = cdll.LoadLibrary("./awesome.so")# describe and invoke Add()lib.Add.argtypes = [c_longlong, c_longlong]
    lib.Add.restype = c_longlongprint"awesome.Add(12,99) = %d"% lib.Add(12,99)# describe and invoke Cosine()lib.Cosine.argtypes = [c_double]
    lib.Cosine.restype = c_doubleprint"awesome.Cosine(1) = %f"% lib.Cosine(1)# define class GoSlice to map to:# C type struct { void *data; GoInt len; GoInt cap; }classGoSlice(Structure):
     _fields_ = [("data", POINTER(c_void_p)), ("len", c_longlong), ("cap", c_longlong)]
    nums = GoSlice((c_void_p *5)(74, 4, 122, 9, 12), 5, 5) # call Sortlib.Sort.argtypes = [GoSlice]
    lib.Sort.restype =Nonelib.Sort(nums)print"awesome.Sort(74,4,122,9,12) = [",for i inrange(nums.len):
     print"%d"% nums.data[i],print"]"# define class GoString to map:# C type struct { const char *p; GoInt n; }classGoString(Structure):
     _fields_ = [("p", c_char_p), ("n", c_longlong)]# describe and call Log()lib.Log.argtypes = [GoString]
    lib.Log.restype = c_longlong
    msg = GoString(b"Hello Python!", 13)print"log id %d"% lib.Log(msg)

    注意 lib 变量代表共享对象文件中已经加载的符号。 我们还定义了 python 类 GoStringGoSlice 映射到它们各自的C 结构类型。 执行 python 代码时,它会调用生成以下输出的共享对象中的go函数:

    $> python client.py
    awesome.Add(12,99) = 111
    awesome.Cosine(1) = 0.540302
    awesome.Sort(74,4,122,9,12) = [ 4 9 12 74 122 ]
    Hello Python!

    python CFFI ( 贡献)

    以下示例由 @sbinet 提供( 谢谢) !

    python 还有一个可以移植的CFFI库,可以与 Python2/Python3/pypy 一起工作。 下面的示例使用一个c 包装来定义导出的goto类型。 这使得 python 示例不透明,更容易理解。

    文件 client-cffi.py

    from __future__ import print_function
    import sys
    from cffi import FFI
    is_64b = sys.maxsize> 2**32
    ffi = FFI()
    if is_64b: ffi.cdef("typedef long GoInt;n")
    else: ffi.cdef("typedef int GoInt;n")
    ffi.cdef("""
    typedef struct {
     void* data;
     GoInt len;
     GoInt cap;
    } GoSlice;
    typedef struct {
     const char *data;
     GoInt len;
    } GoString;
    GoInt Add(GoInt a, GoInt b);
    double Cosine(double v);
    void Sort(GoSlice values);
    GoInt Log(GoString str);
    """)
    lib = ffi.dlopen("./awesome.so")
    print("awesome.Add(12,99) = %d" % lib.Add(12,99))
    print("awesome.Cosine(1) = %f" % lib.Cosine(1))
    data = ffi.new("GoInt[]", [74,4,122,9,12])
    nums = ffi.new("GoSlice*", {'data':data, 'len':5, 'cap':5})
    lib.Sort(nums[0])
    print("awesome.Sort(74,4,122,9,12) = %s" % [
     ffi.cast("GoInt*", nums.data)[i] 
     for i in range(nums.len)])
    data = ffi.new("char[]", b"Hello Python!")
    msg = ffi.new("GoString*", {'data':data, 'len':13})
    print("log id %d" % lib.Log(msg[0]))

    来自 ruby的

    从 ruby 调用go函数follows类似于上面的Pattern 。 在 awesome.so 共享目标文件中,我们使用 FFI gem 动态加载和调用导出的go函数,如下面的Fragment所示。

    文件 client.rb

    require'ffi'# Module that represents shared libmoduleAwesomeextendFFI::Library ffi_lib './awesome.so'# define class GoSlice to map to:# C type struct { void *data; GoInt len; GoInt cap; }classGoSlice <FFI::Struct layout :data, :pointer,
     :len, :long_long,
     :cap, :long_longend# define class GoString to map:# C type struct { const char *p; GoInt n; }classGoString <FFI::Struct layout :p, :pointer,
     :len, :long_longend# foreign function definitions attach_function :Add, [:long_long, :long_long], :long_long attach_function :Cosine, [:double], :double attach_function :Sort, [GoSlice.by_value], :void attach_function :Log, [GoString.by_value], :intend# Call Addprint"awesome.Add(12, 99) = ", Awesome.Add(12, 99), "n"# Call Cosineprint"awesome.Cosine(1) = ", Awesome.Cosine(1), "n"# call Sortnums = [92,101,3,44,7]
    ptr =FFI::MemoryPointer.new:long_long, nums.size
    ptr.write_array_of_long_long nums
    slice =Awesome::GoSlice.newslice[:data] = ptr
    slice[:len] = nums.size
    slice[:cap] = nums.sizeAwesome.Sort(slice)
    sorted = slice[:data].read_array_of_long_long nums.sizeprint"awesome.Sort(", nums, ") = ", sorted, "n"# Call Logmsg ="Hello Ruby!"gostr =Awesome::GoString.newgostr[:p] =FFI::MemoryPointer.from_string(msg) 
    gostr[:len] = msg.sizeprint"logid ", Awesome.Log(gostr), "n"

    为了声明从共享库加载的符号,我们必须在 ruby 中扩展 FFI MODULE 。 我们使用 ruby 类 GoSliceGoString 来映射相应的C 结构。 运行代码时,它会调用导出的转到函数,如下所示:

    $> ruby client.rb
    awesome.Add(12, 99) = 111
    awesome.Cosine(1) = 0.5403023058681398
    awesome.Sort([92, 101, 3, 44, 7]) = [3, 7, 44, 92, 101]
    Hello Ruby!

    来自 node的

    对于 node,我们使用名为节点ffi的外部函数库,在 awesome.so 共享目标文件中动态加载和调用导出的函数,如以下代码段所示:

    文件 client.js

    var ref =require("ref");var ffi =require("ffi");var Struct =require("ref-struct")var ArrayType =require("ref-array")var longlong =ref.types.longlong;var LongArray =ArrayType(longlong);// define object GoSlice to map to:// C type struct { void *data; GoInt len; GoInt cap; }var GoSlice =Struct({
     data: LongArray,
     len:"longlong",
     cap:"longlong"});// define object GoString to map:// C type struct { const char *p; GoInt n; }var GoString =Struct({
     p:"string",
     n:"longlong"});// define foreign functionsvar awesome =ffi.Library("./awesome.so", {
     Add: ["longlong", ["longlong", "longlong"]],
     Cosine: ["double", ["double"]], 
     Sort: ["void", [GoSlice]],
     Log: ["longlong", [GoString]]
    });// call Addconsole.log("awesome.Add(12, 99) = ", awesome.Add(12, 99));// call Cosineconsole.log("awesome.Cosine(1) = ", awesome.Cosine(1));// call Sortnums =LongArray([12,54,0,423,9]);var slice =newGoSlice();
    slice["data"] = nums;
    slice["len"] =5;
    slice["cap"] =5;awesome.Sort(slice);console.log("awesome.Sort([12,54,9,423,9] = ", nums.toArray());// call Logstr =newGoString();
    str["p"] ="Hello Node!";
    str["n"] =11;awesome.Log(str);

    node 使用 FFI 对象从共享库中声明已经加载的符号。 我们还使用 node 结构对象 GoSliceGoString 映射到它们各自的C 结构。 运行代码时,它会调用导出的转到函数,如下所示:

    awesome.Add(12, 99) = 111
    awesome.Cosine(1) = 0.5403023058681398
    awesome.Sort([12,54,9,423,9] = [ 0, 9, 12, 54, 423 ]
    Hello Node!

    来自Java的插件

    要从Java调用导出功能,我们使用本机访问库或者 JNA,如下面的代码段( 省略或者省略某些语句) 所示:

    文件 Client.java

    importcom.sun.jna.*;importjava.util.*;importjava.lang.Long;publicclassClient {
     publicinterfaceAwesomeextendsLibrary {
     // GoSlice class maps to:// C type struct { void *data; GoInt len; GoInt cap; }publicclassGoSliceextendsStructure {
     publicstaticclassByValueextendsGoSliceimplementsStructure.ByValue {}
     publicPointer data;
     publiclong len;
     publiclong cap;
     protectedListgetFieldOrder(){
     returnArrays.asList(newString[]{"data","len","cap"});
     }
     }
     // GoString class maps to:// C type struct { const char *p; GoInt n; }publicclassGoStringextendsStructure {
     publicstaticclassByValueextendsGoStringimplementsStructure.ByValue {}
     publicString p;
     publiclong n;
     protectedListgetFieldOrder(){
     returnArrays.asList(newString[]{"p","n"});
     }
     }
     // Foreign functionspubliclongAdd(longa, longb);
     publicdoubleCosine(doubleval);
     publicvoidSort(GoSlice.ByValuevals);
     publiclongLog(GoString.ByValuestr);
     }
     staticpublicvoidmain(Stringargv[]) {
     Awesome awesome = (Awesome) Native.loadLibrary(
     "./awesome.so", Awesome.class);
     System.out.printf("awesome.Add(12, 99) = %sn", awesome.Add(12, 99));
     System.out.printf("awesome.Cosine(1.0) = %sn", awesome.Cosine(1.0));
     // Call Sort// First, prepare data array long[] nums =newlong[]{53,11,5,2,88};
     Memory arr =newMemory(nums.length *Native.getNativeSize(Long.TYPE));
     arr.write(0, nums, 0, nums.length); 
     // fill in the GoSlice class for type mappingAwesome.GoSlice.ByValue slice =newAwesome.GoSlice.ByValue();
     slice.data = arr;
     slice.len = nums.length;
     slice.cap = nums.length;
     awesome.Sort(slice);
     System.out.print("awesome.Sort(53,11,5,2,88) = [");
     long[] sorted = slice.data.getLongArray(0,nums.length);
     for(int i =0; i < sorted.length; i++){
     System.out.print(sorted[i] +"");
     }
     System.out.println("]");
     // Call LogAwesome.GoString.ByValue str =newAwesome.GoString.ByValue();
     str.p ="Hello Java!";
     str.n = str.p.length();
     System.out.printf("msgid %dn", awesome.Log(str));
     }
    }

    为了使用 JNA,我们定义了Java接口 awesome 来表示从 awesome.so 共享库文件加载的符号。 我们还声明类 GoSliceGoString 映射到它们各自的C 结构表示。 编译和运行代码时,它会调用导出的go函数,如下所示:

    $> javac -cp jna.jar Client.java
    $> java -cp. :jna.jar Client
    awesome.Add(12, 99) = 111
    awesome.Cosine(1.0) = 0.5403023058681398
    awesome.Sort(53,11,5,2,88) = [2 5 11 53 88 ]
    Hello Java!

    来自Lua的 ( 贡献)

    这里示例由 @titpetric 提供。 see他在上的insightful写functions从LUA调用go函数

    forllowing展示了如何从Lua调用导出的go函数。 以前一样,它使用FFI库动态加载共享目标文件并绑定到导出的函数符号。

    文件 client.lua

    local ffi =require("ffi")local awesome = ffi.load("./awesome.so")
    ffi.cdef([[typedef long long GoInt64;typedef unsigned long long GoUint64;typedef GoInt64 GoInt;typedef GoUint64 GoUint;typedef double GoFloat64;typedef struct { const char *p; GoInt n; } GoString;typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;extern GoInt Add(GoInt p0, GoInt p1);extern GoFloat64 Cosine(GoFloat64 p0);extern void Sort(GoSlice p0);extern GoInt Log(GoString p0);]]);io.write( string.format("awesome.Add(12, 99) = %fn", math.floor(tonumber(awesome.Add(12,99)))) )io.write( string.format("awesome.Cosine(1) = %fn", tonumber(awesome.Cosine(1))) )local nums = ffi.new("long long[5]", {12,54,0,423,9})local numsPointer = ffi.new("void *", nums);local typeSlice = ffi.metatype("GoSlice", {})local slice =typeSlice(numsPointer, 5, 5)
    awesome.Sort(slice)io.write("awesome.Sort([12,54,9,423,9] = ")for i=0,4doif i >0thenio.write(", ")
     endio.write(tonumber(nums[i]))endio.write("n");local typeString = ffi.metatype("GoString", {})local logString =typeString("Hello LUA!", 10)
    awesome.Log(logString)

    执行该示例时,将产生以下结果:

    $> luajit client.lua
    awesome.Add(12, 99) = 111.000000
    awesome.Cosine(1) = 0.540302
    awesome.Sort([12,54,9,423,9] = 0, 9, 12, 54, 423
    Hello LUA!

    来自 Julia ( 贡献者)的

    下面的示例由 @r9y9 提供。 它展示了如何从Julia语言调用导出的go函数。 作为这里的文档,Julia有能力从共享库调用导出函数,类似于这里讨论的其他语言。

    文件 client.jl

    struct GoSlice
     arr::Ptr{Void} len::Int64 cap::Int64endGoSlice(a::Vector, cap=10) =GoSlice(pointer(a), length(a), cap)struct GoStr
     p::Ptr{Cchar} len::Int64endGoStr(s::String) =GoStr(pointer(s), length(s))const libawesome ="awesome.so"Add(x,y) =ccall((:Add, libawesome), Int,(Int,Int), x,y)Cosine(x) =ccall((:Cosine, libawesome), Float64, (Float64,), x)functionSort(vals)
     ccall((:Sort, libawesome), Void, (GoSlice,), GoSlice(vals))
     return vals # for convenienceendLog(msg) =ccall((:Log, libawesome), Int, (GoStr,), GoStr(msg))for ex in [:(Add(12, 9)),:(Cosine(1)), :(Sort([77,12,5,99,28,23]))]
     println("awesome.$ex = $(eval(ex))")endLog("Hello from Julia!")

    执行该示例时,将产生以下结果:

    > julia client.jl
    awesome.Add(12, 9) = 21
    awesome.Cosine(1) = 0.5403023058681398
    awesome.Sort([77, 12, 5, 99, 28, 23]) = [5, 12, 23, 28, 77, 99]
    Hello from Julia!

    结论

    这个 repo 展示了如何创建一个可以从C 。python 。ruby 。node 。Java 。Lua 。Julia中使用的go库。 通过将of编译为c 样式共享库,of程序员有一种强大的方式来集成支持动态加载和链接的现代语言。




    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语