在Rust中调用Go动态库

2020/07/20 posted in  Rust
Tags: 

最近在研究Rust和Go语言如何相互调用, 这样可以发挥两个语言各自的优势, 用Go语言能够进行快速的开发, 用Rust语言可以提升程序的性能和安全性

编译Go动态库

package main

import "C"
import "fmt"

//export Hello
func Hello(str string) {
    fmt.Printf("Hello: %s!\n", str)
    fmt.Printf("Hello: %#v!\n", str)
}

func main() {}

在编写Go动态库时需要

  1. 引入C
  2. 保留一个空的main函数
  3. 在函数的上一行需要用//export 函数名的形式指定需要导出的函数名

我们使用go build命令编译上面的源码

go build -buildmode=c-shared -o /Data/Rust/learn4libloading/src/plugins/libhello.so hello.go

我直接编译到Rust代码的目录中了
这样, 我们在编译的目录中会多出两个文件libhello.so文件和libhello.h文件
libhello.so就是动态库了, 而libhello.hC的头文件
我们可以看一下libhello.h的内容

/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package command-line-arguments */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */




/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif


extern void Hello(GoString p0);

#ifdef __cplusplus
}
#endif

这其中对我们来说最重要的内容就是Go中的类型在C中是如何定义的, 只有知道这些信息, 我们才能在Rust中调用相应的类型构建Go动态库中需要用到的类型

使用Rust调用Go动态库

Rust代码中我用到了一个crate叫做libloading

extern crate libloading as lib;
use std::ffi::CString;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GoString {
    pub p: *const ::std::os::raw::c_char,
    pub n: isize,
}

fn callGolang(go_name: GoString) -> Result<u32, Box<dyn std::error::Error>> {
    let lib = lib::Library::new("/Data/Rust/learn4libloading/src/plugins/libhello.so")?;
    unsafe {
        let func: lib::Symbol<unsafe extern fn(GoString)> = lib.get(b"Hello")?;
        func(go_name);
        Ok(0)
    }
}

fn main() {
    let c_name = CString::new("Alex").unwrap();

    let go_str_ref = GoString {
        p: c_name.as_ptr(),
        n: c_name.as_bytes().len() as isize,
    };

    let _ = callGolang(go_str_ref);
}

其中的关键是, 先定义了一个GoString结构体, 对应Go语言中的string类型.
然后用libloading加载动态库, 获取我们需要的函数, 再将对应的GoString类型参数传递过去.