Digicrat
Digicrat

Reputation: 709

How to define fixed-length strings in a Perl6 NativeCall struct?

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?

Upvotes: 7

Views: 383

Answers (3)

Xliff
Xliff

Reputation: 324

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.

Upvotes: 1

Digicrat
Digicrat

Reputation: 709

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.

Upvotes: 2

YvesgereY
YvesgereY

Reputation: 3888

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.

Upvotes: 1

Related Questions