Create array of C struct and pass struct pointer to C function - go

I want to make a wrapper for my C function, which takes pointer to C struct as parameter.
In my Go code, I tried two approaches to allocate the space for C struct:
bps := make([]_Ctype_T32_Breakpoint, max) (1)
C.MyFunc((*_Ctype_T32_Breakpoint)(unsafe.Pointer(&bps[0]), C.int(max))
bps := make([]C.struct_T32_Breakpoint, max) (2)
C.MyFunc((*C.struct_T32_Breakpoint)(unsafe.Pointer(&bps[0]), C.int(max))
For the (1) method, it works, but for (2) method, I got error message :
cannot use (*[0]byte)(unsafe.Pointer(&bps[0])) (type *[0]byte) as type *_Ctype_T32_Breakpoint in function argument
Why method (2) created type of *[0]byte instead of *C.struct_T32_Breakpoint, and it seems the cast to *C.struct_T32_Breakpoint doesn't work.
What's the difference of using method (1) and (2)?
Thanks.
Test Code
File: t32.h
#ifndef __T32_H__
#define __T32_H__
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned int dword;
typedef struct t32_breakpoint {
dword address;
byte enabled;
dword type;
dword auxtype;
} T32_Breakpoint;
int T32_GetBreakpointList( int *, T32_Breakpoint*, int );
#endif /* __T32_H__ */
File: remote.c
#include "t32.h"
int T32_GetBreakpointList (int* numbps, T32_Breakpoint* bps, int max)
{
return 0;
}
File : t32.go
package t32
// #cgo linux,amd64 CFLAGS: -DT32HOST_LINUX_X64
// #cgo linux,386 CFLAGS: -DT32HOST_LINUX_X86
// #cgo windows,amd64 CFLAGS: -D_WIN64
// #cgo windows,386 CFLAGS: -D_WIN32
// #cgo windows CFLAGS: -fno-stack-check -fno-stack-protector -mno-stack-arg-probe
// #cgo windows LDFLAGS: -lkernel32 -luser32 -lwsock32
// #include "t32.h"
// #include <stdlib.h>
import "C"
import (
"errors"
"unsafe"
)
const (
_INVALID_U64 = 0xFFFFFFFFFFFFFFFF
_INVALID_S64 = -1
_INVALID_U32 = 0xFFFFFFFF
_INVALID_S32 = -1
_INVALID_U16 = 0xFFFF
_INVALID_S16 = -1
_INVALID_U8 = 0xFF
_INVALID_S8 = -1
)
type BreakPoint struct {
Address uint32
Enabled int8
Type uint32
Auxtype uint32
}
func GetBreakpointList(max int) (int32, []BreakPoint, error) {
var numbps int32
// bps := make([]_Ctype_T32_Breakpoint, max) // Method (1), can compile
// code, err := C.T32_GetBreakpointList((*C.int)(&numbps), (*_Ctype_T32_Breakpoint)(unsafe.Pointer(&bps[0])), C.int(max))
bps := make([]C.struct_T32_Breakpoint, max) // Method (2) can't compile
code, err := C.T32_GetBreakpointList((*C.int)(&numbps), (*C.struct_T32_Breakpoint)(unsafe.Pointer(&bps[0])), C.int(max))
if err != nil {
return _INVALID_S32, nil, err
} else if code != 0 {
return _INVALID_S32, nil, errors.New("T32_GetBreakpointList Error")
}
if numbps > 0 {
var gbps = make([]BreakPoint, numbps)
for i := 0; i < int(numbps); i++ {
gbps[i].Address = uint32(bps[i].address)
gbps[i].Auxtype = uint32(bps[i].auxtype)
gbps[i].Enabled = int8(bps[i].enabled)
gbps[i].Type = uint32(bps[i]._type)
}
return numbps, gbps, nil
}
return 0, nil, nil
}

There are two reasons that the second snippet doesn't compile.
The first is that the capitalization doesn't match. The C declaration declares struct t32_breakpoint, but the Go code refers to struct T32_Breakpoint. C is case-sensitive, but it permits creating a pointer to a struct that has not been defined. So that is what CGo thinks you are doing. This pointer can't be dereferenced, since the contents and size of the struct are not known. It's basically equivalent to a void pointer, but with stronger typing. Cgo treats it as a void pointer, translating it as *[0]byte, i.e. a pointer to a zero-sized object. But unlike C, Go doesn't allow passing a void pointer to just any function that takes a pointer. So it has a type mismatch.
The other issue is that if you want to pass a struct t32_breakpoint* instead of a T32_Breakpoint*, you will need to change your function declaration in C. The distinction between the two types is probably not significant in C, but it is in Go, since Go has stronger typing than C.

Related

Can't find struct's some field with pragma pack, 64 bits integer

package main
/*
#include <inttypes.h>
#pragma pack(1)
typedef struct _sss {
uint64_t some; // ok
uint32_t wow; // ok
uint64_t some2; // not found
uint64_t some3; // not found
uint64_t some4; // not found
uint32_t some5; // ok
} Type;
#pragma pack()
*/
import "C"
import (
"fmt"
)
func main() {
s := C.Type{}
s.some = 10;
s.wow = 10;
s.some2 = 10;
s.some5 = 10;
fmt.Println("Hello, playground")
}
I declared struct named C.Type.
If pragma pack(1) and 64 bits members are used together.
╭─dire#dire-81w4 ~/workspace
╰─go run test.go 2 ↵
# command-line-arguments
./test.go:25:6: s.some2 undefined (type _Ctype_struct__sss has no field or method some2)
Compilation is possible by removing pragma pack(1).
Go 1.15 version is being used. Do you know why?
I found answer about my question.
If the fields of a C struct are aligned such that they can't be represented by a Go struct, then they cannot be accessed directly from cgo. You will have to write functions in C to read and write those fields.
I nowdays am too busy to comment. It's late, but it's answered.

C references passed to Go can not recognize typedef void*?

c.h
typedef void* MVar;
C_FUNC(
MVar* myvar //[out], return value
)
test.go
var cvar unsafe.Pointer
_ = C.C_FUNC(&cvar)
when I run test.go, it tells me
cannot use _cgo5 (type *unsafe.Pointer) as type *_Ctype_MVar
in argument to _Cfunc_C_FUNC
In this document Command cgo: Go references to C, it says "The C type void* is represented by Go's unsafe.Pointer."
cannot use _cgo5 (type *unsafe.Pointer) as type *_Ctype_MVar
in argument to _Cfunc_C_FUNC
The Go toolchain says you have a type mismatch.
An equivalent, working example, with matching types,
package main
import (
"fmt"
"unsafe"
)
/*
typedef void* pv_t;
void cfunc(pv_t *p);
#include <stdio.h>
int i;
void cfunc(pv_t *p) {
i = 42;
*p = &i;
printf("%p %p %d\n", p, *p, *(int*)(*p));
}
*/
import "C"
func main() {
var cptr unsafe.Pointer
C.cfunc((*C.pv_t)(&cptr))
fmt.Println(&cptr, cptr, *(*C.int)(cptr))
}
Output:
$ go run so.go
0xc000010028 0x592d18 42
0xc000010028 0x592d18 42
$

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:
Since the C struct has the field char* error_string, is error_string *byte in my GO struct correct?
Should I use []byte or something else?
How do I read the content of the error_string field?
fmt.Println(err.error_string) prints the memory address
fmt.Println(*err.error_string) prints always "101"
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)

How to return a slice in Go and calling from C?

I am trying to use cgo to use Go package in C code. Following is a piece of my code:
func LinearTransformToUInt8(frame []int64, winWidth int, winCenter int) []uint8 {
var transformed []uint8
// my cool code
return transformed
}
However, when calling from C, it says
panic: runtime error: cgo result has Go pointer
I believe the problem is the returned []uint8 is a Go type, which should be replaced by a C type. However, I don't know how to achieve it. Please help!
main.go
package main
import (
"C"
"unsafe"
)
import (
"reflect"
)
func main() {
}
//export phew
func phew() uintptr {
res := make([]uint8, 2)
for i := 0; i < 2; i++ {
res[i] = uint8(i + 1)
}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&res))
return hdr.Data
}
main.c
#include <stdio.h>
#include <inttypes.h>
#include "libtemp.h"
int main(){
uintptr_t resPtr = phew();
uint8_t *res = (uint8_t*)resPtr;
for (int i = 0; i < 2; i++){
printf("%d\n", res[i]);
}
printf("Exiting gracefully\n");
}
You cannot pass a Go pointer which contains other Go Pointer, slice,string,channel,function, interface, map contain pointers.
So one cannot pass them around, rules to passing around pointers are documented here and go's representation of basic types is documented here.
But some Go contributors were saying, one shouldn't return a Go pointer to C code in the first place.

