Interoperability between Go and C language, import "C" instance

Table of contents

a source code

The principle of Go calling C code

Using C's types in Go

native type

value type

pointer type

string type

array type

custom type

Enumeration (enum)

Structure (struct)

union

typedef

Accessing C variables and functions in Go

Using Go functions in C

other


a source code

/*
#cgo LDFLAGS: -L. -ldevmapper
#include <libdevmapper.h>
#include <linux/fs.h>   // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
// FIXME: Can't we find a way to do the logging in pure Go?
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void	log_cb(int level, const char *file, int line, int dm_errno_or_class, const char *f, ...)
{
  char buffer[256];
  va_list ap;
  va_start(ap, f);
  vsnprintf(buffer, 256, f, ap);
  va_end(ap);
  DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void	log_with_errno_init()
{
  dm_log_with_errno_init(log_cb);
}
*/
import "C"

It can be seen that go and c are interoperable. Introduce the interoperability between go and c:
Go has a strong C background. In addition to the inheritance of syntax, its designers and design goals are inextricably linked with the C language. In terms of Go and C language interoperability (Interoperability), Go provides strong support. Especially using C in Go, you can even write C code directly in the Go source file, which is unmatched by other languages.

 
Interoperability between Go and C may be involved in the following scenarios:
 

  • Replace some Go code with C when improving local code performance. C is to Go what assembly is to C.
  • Suspect that the GC performance of Go memory is not enough, so manually manage the application memory by yourself.
  • Implements a Go Wrapper for some libraries. For example, Oracle provides the C version of OCI, but Oracle does not provide the Go version and the details of the protocol for connecting to the DB, so it can only be provided to Go developers by packaging the C OCI version.
  • Go exports functions for use by C developers (this need should be rare at the moment).
     

The principle of Go calling C code

package main
 
// #include <stdio.h>
// #include <stdlib.h>
/*
void print(char *str) {
    printf("%s\n", str);
}
*/
import "C"
 
import "unsafe"
 
func main() {
    s := "Hello Cgo"
    cs := C.CString(s)
    C.print(cs)
    C.free(unsafe.Pointer(cs))
}
  1. There are several "special" things about the above code compared to "normal" Go code:
  2. The word include of the C header file appears in the comment at the beginning
  3. The C function print is defined in the comment
  4. A "package" called C that imports
  5. In the main function, the above C function -print is actually called

That's right, this is the step of calling C code in Go source code. It can be seen that we can directly write C code in Go source code file.
 
First of all, the C code in the Go source code file needs to be wrapped with comments, just like the include header file and the print function definition above; secondly, the
import "C" statement is necessary, and it cannot be separated from the above C code. Separated by blank lines, must be closely connected. The "C" here is not a package name, but a concept similar to a namespace, or it can be understood as a pseudo-package. All syntax elements of C language are under this pseudo-package; finally, when accessing C syntax elements, they must be in front of it
. Add a pseudo-package prefix, such as C.uint and C.print, C.free, etc. in the above code.
 
How do we compile this go source file? In fact, it is no different from "normal" Go source files, and can still be compiled and executed directly through go build or go run. But in the actual compilation process, go calls a tool named cgo, cgo will identify and read the C elements in the Go source file, and extract them and hand them over to the C compiler for compilation, and finally compile the target file with the Go source code link into an executable program. In this way, it is not difficult for us to understand why the C code in the Go source file is wrapped with comments. These special syntaxes can be recognized and used by Cgo.

Using C's types in Go

native type

value type

In Go, you can access C's native numeric types as follows:

C.char,
C.schar (signed char),
C.uchar (unsigned char),
C.short,
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.long,
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
C.float,
C.double

There is not a one-to-one correspondence between Go's numeric types and C's numeric types. Therefore, explicit transformation operations are indispensable when using variables of the other type, such as this example in Go doc:

func Random() int {
    return int(C.random())//C.long -> Go的int
}
 
func Seed(i int) {
    C.srandom(C.uint(i))//Go的uint -> C的uint
}

pointer type

The pointer type of the primitive numerical type can be added * before the type according to the Go syntax, such as var p *C.int. And void* is special, represented by unsafe.Pointer in Go. Any type of pointer value can be converted to unsafe.Pointer type, and unsafe.Pointer type value can also be converted to any type of pointer value. unsafe.Pointer can also be converted to and from the type uintptr. Since the pointer type of unsafe.Pointer cannot perform arithmetic operations, it can perform arithmetic operations after being converted to uintptr.

string type

There is no formal string type in C language. In C, a character array with a trailing '\0' is used to represent a string; in Go, the string type is a native type, so interoperability between the two languages ​​is inevitable. To do string type conversion.
 
