Your example C++ code is ... not good, as it returns a pointer to a variable whose storage has been released. Use static char text[]
or similar to fix it. Real C or C++ code will return either a pointer to memory that should be released with free
, or not, and you will need to handle these cases too.
Still, you can get some or most of the way to what you want with cgo
, which is built in to Go these days. Note that when using cgo, you need a separate import "C"
statement, generally prefixed with a comment. (You cannot put this import into the regular import section.)
Let's cover all this in several parts.
No DLL, C code that returns pointer to static
storage-duration char
s
If we ignore all the DLL aspects, and assume that you can link directly against a particular C function that returns char *
, the example Go code to work with it would be:
package main
/*
extern char *Function();
*/
import "C"
import (
"fmt"
)
func WrapFunction() string {
return C.GoString(C.Function())
}
func main() {
fmt.Println(WrapFunction())
}
I put a sample Function()
in a file by itself named cgo.go
in a directory with just the one .c
file, function.c
, as well. Here's the very simple C code for this particular Function()
:
char *Function() {
return "hello";
}
(A C string literal has type char [N]
with the integer constant N
determined at compile time, and static duration, and C has that funny rule of "decaying" an array to a pointer to its first element, so we meet our goal of returning char *
pointing to static-duration memory.)
$ go build
$ ./cgo
hello
$
C code that returns pointer to dynamically-allocated memory
Let's say instead that the C (or wrapped C++) function uses malloc
(or new
) to allocate the string, so that we must free it. Here's the updated function.c
that does so:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
char *Function() {
char *text = malloc(25); /* big enough for hello and a number */
if (text != NULL) {
srand(time(NULL)); /* should do this just once, really */
sprintf(text, "hello %d", rand() % 100);
}
return text;
}
Then we would rewrite cgo.go
as:
package main
/*
#include <stdlib.h>
extern char *Function();
*/
import "C"
import (
"fmt"
"unsafe"
)
func WrapFunction() string {
s := C.Function()
// Optional -- note that C.free(nil) is a no-op,
// and (by experimentation) C.GoString(nil) returns "".
// So, if it's OK to use "" if `C.Function` returns NULL,
// skip this test-and-panic.
if s == nil {
panic("C.Function returned NULL")
}
defer C.free(unsafe.Pointer(s))
return C.GoString(s)
}
func main() {
fmt.Println(WrapFunction())
}
Compiling and running this shows:
$ go build
$ ./cgo
hello 27
See also https://blog.golang.org/c-go-cgo and https://golang.org/cmd/cgo/.
DLLs
Both of the above assume that the C wrapper is in the build directory. Running go build
discovers this and builds function.c
to an object file, adds all the necessary runtime interface between C and Go, and invokes the function Function
directly, by name.
If you must determine the function name at runtime, you have a much more serious problem. The heart of the issue is that you cannot get assistance from cgo
to wrap the function directly, because cgo needs to see the function's name, in that comment above the import "C"
line.
See how to import a DLL function written in C using GO? for several approaches. Another one not listed there is to do the DLL lookup in a C wrapper that you write yourself. Use cgo
to pass the name of the function to look up, and all the (fixed) arguments. Then you're calling a known (compile-time) function with a known (compile-time) return type and value, which simplifies the problem. The cgo runtime wrapper will insert the appropriate runtime stack-switching for your known function, so that you do not have to use the syscall
wrappers.