Skip to content

Instantly share code, notes, and snippets.

@helinwang
Last active March 7, 2024 01:23
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save helinwang/2c7bd2867ea5110f70e6431a7c80cd9b to your computer and use it in GitHub Desktop.
Save helinwang/2c7bd2867ea5110f70e6431a7c80cd9b to your computer and use it in GitHub Desktop.
How to call go from c with string (char *) as the parameter without making a copy

Here is the gist for showing how to send string from c to go without a copy, for sending an float32 array from c to go without copy, please see here

Below is the documentation for converting between a c string and a go string:

// From https://golang.org/cmd/cgo/
// A few special functions convert between Go and C types by making copies of the data. 
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char

// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer

// C string to Go string
func C.GoString(*C.char) string

// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string

// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

According to

A few special functions convert between Go and C types by making copies of the data.

we need to avoid calling C.GoString when converting c string to go string if we want to save a copy.

Here is the comparison code:

foo.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "foo.h"

int main() {
  char buf[10];
  GoString go_str;

  for (int i = 0; i < 10; i++) {
    buf[i] = 'a';
  }
  buf[9] = 0;
  
  go_str.p = buf;
  go_str.n = 10;
  char *r = bar(go_str, buf);

  printf("return: %s\n", r);
  free(r);
  sleep(3);

  for (int i = 0; i < 10; i++) {
    buf[i] = 'b';
  }
  buf[9] = 0;
  
  sleep(100000000);
  return 0;
}

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-archive foo.go
gcc foo.c foo.a -o 123 && ./123

Output:

a: aaaaaaaaa
b: aaaaaaaaa
c: aaaaaaaaa
d: aaaaaaaaa
a: aaaaaaaaa
b: aaaaaaaaa
c: aaaaaaaaa
d: aaaaaaaaa
a: bbbbbbbbb
b: bbbbbbbbb
c: bbbbbbbbb
d: aaaaaaaaa
a: bbbbbbbbb
b: bbbbbbbbb
c: bbbbbbbbb
d: aaaaaaaaa
a: bbbbbbbbb
b: bbbbbbbbb
c: bbbbbbbbb
d: aaaaaaaaa

You will see that row "d:" never changed to "bbbbbbbbb" since C.GoString made a copy when converting c string to go string.

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