Using C code from Go
19-08-2024
Background
In the previous article, testing C with Go , I looked at how easy it is to include simple C code in your Go application. I also demonstrated how you can use Go to test C code and how easy it is with Go's excellent testing support.
In this post I'll be digging a little bit deeper into the C and Go interoperability features by making use of some more complex C code and showing how C and Go can coexist in a project.
If you need to set up Go on your computer, please see the previous article for resources.
Easy embed C code in Go
Did you know you can embed C code directly inside your Go sources? Init a new Go project with go mod init in a new directory. I called my module cwithgo, use whatever works for you. Create a source file called main.go and add the following content:
package main // #include <stdio.h> // void responder(void) { // printf("This is C. Read you five by five.\n"); //} import "C" import "fmt" // ======================================================================= main func main() { fmt.Println("Attempting to call C.") C.responder() }
You can run it with the standard:
go run .
On my system, I see:
Attempting to call C. This is C. Read you five by five.
Pretty handy when you really just want to have a short bit of C. Of course, for larger projects, or existing code, you really want to use external source files, especially since you get zero help from most editors inside comments. I mainly use this technique when trying out some small samples or when I really just need to call a particular function in a library.
Speaking of editors, have you checked out Zed yet? It's definitely worth a visit.
Go, C and Strings
The next challenge I've run into is having Go and C work together with strings. I often need to either pass a string value to C or receive one from a C function. Once again, Go makes this less of a headache than one would expect. To enable interoperability between C and Go, Go includes utility functions that cast Go types to their C versions. For example, CString can convert a Go-style string to a C-style string.
Update the main.go file a bit:
package main // #include <ctype.h> // #include <stdio.h> // // void responder(void) { // printf("This is C. Read you five by five.\n"); // } // // void sayHelloTo(char* person) { // printf("Oh, howdy %s!\n", person); // } // // char* toUpper(char* s) { // for (char *ptr=s; *ptr; ptr++) *ptr=toupper(*ptr); // return s; // } import "C" import "fmt" // ======================================================================= main func main() { fmt.Println("Attempting to call C.") C.responder() C.sayHelloTo(C.CString("Roger")) fmt.Println("abcd becomes:", C.GoString(C.toUpper(C.CString("abcd")))) }
Run it with:
go run .
On my system I see:
Attempting to call C. This is C. Read you five by five. Oh, howdy Roger! abcd becomes: ABCD
We can send strings to C and easily receive them as well. The only trick is to use the correct cast, as provided by Go, to cross the divide safely. The GoString function is used to convert a C-style string to a Go-style string. We can also include and access the standard C libraries, which is pretty handy.
Passing buffers to C
A lot of C code I need to integrate with Go requires some kind of a character buffer as a parameter. As they say, with great power comes great crashes. To really integrate with C, we need to resort to the realm of unsafe operations. Basically, we want to forego the convenience and safety of having Go manage program memory on our behalf and dip into the realm of handling memory management ourselves a bit. To do this, we need to do the following in Go:
package main /* #cgo CFLAGS: -g #include <ctype.h> #include <stdio.h> #include <stdlib.h> void responder(void) { printf("This is C. Read you five by five.\n"); } void sayHelloTo(char* person) { printf("Oh, howdy %s!\n", person); } char* toUpper(char* s) { for (char *ptr=s; *ptr; ptr++) *ptr=toupper(*ptr); return s; } int populate(int quantity, const char *item, char *out) { return sprintf(out, "We have %d %s available.", quantity, item); } */ import "C" import ( "fmt" "unsafe" ) // ======================================================================= main func main() { fmt.Println("Attempting to call C.") C.responder() C.sayHelloTo(C.CString("Roger")) fmt.Println("abcd becomes:", C.GoString(C.toUpper(C.CString("abcd")))) // ---------- unsafe operations follow ---------- // create parameters productName := C.CString("Groovy Cheese Straws") defer C.free(unsafe.Pointer(productName)) stockCount := C.int(11) // create buffer bufferPtr := C.malloc(C.sizeof_char * 1024) defer C.free(unsafe.Pointer(bufferPtr)) // make the call, passing the parameters and the buffer size := C.populate(stockCount, productName, (*C.char)(bufferPtr)) // copy the buffer into a Go memory-managed slice goSlice := C.GoBytes(bufferPtr, size) // print the slice we created from the C buffer fmt.Println(string(goSlice)) }
The first thing you'll notice is that I'm using a different style of Go comment, a block comment. This is my preferred way as it displays neater and cleaner, but you should stick to what works for you. Also, you'll notice that you can give compiler flags using the #cgo CFLAGS: -g statement. This directs CGO to use the specified flag(s) when compiling your C code.
Take note of the unsafe import. This allows us to do C-style pointer operations, like getting a pointer to a string or a buffer that can be passed to the C code. Because we are managing the memory when in unsafe mode, it is important to remember to set up the memory free calls. Defer is a great way of getting that done whenever you allocate the memory, so you don't forget!
When I run the code on my machine I see:
Attempting to call C. This is C. Read you five by five. Oh, howdy Roger! abcd becomes: ABCD We have 11 Groovy Cheese Straws available.
Conclusion
Go has very good support for interoperability with C, as you've seen. It does require a bit of unsafe code, but then again, C code is not memory managed anyway! With some caution, and testing, this can be achieved safely and be a great tool when you need to make use of existing C code.
Resources
To learn more about Go or C, you can use the following resources as a starting point:
- Go's learning resources.
- For Go and or C practice, Exercism is amazing.
- The FreeCodecamp Go Full Course Youtube video is a great resource.
- More information about CGO can be found in the reference documentation.
- If you are serious about learning C, Low Level Academy has an excellent course.
Return to the blog index