Through the C.CString function, we can convert Go's string type to C's "string" type, and then pass it to the C function for use. As we used in the example at the beginning of this article:

s := "Hello Cgo\n"
cs := C.CString(s)
C.print(cs)

However, the C string cs obtained after such transformation cannot be managed by Go's gc. We must manually release the memory occupied by cs, which is why the last call to C.free releases cs in the example. The GC in Go cannot perceive the memory allocated in C, so remember to release it.

The C string (*C.char) can be converted to the Go string type through C.GoString, for example:

// #include <stdio.h>
// #include <stdlib.h>
// char *foo = "hellofoo";
import "C"
 
import "fmt"
 
func main() {
… …
    fmt.Printf("%s\n", C.GoString(C.foo))
}

array type

The array in C language is quite different from the array in Go language. The latter is a value type, while the former and the pointer in C can be converted at will in most cases. At present, it seems that there is no direct and explicit transformation between the two, and the official documentation does not explain it. But we can convert an array of C to a Slice of Go by writing a conversion function (since the array in Go is a value type and its size is static, it is more general to convert to a Slice), the following is an example of integer array conversion :

// int cArray[] = {1, 2, 3, 4, 5, 6, 7};
 
func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {
    p := uintptr(cArray)
    for i :=0; i < size; i++ {
        j := *(*int)(unsafe.Pointer(p))
        goArray = append(goArray, j)
        p += unsafe.Sizeof(j)
    }
 
    return
}
 
func main() {
    … …
    goArray := CArrayToGoArray(unsafe.Pointer(&C.cArray[0]), 7)
    fmt.Println(goArray)
}

Execution result output: [1 2 3 4 5 6 7]

It should be noted here that the Go compiler cannot automatically convert C's cArray to the address of the array, so the array variable cannot be passed directly to the function like an array in C, but the address of the first element of the array is passed to the function.

custom type

In addition to native types, we can also access custom types in C.

Enumeration (enum)

// enum color {
//    RED,
//    BLUE,
//    YELLOW
// };
 
var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW
fmt.Println(e, f, g)

Output: 0 1 2

For named C enumeration types, we can access the type through C.enum_xx. If it's an anonymous enum, it seems that only its fields can be accessed.

Structure (struct)

// struct employee {
//     char *id;
//     int  age;
// };
 
id := C.CString("1247")
var employee C.struct_employee = C.struct_employee{id, 21}
fmt.Println(C.GoString(employee.id))
fmt.Println(employee.age)
C.free(unsafe.Pointer(id))

output:

1247

21

Similar to enum, we can access the structure type defined in C through C.struct_xx.

union

Here I'm trying to access a C union the same way I access a struct:

// #include <stdio.h>
// union bar {
//        char   c;
//        int    i;
//        double d;
// };
import "C"
 
func main() {
    var b *C.union_bar = new(C.union_bar)
    b.c = 4
    fmt.Println(b)
}

However, when compiling, go reports an error: bc undefined (type *[8]byte has no field or method c). From the error message, Go treats union differently from other types. It seems to treat union as [N]byte, where N is the size of the largest field in union (after rounding), so we can handle C as follows .union_bar:

func main() {
    var b *C.union_bar = new(C.union_bar)
    b[0] = 13
    b[1] = 17
    fmt.Println(b)
}

Output: &[13 17 0 0 0 0 0 0]

typedef

When accessing an alias type defined with typedef in Go, the access method is the same as the original actual type access method. like:

// typedef int myint;
 
var a C.myint = 5
fmt.Println(a)
 
// typedef struct employee myemployee;
 
var m C.struct_myemployee

As can be seen from the example, for the alias of the original type, you can directly access the new type name. For the alias of the compound type, the new alias needs to be accessed according to the access method of the original compound type. For example, the actual type of myemployee is struct, and the prefix struct_ should be added when using myemployee.

Accessing C variables and functions in Go

In fact, in the above example, we have demonstrated how to access C variables and functions in Go. The general method is to add the C prefix, especially for the functions in the C standard library. However, although we can directly define C variables and C functions in the Go source code file, in terms of code structure, writing a large number of C codes in the Go source code does not seem to be so "professional". So how to separate the C function and variable definition from the Go source code and define it separately? It is easy for us to think of providing the C code to the Go source code in the form of a shared library.
 
Cgo provides the #cgo directive to specify which shared libraries the Go source code will be linked with after compilation. Let's look at an example:

package main
 
// #cgo LDFLAGS: -L ./ -lfoo
// #include <stdio.h>
// #include <stdlib.h>
// #include "foo.h"
import "C"
import "fmt“
 
