Calls to C libraries using Go tend to be more common than dynamic libraries using C to call Go (previous article). The reason why the common library is encapsulated by the bottom similar to C language, by the upper application layer language to call, or performance bottleneck; Or lower maintenance costs for common services, especially in a large company with a multi-lingual team that encapsulates core functions in one language and then calls them in an application-layer language, which is often cheaper to maintain.

In practice, the application layer and core services are often separate projects, maintained by disparate teams. So the following process is to think about the common library designed by C language in this actual scenario, how to integrate with CGO, and then use it in Go projects. The main steps are as follows:

  1. Convention interface, encapsulation interface
  2. Build a dynamic library
  3. Access to the use of

1. Convention interface and encapsulation interface

As both sides of the project, the first step is definitely to agree on the interface functions. Here we assume that a similar design process the message queue data of public services, the core of public service is finish initializing the message queue connection (the Init function), pull data from the consumer queue (Start function), Stop the service (Stop function), so we put the exposure to the language, the three method calls by the Go.

// Expose the interface used by Go
void Init(char *host, char *topic, uintptr_t callback);
int Start(a);
void Stop(a);

// This interface is used by C -> Go to send back data
extern void sendmsg_callback(uintptr_t callback, char *content);
Copy the code

Since the processing of message queue data is a real-time polling process, once the data is read, we need to send the data back to the Go upper application, so a callback function (sendMSG_callback) is also agreed here.

Once you’ve agreed on a functional interface, go your separate ways. For the development of the underlying public service, only need to implement Init, Start, Stop three function details. The sendmsg_callback() function needs to be implemented by Go, so use the extern keyword in the declaration.

1.1 Encapsulating C Public Services

This step, in fact, relatively independent, according to the requirements of the implementation of logic can be. Therefore, there is no requirement for the structure of the project header file and the code logic, but only the requirement for the final export as a. So dynamic library, of course, the minimum requirement must include the above four interface functions.

#Project directoryLibkafka / ├ ─ ─ libkafka. H └ ─ ─ libkafka. CCopy the code

Here we define a simple project project directory (above). The libkafka.h header is of our own design, as long as it contains the above four functions. You don’t even need a header file. I could just write it in dot c. But remember, you don’t have to worry about how you’re going to integrate this function into the Go caller, you just have to follow the interface conventions. Because many students at the beginning of the USE of CGO, always struggling with how to integrate their own packaged libraries into Go projects, in fact, do not care too much, these are some Go students to complete.

Libkafka. H statement

#ifndef LIB_KAFKA
#define LIB_KAFKA

#include <stdint.h>

void Init(char *host, char *topic, uintptr_t callback);
int Start(a);
void Stop(a);
extern void sendmsg_callback(uintptr_t callback, char *content);

#endif
Copy the code

Libkafka. c code implementation

//libkafka.c
#include <strings.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include "libkafka.h"

typedef struct {
    char *host;
    char *topic;
    uintptr_t callback;
} kafka_t;

typedef struct {
    uintptr_t callback;
} poll_param_t;

kafka_t *kafka;
pthread_t pid;
poll_param_t param;

int cancel;

void *poll(void *args) {
    pthread_detach(pthread_self());

    / /... Simulate reading queue data

    while(! cancel) { sleep(5); // Process a batch of data every 5 seconds
        / /... Return data to the Go callback
        char * msg = malloc(sizeof(char) * 6);
        strcpy(msg, "hello");
        printf(" trigger callback: %s, call func: %ld \n", msg, kafka->callback);
        sendmsg_callback(kafka->callback, msg);
    }

    return NULL;
}

void Init(char *host, char *topic, uintptr_t callback) {
    kafka = (kafka_t *) malloc(sizeof(kafka_t));
    kafka->host = host;
    kafka->topic = topic;
    kafka->callback = callback;

    printf("Init: host=%s, topic=%s, callback=%ld \n", host, topic, callback);
}

int Start(a) {
    printf("Start func \n");
    pthread_create(&pid, NULL, &poll, NULL);

    return 0;
}

