[Go語言]cgo用法演示


 

 
 

經歷了數十年發展的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)的方法。

 

  1. 變量
  2. 函數
  3. 結構體
  4. 聯合體
  5. 回調函數
  6. 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
}


 

6. dll

以后再寫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM