Skip to content

Instantly share code, notes, and snippets.

@helinwang
Last active December 2, 2024 10:11
Call go (or c) code from python with string as argument and string as return value

go code (foo.go) compiled into a shared library and a c header (foo.h) is generated.

For calling go from c, please see here

Arguments

The code below shows two ways of passing string parameter to go function:

  1. Using GoString structure as argument, without making a copy in go code: no conversion to go string needed.
  2. Using c_char_p as argument, making a copy in go code when converting to go string.

When using the first method without the copy, I don't know how python will do the memory management with the pointer passed into go. So the second method is preferred.

Return Value

The go function returns a c string, and the reciving side is responsible for managing the lifecycle of the c string. It seems that python does not free it.

Code and Build Command

foo.py:

from ctypes import *
from ctypes import cdll
import time

class go_string(Structure):
    _fields_ = [
        ("p", c_char_p),
        ("n", c_int)]

lib = cdll.LoadLibrary('./foo.so')

def bar(str):
    b = go_string(c_char_p(str), len(str))
    lib.bar.restype = c_char_p
    a = lib.bar(b, c_char_p(str))
    print a

bar("haha")
bar("hoho")
time.sleep(100)

foo.go:

package main

import (
	"C"
	"fmt"
	"time"
)

//export bar
func bar(a string, b *C.char) *C.char {
	c := a
	d := C.GoString(b)
	go func() {
		for {
			time.Sleep(time.Second)
			fmt.Println("a:", a)
			fmt.Println("b:", C.GoString(b))
			fmt.Println("c:", c)
			fmt.Println("d:", d)
		}
	}()
	return C.CString("hello from go")
}

func main() {}

Command:

go build -buildmode=c-shared -o foo.so foo.go
python foo.py

Appendex:

Generated foo.h:

/* Created by "go tool cgo" - DO NOT EDIT. */

/* package command-line-arguments */

/* Start of preamble from import "C" comments.  */




/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

typedef struct { const char *p; GoInt n; } GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif


extern char* bar(GoString p0, char* p1);

#ifdef __cplusplus
}
#endif
@wangkuiyi
Copy link

A digested version here:

The Go Source

// a.go
package main

// #include <string.h>
import "C"

//export add
func add(left, right int) int {
	return left + right
}

//export concat
func concat(left, right *C.char) *C.char {
	return C.CString(C.GoString(left) + C.GoString(right))
}

func main() {
}

To build it into a shared libarary:

go build -buildmode=c-shared -o liba.so a.go

The Python Code

import ctypes
lib = ctypes.cdll.LoadLibrary('./liba.so')
lib.concat.restype = ctypes.c_char_p
print(lib.concat("apple", "orange"))

@softarts
Copy link

need to free the buffer?

@hqnguyen99
Copy link

@stackquest
Copy link

@wangkuiyi that code returns b'ao' instead appleorange.

@kadnan
Copy link

kadnan commented Jan 3, 2023

@wangkuiyi same to me, how did you resolve it?

@kadnan
Copy link

kadnan commented Jan 3, 2023

@wangkuiyi you must call encode("utf-8") with the string being passed from Python as it will convert it into bytes

So print(lib.concat("apple", "orange")) will be changed to print(lib.concat("apple".encode("utf-8"), "orange".encode("utf-8")))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment