浅谈go中cgo的几种使用方式

目录
  • 最简单的CGO程序
  • 源码方式调用C函数
  • 内部机制
  • 编译和链接参数
    • 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS
    • 链接参数:LDFLAGS
  • 通过静态库的方式调用C函数
  • 通过动态库的方式调用C函数

最简单的CGO程序

//cgo.go
package main

import "C"

func main(){
    println("hello cgo")
}

上述代码是一个完整的CGO程序,通过import "C"语句启动了CGO特性,go build命令会在编译和链接阶段启动gcc编译器

源码方式调用C函数

cgoTest.h

void SayHello(const char* s);

cgoTest.c

#include <stdio.h>
#include "cgoTest.h"

void SayHello(const char* s) {
    puts(s);
}

main.go

package main

/*
#include <cgoTest.h>
 */
import "C"

func main(){
    C.SayHello(C.CString("Hello world\n"))
}

上述.c文件也可以是.cpp文件,前提是编译时需要g++

cgoTest.cpp

#include <iostream>

extern "C" {
    #include "cgo01.h"
}

void SayHello(const char* s) {
    std::cout << s;
}

上述.c和.cpp的不同实现都实现了SayHello函数,说明解放了函数的实现者,那如果是这种情况,可不可以使用go实现SayHello函数呢?

答案是可以的,这种技术也称为面向C语言接口(.h中的接口声明)的编程技术,该技术不仅仅可以解放函数的实现者,同时也可以简化函数的使用者。

cgoTest.go

package main

import "C"

import "fmt"

//export SayHello
func SayHello(s *C.char){
    fmt.Print(C.GoString(s))  //注意:这里是C.GoString
}

注意:上述main.go文件在使用C函数CString后在程序退出前没有释放C.CString创建的字符串会导致内存泄漏,但是对于这个小程序来说,这样是没有问题的,因为程序推出后操作系统会自动回收程序的所有资源

改进后的main.go代码

package main

/*
#include <cgoTest.h>
#include <stdlib.h>
 */
import "C"
import "unsafe"

func main(){
    cs := C.CString("CPP Hello world\n")
    C.SayHello(cs)
    C.free(unsafe.Pointer(cs))
}

当然也有其他方法可以避免这种麻烦的情况出现,而且只需要一个go文件就可以实现面向C语言的编程

main.go (只有这一个文件)

//+build go1.10
package main

//void SayHello(_GoString_ s); //Go1.10中CGO新增的预定义C语言类型,用来表示Go语言字符串
import "C"
import "fmt"

//export SayHello
func SayHello(s string){ //注意这里变量类型为Go 中的string
    fmt.Print(s)
}
func main(){
    C.SayHello("Hello CGO\n")
}

上面代码执行时先从Go语言的main函数开始,到CGO自动生成的C语言版本SayHello桥接函数,最后到Go语言环境的SayHello函数,是不是有一种合久必分、分久必合的感觉,这也是CGO编程的精华所在。

内部机制

如果在一个go文件中出现了import "C" 指令则表示将调用cgo命令生成的对应的中间文件,下图是cgo生成的中间文件的示意图:

在保证go build 没问题的情况下执行如下命令就可以生成中间文件

go tool cgo main.go

生成的中间文件在_obj目录下

为了在C语言中使用Go语言定义的函数,我们需要将Go代码编译为一个C静态库

go build -buildmode=c-archive -o SayHello.a  cgoTest.go

如果没有错误的话,会生成一个SayHello.a静态库和SayHello.h头文件

既然提到了静态库的生成,顺便也说一下Go生成C动态库

go build -buildmode=c-shared -o SayHello.so cgoTest.go

编译和链接参数

编译和链接参数是每一个C/C++程序员需要经常面对的问题。构建每一个C/C++应用均需要经过编译和链接两个步骤,CGO也是如此

编译参数:CFLAGS/CPPFLAGS/CXXFLAGS

编译参数主要是头文件的检索路径,预定义的宏等参数。理论上来说C和C++是完全独立的两个编程语言,它们可以有着自己独立的编译参数。 但是因为C++语言对C语言做了深度兼容,甚至可以将C++理解为C语言的超集,因此C和C++语言之间又会共享很多编译参数。 因此CGO提供了CFLAGS/CPPFLAGS/CXXFLAGS三种参数,其中CFLAGS对应C语言编译参数(以.c后缀名)、 CPPFLAGS对应C/C++ 代码编译参数(.c,.cc,.cpp,.cxx)、CXXFLAGS对应纯C++编译参数(.cc,.cpp,*.cxx)

链接参数:LDFLAGS

链接参数主要包含要链接库的检索目录和要链接库的名字。因为历史遗留问题,链接库不支持相对路径,我们必须为链接库指定绝对路径。 cgo 中的 ${SRCDIR} 为当前目录的绝对路径。经过编译后的C和C++目标文件格式是一样的,因此LDFLAGS对应C/C++共同的链接参数

CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在import "C"之前的注释部分包含C代码,或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似,都是通过在LDFLAGS选项指定要链接的库方式链接

通过静态库的方式调用C函数

如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式,但很多时候我们并没有源代码,或者从C/C++源代码开始构建的过程异常复杂,这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现了符号冲突则会导致链接的失败

假设dirname 下有filename.c文件和filename.h文件,则生成静态库的命令为

$ cd ./dirname
$ gcc -c -o filename.o filename.c
$ ar rcs libfilename.a filename.o

使用静态库中的C函数

package main

//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"

func main() {
    fmt.Println(C.filename_func())
}

通过动态库的方式调用C函数

动态库出现的初衷是对于相同的库,多个进程可以共享同一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢?从库开发角度来说,动态库可以隔离不同动态库之间的关系,减少链接时出现符号冲突的风险。而且对于windows等平台,动态库是跨越VC和GCC不同编译器平台的唯一的可行方式

