Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle varargs with NativeCall

Tags:

c

raku

nativecall

I'm writing bindings for Editline; one of its functions, history, does the bulk of the work for this part of the library, but has several possible signatures:

:(Pointer[Internal], Pointer[Event], int32 --> int32)
:(Pointer[Internal], Pointer[Event], int32, int32 --> int32)
:(Pointer[Internal], Pointer[Event], int32, Str --> int32)
# etc.

The third argument is a flag that determines which function history should call with the arguments given, but since those symbols aren't exported, I have no way of using them instead. How can I find a way to use the function? I can't use multi subs or use cglobal to cast it to a Pointer, then to a function with the right signature.

Edit: I know about is symbol, but I'm wondering if there are any better ways to go about this since there are 9 different signatures to write for.

like image 894
Kaiepi Avatar asked Mar 31 '18 08:03

Kaiepi


1 Answers

You could wrap the native function with a Raku function:

sub history ( Pointer[Internal] $a, Pointer[Event] $b, int32 $flag, $c? --> int32 ){
  given $flag {
    when 0|2 {
      sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
      history( $a, $b, $flag )
    }
    when 1|3 {
      sub history (Pointer[Internal], Pointer[Event], int32, int32 --> int32) is native(…) {}
      history( $a, $b, $flag, $c )
    }
    when 4 {
      sub history (Pointer[Internal], Pointer[Event], int32, Str --> int32) is native(…) {}
      history( $a, $b, $flag, $c )
    }
  }
}

Or you could wrap each version in its own multi:

# Note the flag value ---------------------------------------+
#                                                            |
#                                                            V
multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, 0 --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 0 )
}
multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, 2 --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 2 )
}

multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, int32 $flag where 1|3, int32 $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32, int32 --> int32) is native(…) {}
  history( $a, $b, $flag, $c )
}

multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, 4, Str:D $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32, Str --> int32) is native(…) {}
  history( $a, $b, 4, $c )
}

You might even be able to determine the correct flag to give based on the arguments. This would only work if there is only one flag for each signature.
(So this following example does not follow the above where 0 and 2 have the same signature.)

multi sub history ( Pointer[Internal] $a, Pointer[Event] $b --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 0 )
}

multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, int32 $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 1, $c )
}

multi sub history ( Pointer[Internal] $a, Pointer[Event] $b, Str:D $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32, Str --> int32) is native(…) {}
  history( $a, $b, 4, $c )
}

You could write wrappers with different names:

sub foo ( Pointer[Internal] $a, Pointer[Event] $b --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 0 )
}

sub bar ( Pointer[Internal] $a, Pointer[Event] $b, int32 $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32 --> int32) is native(…) {}
  history( $a, $b, 1, $c )
}

sub baz ( Pointer[Internal] $a, Pointer[Event] $b, Str:D $c --> int32 ){
  sub history (Pointer[Internal], Pointer[Event], int32, Str --> int32) is native(…) {}
  history( $a, $b, 4, $c )
}

You could do that without wrappers by using is symbol.

sub foo (Pointer[Internal], Pointer[Event], int32        --> int32) is native(…) is symbol<history> {}
sub bar (Pointer[Internal], Pointer[Event], int32, int32 --> int32) is native(…) is symbol<history> {}
sub baz (Pointer[Internal], Pointer[Event], int32, Str   --> int32) is native(…) is symbol<history> {}

You could also use nativecast and a generated Signature object in your wrapper function:

sub history ( Pointer[Internal] $a, Pointer[Event] $b, int32 $flag, $c? --> int32 ){
  my \I32 = Parameter.new( type => int32 );
  my \PI  = Parameter.new( type => Pointer[Internal] );
  my \PE  = Parameter.new( type => Pointer[Event] );
  my \STR = Parameter.new( type => Str );

  my @params = ( PI, PE, I32 );
  given $flag {
    when 0|2 {
    }
    when 1|3 {
      @params.push( I32 );
    }
    when 4 {
      @params.push( STR );
    }
  }

  my \signature = Signature.new( params => @params.List, returns => int32 );

  # fill this out -----------V
  my \history-ptr = cglobal( …, 'history', Pointer );

  my &history = nativecast( signature, history-ptr );

  history( $a, $b, $flag, ($c if +@params == 4) );
}

This is how you might write a wrapper for C’s printf or scanf.


Regardless of which of the above you use, you need some way to determine which variation you need to call.

Even in C, there has to be a way to determine the number and type of arguments based on the current, or a previous argument.

In printf the number and type of arguments is based on the the first argument which is the format.

A C function could determine the number of arguments by having a trailing null argument. In which case you have to tell Raku that.

Regardless of how the underlying foreign function handles va_args, you have to copy that algorithm into Raku in some form. It can't just guess.

If you had several foreign functions that all worked similarly, you could create a wrapper generator for them.

like image 174
Brad Gilbert Avatar answered Nov 02 '22 18:11

Brad Gilbert