go--cgo

evsqiezi · · 1330 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

经历了数十年发展的C语言,各种各样的现成的库已经非常丰富。通过cgo,可以在Go语言中使用C语言代码,充分利用好现有的“轮子”。
本文所有代码,在下述环境中调试通过:
    Windows 8.1 64-bit
    Go 1.3.3 64-bit
    GCC 4.8.1 64-bit
要想使用cgo,要导入C“包”:
import "C"
这行代码的上方要紧挨着连续的若干行的注释,在这些注释中编写C代码。例如:
/*
int PlusOne(int n)
{
    return n + 1;
}
*/
import "C"
我们知道,如果要引用一个包中的符号,需要用“包名.符号名”的方式,C“包”也是这样,例如:C.int、C.GetWindowLongPtr。
下面介绍使用C语言变量、函数、结构体、联合体、回调函数和动态链接库(Dynamic Link Library,dll)的方法。
    变量
    函数
    结构体
    联合体
    回调函数

    dll


1. 变量


使用C的变量很简单,比方说,要使用int,只要在Go代码中写C.int就可以了。
package main

import (
    "fmt"
)

import "C"

func main() {
    var n C.int
    n = 5
    fmt.Println(n) // 5

    var m1 int
    // Go不认为C.int与int、int32等类型相同
    // 所以必须进行转换
    m1 = int(n + 3)
    fmt.Println(m1) // 8

    var m2 int32
    m2 = int32(n + 20)
    fmt.Println(m2) // 25
}


2. 函数

在Go中调用C的函数也不困难。

package main

import (
    "fmt"
)

/*
int PlusOne(int n)
{
    return n + 1;
}
*/
import "C"

func main() {
    var n int = 10
    var m int = int(C.PlusOne(C.int(n))) // 类型要转换
    fmt.Println(m)                       // 11
}


3. 结构体

package main

import (
    "fmt"
)

/*
typedef struct _POINT
{
    double x;
    double y;
}POINT;
*/
import "C"

func main() {
    var p C.POINT
    p.x = 9.45
    p.y = 23.12
    fmt.Println(p) // {9.45 23.12}
}


4. 联合体

Go中使用C的联合体是比较少见而奇怪的事情,而且稍显麻烦,因为Go将C的联合体视为字节数组。比方说,下面的联合体LARGE_INTEGER被视为[8]byte。

typedef long LONG;
typedef unsigned long DWORD;
typedef long long LONGLONG;

typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    };
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;


所以,如果一个C的函数的某个参数的类型为LARGE_INTEGER,我们可以给它一个[8]byte类型的实参,反之亦然。

package main

import (
    "fmt"
)

/*
typedef long LONG;
typedef unsigned long DWORD;
typedef long long LONGLONG;

typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    };
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

void Show(LARGE_INTEGER li)
{
    li.u.LowPart = 1;
    li.u.HighPart = 4;
}
*/
import "C"

func main() {
    var li C.LARGE_INTEGER // 等价于: var li [8]byte
    var b [8]byte = li     // 正确,因为[8]byte和C.LARGE_INTEGER相同
    C.Show(b)              // 参数类型为LARGE_INTEGER,可以接收[8]byte
    li[0] = 75
    fmt.Println(li) // [75 0 0 0 0 0 0 0]
    li[4] = 23
    Test(li) // 参数类型为[8]byte,可以接收C.LARGE_INTEGER
}

func Test(b [8]byte) {
    fmt.Println(b)
}



5. 回调函数

有些C函数的参数是回调函数,比方说:

typedef UINT_PTR(__stdcall* GIRL_PROC)(int);
typedef UINT_PTR(__cdecl* GIRL_PROC_CDECL)(int);

UINT_PTR Func1(int n, GIRL_PROC gp)
{
    if (gp == NULL)
    {
        return 0;
    }
    return (*gp)(n);
}

UINT_PTR Func2(int n, GIRL_PROC_CDECL gp)
{
    if (gp == NULL)
    {
        return 0;
    }
    return (*gp)(n);
}


syscall包中有如下两个函数:

syscall.NewCallback
syacall.NewCallbackCDecl

其中,第一个函数接收一个Go函数(这个Go函数的返回值必须只有一个,而且类型为uintptr),并生成一个__stdcall调用约定的C函数,并将生成的函数的地址以uintptr的形式返回;第二个函数的作用与之类似,但生成的函数的调用约定是__cdecl。

一个值得注意的问题是:C的指向函数的指针在Go中被视为*[0]byte,所以要转换一下才能用。这里演示一下__stdcall调用约定的函数的用法,__cdecl类似。

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

/*
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

typedef UINT_PTR(__stdcall* GIRL_PROC)(int);
typedef UINT_PTR(__cdecl* GIRL_PROC_CDECL)(int);

UINT_PTR Func1(int n, GIRL_PROC gp)
{
    if (gp == NULL)
    {
        return 0;
    }
    return (*gp)(n);
}

UINT_PTR Func2(int n, GIRL_PROC_CDECL gp)
{
    if (gp == NULL)
    {
        return 0;
    }
    return (*gp)(n);
}
*/
import "C"

func GirlProc(n int32) uintptr {
    return uintptr(n + 97)
}

func main() {
    gp := syscall.NewCallback(GirlProc)
    fmt.Println(gp)
    gop := (*[0]byte)(unsafe.Pointer(gp))
    var t C.UINT_PTR = C.Func1(C.int(29), gop)
    fmt.Println(t) // 126
}

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:evsqiezi

查看原文:go--cgo

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1330 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传