9

I'm am trying to declare the following C struct in Perl 6:

struct myStruct
{
    int A[2]; //<---NEED to declare this
    int B;
    int C;
};

My problem is that I don't know how to declare the int A[2]; part using the built in NativeCall api.

So for what I have is:

class myStruct is repr('CStruct') {
    has CArray[int32] $.A;
    has int32 $.B;
    has int32 $.C;
};

However, I know that the has CArray[int32] $.A; part is wrong as it does not declare a part in my struct that takes up ONLY 2 int32 sizes.

annoying_squid
  • 513
  • 5
  • 10

4 Answers4

6

Update 2: It turned out that this didn't work at the time I first posted this answer, hence the comments. I still haven't tested it but it must surely work per Tobias's answer to Passing an inlined CArray in a CStruct to a shared library using NativeCall. \o/


I haven't tested this, but this should work when using Rakudo compiler release 2018.05:

use NativeCall;
class myStruct is repr('CStruct') {
    HAS int32 @.A[2] is CArray;
    has int32 $.B;
    has int32 $.C;
}
  • HAS instead of has causes the attribute to be inline rather than a pointer;

  • int32 instead of int is because the Perl 6 int type isn't the same as C's int type but is instead platform specific (and usually 64 bit);

  • @ instead of $ marks the attribute as being Positional ("supports looking up values by index") instead of scalar (which gets treated as a single thing);

  • [2] "shapes" the Positional data to have 2 elements;

  • is CArray binds a CArray as the Positional data's container logic;

  • This commit from April this year wired up the is repr('CStruct') to use the declared attribute information to appropriately allocate memory.

Fwiw I found out about this feature from a search of the #perl6 logs for CArray and found out it had landed in master and 2018.05 from a search of Rakudo commits for the commit message title.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • 1
    Nice solution! I tested this now, and it seems to work. Only problem I had is how to initialize the array before passing it to the C library. I tried `my $s = myStruct.new(); $s.A[0] = 1` but it only outputs garbage for `A` when printed from within my C testing library. Maybe I did something wrong.. I can set the fields `B` and `C` fine in Perl 6, and they prints correctly from C. The `sizeof( struct myStruct)` in C is 16 bytes, and in Perl 6 `nativesizeof(...)` also gives 16 bytes.. – Håkon Hægland Jun 07 '18 at 16:11
  • @HåkonHægland Try using `.ASSIGN-POS` rather than `[...]`. – raiph Jun 07 '18 at 22:17
  • @raiph I tried `$s.A.ASSIGN-POS(0, 1)` but it still does not work for me. I wonder what is going on here? I have tried both Rakudo versions 2018.04.01 and 2018.05 but no difference. Did it work for you? – Håkon Hægland Jun 08 '18 at 04:04
  • 1
    No, I haven't tested it at all. I don't really know C or the nativecall stuff, I just poked around for what appeared to be a solution to the struct layout/allocation, not read/writing, and while I was at that saw a hint about using `.ASSIGN-POS` instead of `[...]`. I've gotta to run to work, and as I say, I don't know C etc., but if iI had time I think I'd focus on timotimo's comments in the question you linked on the OP's question and if I had no joy after doing that, I'd pop on #perl6-dev and ask. Good luck to you and OP. – raiph Jun 08 '18 at 06:14
  • Using your suggestion above If you compare the example above with `HAS int32 @.A[200] is CArray;` with `HAS int32 @.A[2] is CArray;`, you will get the same size. So this doesn't work properly for native operations. Use `nativesizeof` to get the native size. – annoying_squid Jun 08 '18 at 13:13
  • 1
    Follow-up question: [Passing an inlined CArray in a CStruct to a shared library using NativeCall](https://stackoverflow.com/q/50777614/2173773) – Håkon Hægland Jun 09 '18 at 19:17
4

See Declaring an array inside a Perl 6 NativeCall CStruct

There are other ways, but the easiest is instead of an array, just declare each individual item.

class myStruct is repr('CStruct') {
    has int32 $.A0;
    has int32 $.A1;
    ... as many items as you need for your array ...
    has int32 $.B;
    has int32 $.C;
};
Curt Tilmes
  • 3,035
  • 1
  • 12
  • 24
  • 1
    So far this appears to be the only 'workable' solution but it's inconvenient. Having to copy, paste, and rename members takes time, and access to data is very impractical compared to accessing an array which is supported by the `[ ]` indexing syntax. – annoying_squid Jun 08 '18 at 13:17
3

So I've done some experimentation on this and taken a look at the docs and it looks like the CArray type doesn't handle shaping the same way as Perl6 Arrays.

The closest thing you've got it the allocate constructor that preallocates space in the array but it doesn't enforce the size so you can add more things.

Your class definition is fine but you'd want to allocate the array in the BUILD submethod.

https://docs.raku.org/language/nativecall#Arrays

(Further thought)

You could have two objects. One internal and one for the struct.

The Struct has a CArray[int32] array. The internal data object has a shaped int32 cast array my int3 @a[2]. Then you just need to copy between the two.

The getters and setter live on the main object and you use the struct object just when you want to talk to the lib?

Scimon Proctor
  • 4,558
  • 23
  • 22
  • " it looks like the CArray type doesn't handle shaping the same way as Perl6 Arrays" - I wonder if they intentionally left out this feature to prevent buffer overflow problems. At least with a non-fixed array in Perl, it can grow .. but a fixed could break the security goals of Perl 6. – annoying_squid Jun 06 '18 at 20:34
1

This does not really declare an array of fixed size, but puts a constraint on the size of its value: You can try and use where to constraint the size of the array. CArray is not a positional (and thus cannot be declared with the @ sigil) but it does have the elems method.

use NativeCall; 
my CArray[int32] $A where .elems < 2

That is, at least, syntactically correct. Whether that breaks the program in some other place remains to be seen. Can you try that?

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
  • 1
    Here's the problem : `say @A.WHAT;` `(Array[CArray[int32]])` By using `@` you get an Array of CArrays. And you can't shape a Scalar. Nor can you bind a CArray to shaped array. You can call `CArray.new(:shape(2))` but the constructor doesn't handle it. – Scimon Proctor Jun 07 '18 at 08:24
  • @Scimon That's right. I'll see how I can change that. – jjmerelo Jun 07 '18 at 08:45
  • @Scimon Changed to other possible way of doing it. – jjmerelo Jun 07 '18 at 10:08
  • 1
    `where` is a run-time construct (that guards what can be assigned/bound to `$A`) that'll always be too late to guide memory layout at compile-time, which is the OP's issue. – raiph Jun 07 '18 at 11:07
  • @raiph I don't see other way of doing this in compile time, except by putting the elements of the array together, which might be impractical for big arrays. This is simply not foreseen in the original syntax. – jjmerelo Jun 07 '18 at 11:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172701/discussion-between-jjmerelo-and-raiph). – jjmerelo Jun 07 '18 at 15:31