I was working with CGO code and realized the need of keeping the C code in a shared library and using it from Go code. Here is a brief run down of how to go about it.
C Shared Library Overview
Libraries are wonderful as it provides allows to share the existing compiled code by programmes. Here, we look at how to build C shared library, which I am going to use in Go program using CGO.
Create a folder $WS/libs/shared_test, where you can export WS to your working directory path.
Create three files needed to build shared library
mmap_test.h
#ifndef _MMAP_TEST_H_
#define _MMAP_TEST_H_
extern void mmap_main();
#endif // _MMAP_TEST_H_
mmap_test.c
#include <stdio.h>
#include "mmap_test.h"
void mmap_main()
{
puts("mmap_main: Inside shared library");
return;
}
main.c
#include <stdio.h>
#include "mmap_test.h"
int main(void)
{
puts("Driver Program - Testing shared library");
mmap_main();
return 0;
}
Here, mmap_test.h provides the library interface and mmap_test.c provides library function implementation. main.c is the driver program using libmmap library.
Compiling the library
Step 1: Compile with position independent code (pic)
$ gcc -c -Wall -Werror -fpic mmap_test.c
This will create an object file mmap_test.o
Step 2: Create shared library from object file
gcc -shared -o libmmap.so mmap_test.o
Step 3: Linking with the shared library
gcc -L$WS/libs/shared_test -Wall -o test_mmap main.c -lmmap
Note that you have to specify the library path using -L option, otherwise linker does not know where to find libmmap specified with -l option above (GCC assumes that all libraries start with lib and end with .so).
Step 4: Specify LD_LIBRARY_PATH to specify libary path at runtime
The output file created test_mmap can not be run just like that. The path where libmmap.so is stored need to be specified using LD_LIBRARY_PATH, unless it is copied to standard path.
export LD_LIBRARY_PATH=$WS/libs/shared_test:$LD_LIBRARY_PATH
./test_mmap
Driver Program - Testing shared library
mmap_main: Inside shared library
Step 5 Automate using Makefile
# Makefile template for a shared library in C
CC = gcc # C compiler
CFLAGS = -fPIC -Wall -Wextra -O2 -g # C flags
LDFLAGS = -shared # linking flags
RM = rm -f # rm command
TARGET_LIB = ../libmmap.so # target lib
SRCS = mmap_test.c # source files
OBJS = $(SRCS:.c=.o)
.PHONY: all
all: ${TARGET_LIB}
$(TARGET_LIB): $(OBJS)
$(CC) ${LDFLAGS} -o $@ $^
$(SRCS:.c=.d):%.d:%.c
$(CC) $(CFLAGS) -MM $< >$@
include $(SRCS:.c=.d)
.PHONY: clean
clean:
-${RM} ${TARGET_LIB} ${OBJS} $(SRCS:.c=.d)
Using Shared library using CGo
Let’s see, how we can use libmmap.so from Go program using CGO.
Create a new file in same or different folder
main.go
package main
/*
#cgo CFLAGS: -I<change with WS>/libs/mmap_test/
#cgo LDFLAGS: -L<change with WS>/libs/ -lmmap
#include <stdio.h>
#include "mmap_test.h"
*/
import "C"
import (
"fmt"
)
func main() {
fmt.Println("Golang: shared library test")
C.mmap_main()
}
Build and run program
$ go build main.go
$ ./main
Golang: shared library test
mmap_main: Inside shared library
CGO: How to pass function pointer to C
Starting from Go 1.6 cgo has new rules Reference.
These rules are checked during the runtime, and if violated program crashes. So, it is not possible to pass a pointer to C code, if the memory to which it is pointing stores a Go function pointer. Fortunately, it is possible to woraround it by registering the function pointer and storing it in C code, probably using a mapping with index. Here, I show a basic example of storing single Go function pointer in C.
Add the following lines in
mmap_test.h
typedef void (*Callback)(int arg1, void *arg2);
void setCallback(Callback func);
mmap_test.c
#include <string.h>
Callback g_fn;
void setCallback(Callback func) {
g_fn = func;
}
In mmap_main() .. to test the callback
if (g_fn != NULL) {
g_fn(25, g_fn);
}
Rebuild shared library
$ make clean; make
main.c
void InitCallback();
extern void test_callback(int arg1, void *arg2);
void test_callback(int arg1, void *arg2) {
printf("%s called\n", __func__);
printf("arg1: %d arg2: %p\n", arg1, arg2);
}
void InitCallback() {
setCallback(test_callback);
}
In main() .. initialize the callback
InitCallback();
Build and run program
$ gcc -L$WS/libs/shared_test -Wall -o test_mmap main.c -lmmap
$ ./test_mmap
Driver Program - Testing shared library
mmap_main: Inside shared library
test_callback called
arg1: 25 arg2: 0x558d9d2345ea
main.go
In CGO section
static inline void test_callback(int arg1, void *arg2) {
printf("%s called\n", __func__);
printf("arg1: %d arg2: %p\n", arg1, arg2);
}
static inline void InitCallback() {
setCallback(test_callback);
}
In Go Code, main() function
C.InitCallback()
Build and run program
$ go build main.go
$ ./main
Golang: shared library test
mmap_main: Inside shared library
test_callback called
arg1:25 arg2: 0x492640