yongzhy
yongzhy

Reputation: 977

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

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
}

Upvotes: 3

Views: 2086

Answers (1)

andybalholm
andybalholm

Reputation: 16160

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.

Upvotes: 2

Related Questions