If we check, what kind of code is generated for GO functions with a string type parameter, which are exported for access from C,
//export MyGoCallback func MyGoCallback(param1 string) { … }
we will be able to see that param1 type in the generated function is GoString. This is a structure that is declared in the included _cgo_export.h file, which is generated during the construction as
typedef struct { const char *p; GoInt n; } GoString;
If a string is a GoString structure, why can’t we just transfer its address to C instead of allocating memory, copying a string into the allocated buffer and releasing the memory after using it?
The attached example implements the string transfer scheme without memory allocation. At the same time string object address is being transmitted into the function in C as unsafe.Pointer and then we receive direct access to the string data after casting the type to a pointer in GoString. It is necessary to note that the string in utf8 encoding and does not terminate with a zero.
Implementation
Module code in GO (gostring.go):
package main // extern void Test(void* p); import "C" import "unsafe" func main() { str := "test test" C.Test(unsafe.Pointer(&str)) }
Function code Test() in C (test.c)::
#include <_cgo_export.h> #include <string.h> #include <stdio.h> void Test(void* p) { GoString* gs = (GoString*)p; char buf[64]; int n = gs->n < sizeof(buf) - 1 ? gs->n : sizeof(buf) - 1; strncpy(buf, gs->p, n); buf[n] = '\0'; printf("received: %s\n", buf); }
As a result of the example build by a command
go build .
and after starting the application we can see the following on the screen:
received: test test
Test function cuts the string if its length exceeds 63 characters. In a real situation it is necessary to use a larger buffer and use dynamically allocated buffer if its size is not sufficient. You can find a full implementation of functions for using received GoString pointer in the attached files gostr2c.c and gostr2c.h. If you use macros from gostr2c.h, the Test function can be rewritten as:
void Test(void* p) { GO2C(p); printf("received: %s\n", pp); GO2C_FREE(p); }
Conclusion
Why is this method not documented in GO? Will the proposed scheme always work?
To be continued in Part 2…