Skip to content

Instantly share code, notes, and snippets.

@helinwang
Last active January 3, 2023 07:50
Show Gist options
  • Save helinwang/4f287a52efa4ab75424c01c65d77d939 to your computer and use it in GitHub Desktop.
Save helinwang/4f287a52efa4ab75424c01c65d77d939 to your computer and use it in GitHub Desktop.
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
@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