void Stop(a) {
    if(kafka ! =NULL) { // Release the heap memory's Go allocation
        free(kafka->host);
        free(kafka->topic);
        free(kafka);
    }
    cancel = 1;

    // Reclaim the thread
    pthread_join(pid, NULL);

    printf("stop finished! \n");
}
Copy the code

We don’t actually implement the service logic here, but rather approximate the service functionality, because the goal is mainly to understand what the process of developing and collating with CGO is like. The above code is roughly implemented as follows:

  1. InitFunction is responsible for connecting queue functions,GoCall, will queue the request messageIP, need to operatetopic, and the callback function when data is availableIDIt’s coming through.
  2. StartThe function is responsible for running the service, where we start a child thread that simulates pulling data from the message queue continuously, and if there is data, we callsendmsg_callback, back to theGoLanguage.
  3. StopThe function handles some of the collection when the service is down. In particular,GoLanguage strings, pointer type data, be surefreeOut!!!

2. Export the dynamic library

Create a dynamic library called libkafka.so:

gcc -lpthread -fPIC -shared libkafka.c -o libkafka.so
Copy the code

3. Access and use

With the libkafka.so dynamic library, you’re ready. All that remains is for the access side to do.

As an application-layer Go language, we need to do a little bit of work. Suppose we have a project that needs to use this common service, we just need to import the.so file and call the interface methods that are agreed upon. We need to write some code for CGO:

package main

/* #include 
      
        #include 
       
         #cgo LDFLAGS: -L ./libs -lkafka extern void Init(char *host, char *topic, uintptr_t callback); extern int Start(); extern void Stop(); * /
       
      
import "C"

Copy the code

First, if you do not write a separate.h header file, you will need to define the function interface specified above in the cGO comment code. The function interface specified above needs to use the extern keyword. And if c-related data types are used, the associated include header is essential.

Libkafka. so is located in the libs/ directory of the current project, so we declare the link package to find the path -l./ libs-lkafka, of course, you can put it directly in the system directory, without this step.

The sendmsg_callback() function is the convention’s callback function and is the Go exposed interface, so this needs to be defined and implemented in Go:

//export sendmsg_callback
func sendmsg_callback(callback C.uintptr_t, content *C.char) {
   callFunc := cgo.Handle(callback).Value().(func(content string))
   callFunc(C.GoString(content))
   C.free(unsafe.Pointer(content))
}

func GoCallback(content string) {
   fmt.Println("Go Callback String", content)
}
Copy the code

In sendMSG_callback we look directly at the current callback function index to find the type of Go passed in Init (in this case a function) and then execute the function, which triggers the execution of the function GoCallback(). In practice, there is no need to use GoCallback if the callback before C is only one-to-one. The body logic can be done directly in sendMSG_callback. This is mainly done to demonstrate how the service can access different callbacks in multiple scenarios.

Finally, there is the main library entry code to use directly:

func main(a) {
   callback := cgo.NewHandle(GoCallback)
   C.Init(C.CString("127.0.0.1:9093"), C.CString("queue_test"), C.uintptr_t(callback))
   C.Start()

   time.Sleep(time.Second * 10)
   println("Ready to stop")
   C.Stop()
}
Copy the code

Cgo.newhandle is used to register a callback object (in this case, it is a function, you can also design for other data types) passed to C, which will pass back the ID when appropriate, and then use the ID to find the corresponding data type.

C.init calls are initialized directly, C.start is responsible for starting the task, and C.top prepares the reclaim task. Finally compile and run:

# build
go build -o bin/app main.go

#run
LD_LIBRARY_PATH=./libs ./bin/app 
Copy the code

Not surprisingly, every 5 seconds, sendmsg_callback receives string data returned by the C core library until it is stopped by Stop(), and that’s how it works.

Pay attention to

In fact, master the whole CGO development process, the rest of the matter is not complicated. However, it is important to note that the memory destruction of pointer types such as strings, arrays, and even more complex types between the two languages must be considered. The CString passed by Go to C is the space created in heap memory, which must be freed eventually, just as in the Stop() function implemented by C above, we free all the String data passed.

If sendmsG_callback returns a pointer to a * C.char string, do we need to manually free the memory?