动态库的生成

gcc -shared -o libfinename.so filename.c

对于CGO 来说,使用动态库和静态库是一样的

package main

//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"

func main() {
    fmt.Println(C.filename_func())
}

到此这篇关于浅谈go中cgo的几种使用方式的文章就介绍到这了,更多相关go cgo使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言中CGO的使用实践

    目录 1. Go语言调用C函数例子: 2. Go语言调用C库函数: 3. Go语言导出函数给C语言使用: 4. Go语言导出函数指针给c语言使用: 部门产品业务功能采用Golang开发,但是有些功能是用c写的,比如说net-snmp,bfd协议等等,像这些如果使用GO语言重编的话,既有实现的复杂度也需要相当长的时间,好在GO语言提供了CGO机制,使得能够在go代码中直接调用C的库函数,大大提高了效率,减少了重复开发工作,此外还支持在C语言中调用GO函数,这一点还是蛮强大的. 1. Go语言调用C

  • 浅谈go中cgo的几种使用方式

    目录 最简单的CGO程序 源码方式调用C函数 内部机制 编译和链接参数 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS 链接参数:LDFLAGS 通过静态库的方式调用C函数 通过动态库的方式调用C函数 最简单的CGO程序 //cgo.go package main import "C" func main(){     println("hello cgo") } 上述代码是一个完整的CGO程序,通过import "C"语句启动了CG

  • 浅谈java中String的两种赋值方式的区别

    类似普通对象,通过new创建字符串对象.String str = new String("Hello"); 内存图如下图所示,系统会先创建一个匿名对象"Hello"存入堆内存(我们暂且叫它A),然后new关键字会在堆内存中又开辟一块新的空间,然后把"Hello"存进去,并且把地址返回给栈内存中的str, 此时A对象成为了一个垃圾对象,因为它没有被任何栈中的变量指向,会被GC自动回收. 直接赋值.如String str = "Hello&

  • 浅谈Java中随机数的几种实现方式

    众所周知,随机数是任何一种编程语言最基本的特征之一.而生成随机数的基本方式也是相同的:产生一个0到1之间的随机数.看似简单,但有时我们也会忽略了一些有趣的功能. 我们从书本上学到什么? 最明显的,也是直观的方式,在Java中生成随机数只要简单的调用: java.lang.Math.random() 在所有其他语言中,生成随机数就像是使用Math工具类,如abs, pow, floor, sqrt和其他数学函数.大多数人通过书籍.教程和课程来了解这个类.一个简单的例子:从0.0到1.0之间可以生成

  • 浅谈hibernate中对象的3种状态_瞬时态、持久态、脱管态

    Hibernate的对象有3种状态,分别为:瞬时态(Transient). 持久态(Persistent).脱管态(Detached).处于持久态的对象也称为PO(Persistence Object),瞬时对象和脱管对象也称为VO(Value Object). • 瞬时态 由new命令开辟内存空间的java对象, eg. Person person = new Person("amigo", "女"); 如果没有变量对该对象进行引用,它将被java虚拟机回收. 瞬

  • 浅谈C++中virtual的三种用法

    virtual用法一 #include using namespace std; class A{ public: virtual void display(){ cout<<"A"<<ENDL; } }; class B : public A{ public: void display(){ cout<<"B"<<ENDL; } }; void doDisplay(A *p) { p->display(); d

  • 浅谈tensorflow 中的图片读取和裁剪方式

    一 方式1: skimage from skimage import data, io, transform, color import matplotlib.pyplot as plt # io.imread 读出的图片格式是uint8,value是numpy array 类型. image = data.coffee() image = io.imread(dir) plt.imshow(image) plt.show() io.save('1.jpg',image) #保存图像 image

  • 浅谈springMVC中controller的几种返回类型

    Controller方法的返回值可以有以下几种: 1.返回ModelAndView 返回ModelAndView时最常见的一种返回结果.需要在方法结束的时候定义一个ModelAndView对象,并对Model和View分别进行设置. 2.返回String 1):字符串代表逻辑视图名 真实的访问路径="前缀"+逻辑视图名+"后缀" 注意:如果返回的String代表逻辑视图名的话,那么Model的返回方式如下: public String testController(

  • 浅谈ASP.NET Core的几种托管方式

    Kestrel Kestrel 是一个跨平台的适用于 ASP.NET Core 的 Web 服务器,默认包括在 ASP.NET Core 项目模板中. Kestrel 支持以下方案: HTTPS 用于启用 WebSocket 的不透明升级 用于获得 Nginx 高性能的 Unix 套接字 HTTP/2(除 macOS† 以外) 可以单独使用 Kestrel,也可以将其与反向代理服务器 (如 Internet Information Services (IIS).Nginx 或 Apache)结合

  • 浅谈java中math类中三种取整函数的区别

    math类中三大取整函数 1.ceil 2.floor 3.round 其实三种取整函数挺简单的.只要记住三个函数名翻译过来的汉语便能轻松理解三大函数,下面一一介绍 1.ceil,意思是天花板,java中叫做向上取整,大于等于该数字的最接近的整数 例: math.ceil(13.2)=14 math.ceil(-13.2)=-13 2.floor,意思是地板,java中叫做向下取整,小于等于该数字的最接近的整数 例: math.floor(13.2)=13 math.floor(-13.2)=-

  • 浅谈java中对集合对象list的几种循环访问

    java中对集合对象list的几种循环访问的总结如下  1 经典的for循环 public static void main(String[] args) { List<String> list = new ArrayList(); list.add("123"); list.add("java"); list.add("j2ee"); System.out.println("=========经典的for循环=======

随机推荐