Riccardo Pedrielli
Riccardo Pedrielli

Reputation: 237

Passing a struct containing *byte to Syscall and read its content after execution

I'm using syscall.Syscall(...) to call a C method in a dll.


This is the C method signature:

SENSEI_API HSENSEI SENSEI_open(const char* sensigrafo, const char* options, SENSEI_ERR* se);

This is the SENSEI_ERR struct:

typedef struct
{
    int code;
    char* error_string;
} SENSEI_ERR;

In my GO program I declared a struct:

type senseiErr struct {
    code         int
    error_string *byte
}

And tried to call the method:

    var nargs uintptr = 3
    var err senseiErr

    ret, _, callErr := syscall.Syscall(uintptr(senseiOpen),
        nargs,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("en"))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))),
        uintptr(unsafe.Pointer(&err)),
    )

As you may have guessed, the SENSEI_open method fill the SENSEI_ERR argument with the code and the text of the error.

Now I need to read the content of that error.

err.code actually has the correct value.

About err.error_string I don't know. I'm new to GO and i have some questions:

Upvotes: 1

Views: 515

Answers (1)

Laevus Dexter
Laevus Dexter

Reputation: 492

1) I doubt that cost char* meant to be UTF16 encoded. So all what you need is just getting raw data:

sensigrafo := "en\000" // \000 = 0 = null termination, \0 does not valid
options := "\000"

...

uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&sensigrafo))
uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&options))

// *(*unsafe.Pointer) are accessing the first field of string header:
type string struct {
    data *byte
    len int
}

// same with slices
// but for them there's less ugly way:
sensigrafo := []byte("en\000")
options := []byte("\000")

uintptr(unsafe.Pointer(&sensigrafo[0]))
uintptr(unsafe.Pointer(&options[0]))

2) C's int and Golang's int might have different sizeof, so this requires cgo declaration (C.int) or manual matching with random selection (try also int32, int64 if you don't want to use cgo)

type senseiErr struct {
    code         C.int /* Golang's int32/int64 */
    error_string *byte // pointer types are same as C's void* or Golang's unsafe.Pointer
}

Wrong offset might cause error_string be empty or point to random addr.

3) To read content you have to use same methods as C does (read data until null terminated byte, considering that *byte points to first element of string), but I propose to use already implemented runtime functions:

//go:linkname gostringn runtime.gostringn
func gostringn(p *byte, l int) string

//go:linkname findnull runtime.findnull
//go:nosplit
func findnull(s *byte) int

...

error_string := gostringn(err.error_string, findnull(err.error_string))

// or cgo one:

type senseiErr struct {
    code         C.int
    error_string *C.char
}

...

error_string := C.GoString(err.error_string)

Upvotes: 2

Related Questions