6

I'm trying to use NativeCall to interact with some C functions.

I have a simple C struct, and a function that wants an array of them.

struct foo {
    int x;
    char *s;
};

struct foo foo_array[3];

foo_array[0].x = 12;
foo_array[0].s = "foo";
foo_array[1].x = 27;
foo_array[1].s = "bar";
foo_array[2].x = -1;

void somefunc(foo_array);

I've tried a bunch of ways, but can't seem to get it quite right.

class foo is repr('CStruct') {
    has int32 $.x;
    has Str $.s
};

sub somefunc(CArray[foo]) is native { * }

my @foo-array := CArray[foo].new;

@foo-array[0] = ???
@foo-array[1] = ???
@foo-array[2] = ???

somefunc(@foo-array);

How do I properly create an object of class foo and set their values, and how do I make an array of them suitable for passing?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Curt Tilmes
  • 3,035
  • 1
  • 12
  • 24

2 Answers2

5

As far as I'm aware, there's no built-in way to do this. However, there's enough rope to hang yourself build a workaround:

role StructArray[Mu:U \T where .REPR eq 'CStruct'] does Positional[T] {
    has $.bytes;
    has $.elems;

    method new(UInt \n) {
        self.bless(bytes => buf8.allocate(n * nativesizeof T), elems => n);
    }

    method AT-POS(UInt \i where ^$!elems) {
        nativecast(T, Pointer.new(nativecast(Pointer, $!bytes) + i * nativesizeof T));
    }

    method pointer {
        nativecast(Pointer[T], $!bytes);
    }
}

This should allow the following to work:

my @foo-array := StructArray[foo].new(10); # 'array' with 10 elements
@foo-array[0].x = 42;

Interaction with C functions is possible by passing @foo-array.pointer to a parameter of type Pointer[foo]. As structures are passed by pointer as well, you could also pass @foo-array[0] to a parameter of type foo for the same effect.

Christoph
  • 164,997
  • 36
  • 182
  • 240
  • 1
    That lets me assign to my int32 with @foo-array[0].x = 42, but I get "Cannot modify an immutable Str" for the Str. – Curt Tilmes Apr 22 '17 at 01:28
  • 1
    @CurtTilmes: there are at least two ways to get around this: (1) write a C function that does the assignment (2) use a pointer-sized integer as a pointer substitute to which you assign `+nativecast(Pointer, "???".encode)`; in either case, care has to be taken so that the gc won't collect the objects while they are in use on the C side – Christoph Apr 22 '17 at 01:37
  • I changed it from 'Str' to 'size_t' and set it to nativecast(Pointer, CArray[uint8].new($mystring.encode)).Int That seems to work. I'll try it without the CArray. – Curt Tilmes Apr 22 '17 at 01:43
  • +nativecast(Pointer, $mystring.encode) doesn't nul terminate the string. +nativecast(Pointer, CArray[uint8].new($mystring.encode)) works. – Curt Tilmes Apr 22 '17 at 01:47
  • 1
    you could manually terminate the string, eg `"$mystring\0".encode` should do it; you should also store the `CArray` if you go with your version (or the buffer returned by `encode` in mine) in some variable to keep it alife as long as you need it – Christoph Apr 22 '17 at 01:49
  • Yep, I just did that. Works fine. I will ensure the variables are marked in use long enough that the GC won't mess with them. – Curt Tilmes Apr 22 '17 at 01:54
  • Thank you. I understand this a bit better now. – Curt Tilmes Apr 22 '17 at 01:55
2

The following code shows how to pass a pointer to byte and pointer to wchar to a windows api function.

I didn't have a case yet where I need to pass in an array of structures, but I don't see why the same technique would not apply. Most important: You must make sure you allocate memory for your data!

use NativeCall;

constant UINT    := uint32;
constant BOOL    := uint32;
constant BYTE    := uint8;
constant WCHAR   := uint16;
constant int     := int32;

constant LPWTSTR      := CArray[WCHAR];
constant PBYTE        := CArray[BYTE];

my $virtual-keycode = 0xBC; # comma

sub SetConsoleCP(UINT) is native('Kernel32') returns BOOL { * };
sub SetConsoleOutputCP(UINT) is native('Kernel32') returns BOOL { * };

# winapi: int ToUnicode( UINT wVirtKey, UINT wScanCode, const PBYTE lpKeyState, LPWSTR pwszBuff, int cchBuff, UINT wFlags );
sub ToUnicode(UINT, UINT, PBYTE is rw, LPWTSTR is rw, int32, UINT) is native("User32") returns int32 { * };

my @kbs := CArray[BYTE].new(0 xx 256);
my @buf := CArray[WCHAR].new(0 xx 2);

say "Can't set Codepage" unless SetConsoleCP(65001) && SetConsoleOutputCP(65001);

say "Got Unicode" ~ @buf[0] ~ " -> " ~ @buf[0].chr
    if ToUnicode( $virtual-keycode, 0, @kbs, @buf, 2 ,0);   
Holli
  • 5,072
  • 10
  • 27