Using C in golang

Code Reuse

There are generally several reasons for wanting to use C language code within Go language:

Next, we will explain in detail how to use C language code in Go language.

Existing C Code

Here is listed a piece of C language code, which includes a function called ping(). This function, after receiving a passed-in network address string, will invoke the system’s ping to send an ICMP request to this network address and return the result. If there is no response after 2 seconds, ping will also terminate and return the result.

#include <stdio.h>
#include <string.h>

// Macro defines
#define PING_CMD        "/sbin/ping"
#define TIME_OUT        (2)
#define STR_PING_ENG    "PING"
#define STR_TX_ENG      "Tx Pkts"
#define STR_RX_ENG      "Rx Pkts"
#define STR_LOSS_ENG    "Pkt Loss"
#define DEST_LENGTH     (64)

// struct for storing ping result
typedef struct _PING_RESULT_T {
    char dest[DEST_LENGTH + 1];
    unsigned char txPkts;
    unsigned char rxPkts;
    float pktLoss;
} PING_RESULT_T;

// ping function
void ping (char *ip) {
    PING_RESULT_T ping_result;
    char cmdline[160], buf[80];
    FILE *fp;

    if (ip == NULL)
        goto FINISHED;
    else {
        memset(&ping_result, 0, sizeof(PING_RESULT_T));
        strncpy(ping_result.dest, ip, DEST_LENGTH);
    }

    // Generate ping command
    sprintf (cmdline, "%s -t %d -c 1 -q %s", PING_CMD, TIME_OUT, ip);

    // popen() executes given command and return command result
    if ( (fp = popen(cmdline, "r")) == NULL )
        goto FINISHED;

    // Parse command result
    while (fgets (buf, 80, fp) != NULL) {
        sscanf (buf, "%c packets transmitted, %c packets received, %f%% packet loss",
        &ping_result.txPkts,
        &ping_result.rxPkts,
        &ping_result.pktLoss);
    }
    fclose (fp);

    // Print out
    fprintf(stderr,"%s %s\n%s: %d\n%s: %d\n%s: %1.1f%%\n",
        STR_PING_ENG, ip,
        STR_TX_ENG, ping_result.txPkts,
        STR_RX_ENG, ping_result.rxPkts,
        STR_LOSS_ENG, ping_result.pktLoss);

FINISHED:
    return;
}

// Comment out main() function to avoid duplicate symbol
// int main (int argc, char* argv[]) {
//     ping ("www.google.com");
//     return 0;
// }

Please note that the main() function in this code snippet has been commented out because when integrating this code into Go language, the implementation of the main() function is completed by Go language. In both C and Go languages, a standalone program’s main() function must follow the one and only one rule.

Implementation of golang

package main

// #include <stdlib.h>  // for free() function of C library
// #include "myping.c"
import "C"
import "unsafe"

func main() {
// Convert go string to C string (malloc)
cString := C.CString("www.google.com")

// Call C function from included C source file (myping.c)
C.ping(cString)

// Free C variable
C.free(unsafe.Pointer(cString))
}

Let’s explain line by line, starting with:

package main

In Go language programs, packages are used as units of division, and this package concept is similar to the libraries in C language, so the following two lines mean almost the same in both Go and C languages:

#include <stdio.h>  // Introducing the standard IO library in C language
import "fmt"        // Introducing the formatted IO library in Go language

And in Go language, a file that contains the main() function must import package main to indicate that this file is the entry point of the entire program. Next is this line:

// #include <stdlib.h>  // for free() function of C library

This is how to reference C language libraries in Go language. Yes, you need to add the comment symbol // in front. Because without commenting it out, Go’s compiler would directly use Go language’s syntax rules to compile, which would certainly fail. Next is:

// #include "myping.c"

This is also C language syntax; this line includes the entire myping.c file. The following code is not commented, indicating it is Go syntax. The following line is how Go language recognizes that the chunk of comments at the beginning is actually C language code:

import "C"

Please note, this line must immediately follow the commented C language code, with no other imports or blank lines in between, and it must be written on a single line alone, without using Go’s multiple import syntax:

