-1

I got pointer to C-char array I got from C++ dll.How can i get string data?

I am using this code to get the pointer to a memory address:

Go:

dll, err := windows.LoadDLL("someDll.dll")
if err != nil {
    panic(err)
}
proc := dll.MustFindProc("Function")
response, _, _ := proc.Call()

C++ code:

extern "C" __declspec(dllexport) char* Function() {
    char text[] = "something";
    return text;
}

I've tried this:

fmt.Println(*(*string)(unsafe.Pointer(&response)))

And this:

var data []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
sh.Data = response
sh.Len = 9
sh.Cap = 9
fmt.Println(data, string(data))

but nothing works for me.

noremac09
  • 1
  • 2
  • I’m not sure what you’re trying to do in these examples. Are you looking for `C.GoString`? – JimB Nov 23 '19 at 19:08
  • I am trying to read string data from memory. You've said that I can use `C.GoString`. How can I use it? – noremac09 Nov 23 '19 at 19:18
  • 1
    The signature of C.GoString is fairly clear, `func C.GoString(*C.char) string`. Pass it a pointer to the first char, as is normal usage in C. It will scan up to the first null byte and convert that, less the null byte, into a Go string. – Brandon Dube Nov 24 '19 at 03:11

1 Answers1

1

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 chars

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Malloc doesn't guarantee that allocated space will be zeroed, so do not suggest that for regular cases, it causes undefined behavior as well. – Laevus Dexter Nov 25 '19 at 02:25
  • @LaevusDexter: I'm not suggesting that *you* call malloc. I'm suggesting that if you import some existing C library, it may *already* call malloc. If it did, you need to know to call, or have it call, free afterward. The same goes when you invoke existing C++ code, however you do it. (If your case is particularly tricky, consider using SWIG, though I have not experimented with SWIG.) – torek Nov 25 '19 at 05:12