func main() {
    fmt.Println(C.count)
    C.foo()
}

We see that in the above example, the #cgo directive is used to tell the go compiler to link the libfoo shared library in the current directory. Both the C.count variable and the C.foo function are defined in the libfoo shared library. Let's create this shared library:

// foo.h
 
int count;
void foo();
 
//foo.c
#include "foo.h"
 
int count = 6;
void foo() {
    printf("I am foo!\n");
}
 
$> gcc -c foo.c
$> ar rv libfoo.a foo.o
 
我们首先创建一个静态共享库libfoo.a,不过在编译Go源文件时我们遇到了问题:
 
$> go build foo.go
# command-line-arguments
/tmp/go-build565913544/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined
foo(0): not defined

Prompt foo function is not defined. The specific compilation details were printed out through the -x option, and the problem was not found. However, I found an issue (http://code.google.com/p/go/issues/detail?id=3755) in the Go issue list, which mentioned that the current version of Go does not support linking static shared libraries.
 
Then let's try to create a dynamic shared library:

$> gcc -c foo.c
$> gcc -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

Compile foo.go again, and it will indeed succeed. Execute foo.

$> go build foo.go && go
6
I am foo!

It is also worth noting that Go supports multiple return values, while C does not support it. Therefore, when a C function is used in a multi-return value call, C's errno will be returned as the err return value. The following is an example:

package main
 
// #include <stdlib.h>
// #include <stdio.h>
// #include <errno.h>
// int foo(int i) {
//    errno = 0;
//    if (i > 5) {
//        errno = 8;
//        return i – 5;
//    } else {
//        return i;
//    }
//}
import "C"
import "fmt"
 
func main() {
    i, err := C.foo(C.int(8))
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
    }
}
 
$> go run foo.go
exec format error

errno is 8, its meaning can be found in errno.h:

#define ENOEXEC      8  /* Exec format error */

It is indeed "exec format error".

Using Go functions in C

There are fewer occasions to use Go functions in C than to use C source code in Go. In Go, you can use "export + function name" to export Go functions for use in C, see a simple example:

package main
 
/*
#include <stdio.h>
 
extern void GoExportedFunc();
 
void bar() {
        printf("I am bar!\n");
        GoExportedFunc();
}
*/
import "C"
 
import "fmt"
 
//export GoExportedFunc
func GoExportedFunc() {
        fmt.Println("I am a GoExportedFunc!")
}
 
func main() {
        C.bar()
}

But when we compile the Go file, we get the following error message:

# command-line-arguments
/tmp/go-build163255970/command-line-arguments/_obj/bar.cgo2.o: In function `bar':
./bar.go:7: multiple definition of `bar'
/tmp/go-build163255970/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here
collect2: ld returned 1 exit status

There seems to be no problem with the code, but it just cannot be compiled, and it always prompts "multiple definitions". Looking through the Cgo documentation, I found some clues. It turns out
 
There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).
 
It seems that // extern int f() and //export f cannot be placed in a Go source file. We split bar.go into two files bar1.go and bar2.go:

// bar1.go
 
package main
 
/*
#include <stdio.h>
 
extern void GoExportedFunc();
 
void bar() {
        printf("I am bar!\n");
        GoExportedFunc();
}
*/
import "C"
 
func main() {
        C.bar()
}
 
// bar2.go
 
package main
 
import "C"
import "fmt"
 
//export GoExportedFunc
func GoExportedFunc() {
        fmt.Println("I am a GoExportedFunc!")

Compile and execute:

$> go build -o bar bar1.go bar2.go
$> bar
I am bar!
I am a GoExportedFunc!

Personally, I feel that Go’s function of exporting functions for use in C is still very limited. The calling conventions of the two languages ​​are different, the types cannot be one-to-one, and advanced functions like Gc in Go make it difficult to perfectly implement the function of exporting Go functions. The function still cannot be completely separated from the Go environment, so the practicality seems to be discounted.

other

Although Go provides a powerful interoperability function with C, it is still not perfect. For example, it does not support directly calling functions with variable number of parameters in Go (issue975), such as printf (thus, fputs is often used in documents).
 
The advice here is: try to narrow the scope of interoperability between Go and C.
 
What does that mean? If you use C code in Go, try to call C functions in C code. It's best for Go to use only one C function that you wrap. Don't code like this:

C.fputs(…)
C.atoi(..)
C.malloc(..)

Instead, these C function calls are encapsulated into a C function, and Go only knows this C function.

Guess you like

Origin blog.csdn.net/u012206617/article/details/131411803