// Generally, when importing many packages at once, it can be done like this
import (
	"package A"
	"package B"
	"package C"
)

However, when importing C code, the comment block following the C code should not be written like this:

// It cannot be done like this when there is a C language block in front
import (
	"C"
	"fmt"
	"unsafe"
)

Because by doing so, Go would treat the previously commented C language code as pure comments, thus making it completely inaccessible in the program, so you can only do it like this:

import "C"
import (
	"fmt"
	"unsafe"
)

Returning to the original code, the next line is:

import "unsafe"

package unsafe contains operations for converting between different types of pointers in Go language, with the type unsafe.Pointer conceptually similar to void* in C language. Continuing on:

func main() {

Go’s main() function does not return a value, nor does it accept any parameters; this is the only way to write the main() function. As for the final brace {, its position is fixed; placing it not directly after the function name would fail to compile! Perhaps in the future, another article will be written about the programming style of Go language. Before continuing to analyze the code, let’s first explain how to use the syntax of functions from the imported package in Go language:

{package_name}.{function_name}

Because the imported package is “C”, the types in the following code are all C.{function_name}. First, convert a Go language string to a C language string:

cString := C.CString("www.google.com")

In this line of code, www.google.com is a Go language string, which, through the C.CString method, will be converted into a C string. Because this method allocates memory, you must perform a free action after use. Although Go language has a garbage collection mechanism, it currently does not automatically manage memory allocated by C language. Next is:

C.ping(cString)

This line calls a C language function written in the myping.c file, using it in a way similar to how it’s done in C language, just with an added C. before invoking the function.

C.free(unsafe.Pointer(cString))

Finally, free the cString variable that is no longer needed, using C’s free() function. Since the C.free() function accepts an unsafe.Pointer, and Go is a strongly typed language that does not accept implicit type conversion, here you must convert the C string cString into a Go language unsafe.Pointer before passing it in. A meticulous programmer might wonder, since cString is already a C language string, and C.free() calls a function from the C standard library, what does it have to do with unsafe.Pointer? Why is there a need for pointer type conversion? Let’s change the last line of code to the following and recompile to see the result:

C.free(cString)

The Go compiler does not accept this approach, because:

cannot use cString (type *C.char) as type unsafe.Pointer in argument to _Cfunc_free

It turns out, the C functions and C types that appear callable are all wrapped with Go language’s own layer. A char* in C language is actually *C.char in Go language, and the free() function from the C standard library is actually _Cfunc_free in Go language.

Put myping.c and myping.go, which includes the main() function, in the same directory and execute:

go run myping.go

The Go language correctly called the C language code, resulting in the following:

PING www.google.com
Tx Pkts: 114
Rx Pkts: 49
Pkt Loss: 0.0%

The numbers in the result will vary depending on the network connection status.

Continuing Exploration

So far, we have successfully called a C language function we wrote ourselves. But what if we want to call standard functions from the C language in Go? Suppose we want to calculate the length of a C string, for which we need to use the strlen() function. To use this function, we must include the string.h from the C language library. Since myping.c already includes this header file, we can directly call it:

// cString = "www.google.com"
println("\nstrlen(cString):", C.strlen(cString))

The result of the execution is:

strlen(cString): 14

So, is it possible to directly write programs in C language within Go language, without importing already written code through the #include “xxx.c” method? The answer is affirmative; you can add C code before import “C”:

//void sayHelloTo(char *name) {
//    fprintf(stderr, "Hello, %s.\n", name);
//}
import "C"

Then, invoke using the C.{function_name} pattern:

// cString = "www.google.com"
C.sayHelloTo(cString)

The result of the execution is:

Hello, www.google.com.

If there is a lot of C code that needs to be added, Go language indeed supports commenting out C language code in the following way:

/* All the code here is commented out C language code */
import "C"

Type Conversion

From the example above, it’s clear that since Go language does not automatically perform type conversion, and the types in C language are actually encapsulated by an additional Go layer, using them is not as intuitive as it seems. So, how should one convert types between these two? First is for already processed strings, and the method for converting strings between these two languages is:

package main
/*
#include <stdlib.h>
// Declare a C language string named cString
char *cString = "This is a c string.";
*/
import "C"
import (
	"fmt"
	"unsafe"
)

func main() {
	// Convert the C string to a Go string
	goString1 := C.GoString(C.cString)
	// Convert the first 5 characters of the C string to a Go string
	goString2 := C.GoStringN(C.cString, 5)
	// Convert the first 5 characters of the C string to a Go byte array
	goByteArray := C.GoBytes(unsafe.Pointer(C.cString), 5)
	// Print the values of these 3 Go types
	fmt.Println("goString1 =", goString1)
	fmt.Println("goString2 =", goString2)
	fmt.Println("goByteAry =", goByteArray)
	
	// Convert a Go string to another C string (allocate new memory)
	anotherCString := C.CString(goString1)
	// Free the allocated memory
	C.free(unsafe.Pointer(anotherCString))
}

The syntax for converting between non-string basic types is similar to the way type conversion is done in C language, and it goes like this:

NewTypeVariable = NewType(OldTypeVariable)

When incorporating C and Go languages, it looks like this:

C.float, C.double
// Converting from C language types to Go language types
GoVariable = GoType(CVariable)
// Converting from Go language types to C language types
CVariable = C.CType(GoVariable)
// The name of char in C language in Go language
C.char, C.schar (signed char), C.uchar (unsigned char)
// The name of short in C language in Go language
C.short, C.ushort (unsigned short)
// The name of int in C language in Go language
C.int, C.uint (unsigned int)
// The name of long in C language in Go language
C.long, C.ulong (unsigned long)
// The name of long long in C language in Go language
C.longlong (long long), C.ulonglong (unsigned long long)
// The name of floating-point numbers in C language in Go language

The problem is that C language types are encapsulated by an additional Go layer, so the names of C language types are completely different from what one might initially think. golang.org provides explanations for this part, listed as follows:

Here, taking float as an example, demonstrates how to convert between types in the two languages:

package main
/*
#include <stdlib.h>
// Declare a C language float named cFloat
float cFloat = 3.1415926;
*/
import "C"
import "fmt"

func main() {
	// Convert C float to Go float32
	goFloat32 := float32(C.cFloat)
	// Print the value of goFloat32
	fmt.Println("goFloat32 =", goFloat32)
	// Convert Go float32 back to C float
	C.cFloat = C.float(goFloat32)
}

Converting between arrays is also not a problem:

package main
/*
#include <stdlib.h>
/ Declare a C language array named cAry, with a type of int
int cAry[] = {1, 2, 3, 4, 5};
*/
import "C"
import "fmt"

func main() {
	// Declare a slice named goAry
	goAry := []int{}
	// Iterate over the C language array
	for _, element := range C.cAry {
		// Add each element obtained from iterating the C array to the Go slice
		goAry = append(goAry, int(element))
	}
	// Print the value of the Go language array
	fmt.Println("    goAry =", goAry)
	// Iterate over the C language array
	for idx, _ := range C.cAry {
		// Access the value at each index of the C array using [idx]
		C.cAry[C.int(idx)] = C.int(goAry[idx])
	}
	// Print the value of the C language array
	fmt.Println("   C.cAry =", C.cAry)
}

Above is the method for converting arrays between C and Go languages. Please note that when accessing C.cAry[idx], the internal idx type is Go language’s int, which must be converted to C language’s C.int to work.

Additionally, composite types such as struct or union are also common in C language. Here, the method for conversion is introduced, starting with the conversion from struct:

package main
/*
#include <stdlib.h>
// Declare a struct named cStudent
struct cStudent {
	char *name;
	int id;
};
// Initialize cStruct object
struct cStudent cStruct = {"Tom", 3};
*/
import "C"
import (
	"fmt"
	"unsafe"
)

// Declare a struct named goStudent
type goStudent struct {
	name string
	id   int
}

func main() {
	// Convert C struct to Go struct
	goStruct := goStudent{
		name: C.GoString(C.cStruct.name),
		id:   int(C.cStruct.id),
	}
	// Print the value of the Go language struct
	fmt.Println(" goStruct =", goStruct)
	// Convert Go struct back to C struct
	// Convert Go string to C string (allocate new memory)
	cString := C.CString(goStruct.name)
	C.cStruct = C.struct_cStudent{
		name: cString,
		id:   C.int(goStruct.id),
	}
	// Print the value of the C language struct
	fmt.Println("  cStruct =", C.GoString(C.cStruct.name), C.cStruct.id)
	// Free the allocated memory
	C.free(unsafe.Pointer(cString))
}

The concept and conversion of basic types are quite similar, the only thing to note is that when initializing, you need to prefix {struct_name} with C.struct_. Next, let’s try converting a union, first defining the C language union we use as follows:

union cData {
    unsigned char bytes;
    unsigned short shortInt;
};
union cData cUnion = {.shortInt = 0x4321};

The memory size occupied by this cUnion is that of the largest member, shortInt. Since shortInt is initialized to 0x4321 at the beginning, the value of the member bytes must be 0x21, which is 33 in decimal. Let’s print out the just-initialized union to verify this idea:

fmt.Println(C.cUnion.bytes)

Unexpectedly, it fails to compile right from the start, with the compiler throwing an error:

_Cvar_cUnion.bytes undefined (type *[2]byte has no field or method bytes)

How could a union be related to a byte array? After some research on the official site, it turns out that because Go language does not support C language’s union type, a union in Go is converted into a byte array, with the array’s length being the number of bytes occupied by the union. In this example, it can be inferred that the array’s size is the size of short, which is 2, let’s verify this right away:

fmt.Println("Sizeof union =", unsafe.Sizeof(C.cUnion))

The result obtained is:

Sizeof union = 2

Programming languages and real-life crowd behavior are quite similar; behind even the biggest contradictions, there’s merely a difference in the definition of the same thing. Now that the type is known, there’s nothing strange about the phenomenon. Declare a byte array to receive the union and then operate on it:

package main
/*
#include <stdlib.h>
// Declare a union named cData
union cData {
    unsigned char bytes;
    unsigned short shortInt;
};
// Initialize cUnion object
union cData cUnion = {.shortInt = 0x4321};
*/
import "C"
import (
	"fmt"
	"unsafe"
)

// Declare a struct named goData (Go language does not have union)
type goData struct {
	bytes    uint8
	shortInt uint16
}

func main() {
	// Declare a goByteAry to operate on C.cUnion
	var goByteAry [unsafe.Sizeof(C.cUnion)]byte = C.cUnion

	// Convert C union to Go struct
	goUnion := goData{
		bytes:    goByteAry[0],
		shortInt: uint16(goByteAry[0]) + uint16(goByteAry[1])<<8,
	}
	// Print the value of the Go language struct
	fmt.Println("  goUnion =", goUnion)
	// Convert Go struct back to C union
	goByteAry[0] = goUnion.bytes
	goByteAry[1] = uint8(goUnion.shortInt >> 8)
	// Print the value of the C language union
	fmt.Println("   CUnion =", C.cUnion)  // 33 + 67 << 8 = 17185
}

Since there’s no union in Go language, the conversion is a bit awkward. It’s recommended to directly use a byte array in Go language for accessing C language’s union. Next, let’s look at enum, as enum in both C and Go languages are constants whose values are determined at compile time, so there’s no such thing as mutual conversion, and the access method is quite intuitive, similar to accessing regular variables in C language:

package main
/*
#include <stdlib.h>
// Declare an enum
enum {
     B =  1,
    KB =  B * 1<<10,
    MB = KB * 1<<10,
    TB = MB * 1<<10
};
*/
import "C"
import "fmt"

func main() {
	fmt.Println("B, KB, MB, TB =", C.B, C.KB, C.MB, C.TB)
}

Although Go has a built-in garbage collection mechanism, actively controlling memory release can improve program performance. Here is how to actively allocate/release memory in Go language using C language functions:

// Allocate a memory space of 128 bytes
pAry := C.malloc(128)
// Free the memory space
C.free(unsafe.Pointer(pAry))

After memory allocation, the next step is how to operate it. Operating this memory requires some advanced knowledge of pointer arithmetic. Once you’re prepared, you can start learning how to operate shared memory between Go and C languages.

Further Reading