Using strfmon with cgo

I'm trying to use the C function strfmon using cgo.
The example C code that works is:
#include <stdio.h>
#include <monetary.h>
int main(void)
{
char str[100];
double money = 1234.56;
strfmon(str, 100, "%i", money);
printf("%s\n", string);
}
The Go code I've written so far is:
package main
// #cgo CFLAGS: -g -Wall
// #include <stdlib.h>
// #include <monetary.h>
import "C"
import (
"fmt"
)
func main() {
str := [100]C.char{}
var money C.double = 1234.56
C.strfmon(str, 100, "%i", money)
fmt.Printf("%+v\n", str)
}
When I go run main.go I get the following error:
./main.go:14:2: unexpected type: ...
I believe the ... refers to the variadic argument in strfmon but I'm not sure how to work around that from Go.
According to the cgo command documentation:
Calling variadic C functions is not supported. It is possible to circumvent this by using a C function wrapper.
And strfmon(3p) is indeed a variadic function as indicated by the ... characters in the signature:
ssize_t strfmon(char *restrict s, size_t maxsize,
const char *restrict format, ...);
As such, you can create a wrapper function in C which has a fixed number of arguments and calls strfmon(...) as needed, for example:
package main
// #cgo CFLAGS: -g -Wall
//
// #include <locale.h>
// #include <monetary.h>
// #include <stdlib.h>
//
// size_t format_amount(char * s, size_t maxsize, char * format, double amount)
// {
// setlocale(LC_ALL, "en_US");
// return strfmon(s, maxsize, format, amount);
// }
//
import "C"
import "fmt"
import "unsafe"
const SIZE = 100
func main() {
str := C.CString(string(make([]byte, SIZE)))
money := C.double(1234.56)
format := C.CString("[%n]")
C.format_amount(str, SIZE-1, format, money) // Call our wrapper here.
fmt.Printf("OK: %s\n", C.GoString(str))
// OK: [$1,234.56]
C.free(unsafe.Pointer(str))
C.free(unsafe.Pointer(format))
}

Resources