7

I have a third-party C library that defines a struct similar to:

struct myStruct {
  int a;
  int b;
  char str1[32];
  char str2[32];
};

And a function that takes a pointer to this struct and populates it. I need my Perl6 native call to provide that struct, and then read the results.

So far I've got the struct defined in Perl6 as:

class myStruct is repr('CStruct') {
  has int32 $.a;
  has int32 $.b;
  has Str $.str1; # Option A: This won't work as Perl won't know what length to allocate
  has CArray[uint8] $.str2; # Option B: This makes more sense, but again how to define length?  
                     # Also, would this allocate the array in place, or 
                     #    reference an array that is separately allocated (and therefore not valid)?
}

And a native call like:

sub fillStruct(myStruct) is native('test_lib') { ... }
my $struct = myStruct.new();
fillStruct($struct); # Gives a seg fault with any variation I've tried so far

How can I make this work?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Digicrat
  • 581
  • 5
  • 13
  • 1
    Have you tried the code in the docs? https://docs.perl6.org/language/nativecall#Structs We can infer that the struct will be sized dynamically, taking into account the current size of the `CArray`. The docs demonstrate changing the elements in `TWEAK()`. So try passing it a full array instead of an empty one. (Fill it with `0` if you want it empty.) – piojo Sep 29 '17 at 10:27
  • 1
    But perl doesn't technically need to calculate the size. It just needs the memory to be available. So filling the array should do the trick. (If I recall from my C++ days, structs aren't always the same size, since you can put a variable length buffer as the last element.) – piojo Sep 29 '17 at 10:30
  • 1
    C/C++ structs are always defined at a fixed size, though they can be mapped/cast onto areas of memory that are larger and accessible via pointer manipulation. – Digicrat Sep 30 '17 at 01:40
  • I tried the recommended TWEAK function method as well, explicitly initializing each uint8 array to the correct size, however I still get a seg fault when I run it. I suspect that Perl is inserting a pointer to the string-array into the struct rather than defining the array within the struct directly as needed. – Digicrat Sep 30 '17 at 01:45
  • @raiph If that turns out to be correct, we should modify the docs to explain that the first example inserts a pointer rather than an array. – piojo Oct 01 '17 at 07:35
  • @raiph I built some examples locally to test, and `HAS` doesn't help. Including a `CArray` doesn't increase the size of the struct when measured with `nativesiveof`, either with `has` or `HAS`. I understand why `HAS` wouldn't increase the struct size (if I can't properly increase the array size), but I'm surprised it had no effect when including it with `has`. – piojo Oct 02 '17 at 05:04
  • I tried the 'HAS' approach, and as ralph says, that didn't help. It seems likely that Perl is still inserting a pointer instead of the actual array (ie: char *str1 instead of char str1[32]). I suppose the question is really are we still missing something here, or is this a bug/missing-feature in the nativecall library? – Digicrat Oct 02 '17 at 13:14
  • 3
    the functionality you need is sadly still missing from rakudo; you can get a bit of relief from the nativehelpers modules: http://modules.perl6.org/search/?q=nativehelpers - hope that helps – timotimo Oct 02 '17 at 22:54

3 Answers3

2

As others have said, there does not appear to be any way to achieve this at present.

I've resorted to defining a new C function(s) as a workaround. The function effectively acts as an accessor method returning just the fields that I need as discrete NativeCall-friendly pointers.

Hopefully the community will get to implementing proper support for this case at some point.

Digicrat
  • 581
  • 5
  • 13
1

At the time of writing, this doesn't seem to be handled.
As a workaround, my take would be to let a macro generate 32 int8, adequately placing field names.

YvesgereY
  • 3,778
  • 1
  • 20
  • 19
  • Thanks. Could you give an example of a macro that defines a variable, if possible a variable with a programmatically chosen name? Macros are totally undocumented at the moment. – piojo Oct 04 '17 at 03:54
  • actually, no need to use macros, you can just create the class using the metaobject protocol. it'll let you create a type, add attributes, then compose the class. You can probably look at my ADT module for inspiration: https://github.com/timo/adt/ – timotimo Oct 04 '17 at 13:08
1

As of July 2020, you should be able to something like this:

sub setCharArray($a, $s, $l, $c is rw) {
  die 'Too many chars!' unless $s.chars <= $l;;

  $c = $s if $c;
  my $chars = $a.encode;
  $a[$_] = $chars[$_] for ^$chars.elems;
  $a[$chars.elems] = 0 unless $s.elems == 128;
}

class A repr<CStruct> is export {
  HAS uint8 @!myString[128] is CArray;

  state Str $cached;

  method myString is rw {
    Proxy.new:
      FETCH => sub($) {
        return $cached if $cached;
        $cached = Buf.new(@!myString).decode
     },

     STORE => $, Str $s is raw {
       setCharArray(@!myString, $s, 128, $cached);
     }
  }
  
}

Let me explain:

The "HAS" declarator to define static size elements has been in NativeCall for a bit, so that's not the experimental portion. It's the "method myString" that is the tricky part. It allows the consumer of class A to set and get from the @!myString attribute as if it were a proper attribute, rather than an array.

If the data in @!myString is first being read from C, then the $cache state variable will be empty. The Str object is then created via a decoded Buf and returned. It is the hope that the complexity seen in the object implementation is then hidden from the user so that:

my $a = c_function_that_returns_A();
$a.myString;

...will work as one would expect, and that similarly:

$a.myString = 'Crab';

...will also work with no problem.

It is unfortunate that a helper function like setCharArray() needs to iterate to set @!myString, but hopefully that will change, in the future.

IMPORTANT CAVEAT -- This implementation assumes that changes to @!myString are limited to the Raku side once the object is allocated, otherwise once set, the $cached value will mask them. I don't really see a way around that, at the moment, unless you want to spend the cycles to create a new Str a-fresh every time you need to access @!myString.

UPDATE Heh - Code had a minor but in the return statement. It's been fixed.

Xliff
  • 194
  • 5
  • Hi @Xliff. I recall support for `HAS` automatically correctly calculating size information improving after 2017. Is that central to the improvement suggested in this answer, or would it have worked in 2017? – raiph Nov 07 '22 at 12:03
  • I'm wondering whether some techniques could, in principle, improve things using *current* Raku(do) relative to either or both of the two caveats you mention ("`setCharArray()`" ... "changes to `@!myString` are limited to the Raku side ... unless...".) or whether some core aspect of Raku(do) needs to be improved or at least should be. Specifically I'm wondering if a new Raku class with a different `HOW` could automatically handle the issues you mention, and perhaps a new REPR could limit the "new `Str` afresh" performance impact. Does my thinking seem to you sane or more like wishful thinking? – raiph Nov 07 '22 at 12:32
  • raiph - Actually, it MIGHT have worked in 2017. That wasn't the point of my answer. I KNOW it worked in 2020. I guess someone could take the initiative and check.... :) – Xliff Nov 08 '22 at 13:16
  • I'd have to sit down and think about your questions. If I do think of anything, I'll let you know. – Xliff Nov 08 '22 at 13:18