Search code examples
gocgo

Correct Go type to pass to C function?


I'm porting some server code I wrote in C over to Go and it uses an encryption library I really don't want to rewrite. Instead I'm trying to use Cgo to write a wrapper so that the rest of my code can call it more easily. Here's part of the header for the lib:

// encryption/encryption.h
#define CRYPT_BBCFG  1

typedef struct {
    // ...bunch of fields...
    uint32_t bb_posn; 
} CRYPT_SETUP;

int CRYPT_CreateKeys(CRYPT_SETUP* cs, void* key, unsigned char type);

And here's the proof-of-concept snippet I'm trying to get to work:

package goserv

//#include "encryption/encryption.h"
import "C"

func main() {
    cdata := new(C.struct_CRYPT_SETUP)
    key := make([]byte, 48)
    C.CRYPT_CreateKeys(cdata, &key, C.CRYPT_BLUEBURST)
}

I defined a test function (int test() { return 1; }) in the header and have no problem calling that from my code (via C.test()) nor referencing any of the #defined'd constants (C.CRYPT_BBCFG) but get the following error when I attempt to run go install goserv:

Undefined symbols for architecture x86_64:
  "_CRYPT_CreateKeys", referenced from:
   __cgo_e89359206bf1_Cfunc_CRYPT_CreateKeys in goserv.cgo2.o
       (maybe you meant: __cgo_e89359206bf1_Cfunc_CRYPT_CreateKeys)
ld: symbol(s) not found for architecture x86_64

At this point I'm assuming I'm just not calling the function with the correct arguments. I was under the impression that cdata is of type *C.struct_CRYPT_SETUP, key should be *byte (though it doesn't work without the & either) and C.CRYPT_BLUEBURST of type...something. Trying C.uchar(CRYPT_BLURBURST) also doesn't change anything.

Any suggestions on getting this code to compile?

Edit: Forgot my platform, I'm running Mac OS X 10.10

Edit2 (SOLVED): Jsor's point about using unsafe.Pointer with the address of the first element of key helped but I also had to move my C source files into the same directory as my Go file. There was another type error resulting from using C.struct_CRYPT_DATA instead of C.CRYPT_DATA, so if anyone else runs into errors like this:

./goserv.go:18: cannot use cdata (type *C.struct_CRYPT_SETUP) as type *C.struct___0 in argument to _Cfunc_CRYPT_CreateKeys

Then remove the struct_ prefix (though the cgo docs say that's how to directly reference C struct types)


Solution

  • You probably want &key[0], otherwise you're pointing to the Slice Header, which is a struct of 3 values. You end up getting a pointer to a pointer to your buffer, which is probably not what you want.

    In general, the pointer to the backing buffer of a slice is &slice[0], not &slice.

    As a standard disclaimer: be warned about passing Go-allocated buffers into C. Almost everybody does it, (including me) but it may not be a good idea, and may stop being supported at some point. It's probably fine as long as you keep a reference to the data for the entire duration it's in C, and don't store the pointer for later use on the C side, but it's plausible you'll end up with weirdness due to the Go 1.3 stack moving GC.

    Update: Something I just remembered -- void* in C is analogous to Go's unsafe.Pointer. So your call should be:

    C.CRYPT_CreateKeys(cdata, unsafe.Pointer(&key[0]), C.CRYPT_BLUEBURST)
    

    Another issue may be if you usually link an external library with -l. For this, use

    // #cgo LDFLAGS: -llibname
    

    In the area right above import "C" where you do your includes.

    Edit2: Also, looking at it more, it looks like the function CRYPT_CreateKeys is not defined in the header file -- only the function prototype. Make sure if it's defined in a separate .c file to declare it extern. Not doing so will trip up Go.