【问题标题】:How to handle varargs with NativeCall如何使用 NativeCall 处理可变参数
【发布时间】:2018-03-31 08:55:17
【问题描述】:

我正在为 Editline 编写绑定;它的一个函数history 为库的这一部分完成了大部分工作,但有几个可能的签名:

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

第三个参数是一个标志,它决定了history 应该使用给定的参数调用哪个函数,但是由于这些符号没有被导出,我无法使用它们来代替。如何找到使用该功能的方法?我不能使用多子或使用cglobal 将其转换为Pointer,然后转换为具有正确签名的函数。

编辑:我知道is symbol,但我想知道是否有更好的方法来解决这个问题,因为有 9 个不同的签名要写。

【问题讨论】:

  • history 函数可能在内部使用 va_arg,但这是关于如何通过 perl6 的 FFI 库调用它,遗憾的是,该库目前没有明确支持可变 arg 函数。在该支持落地之前,我建议使用 EVAL 生成一堆替代潜艇并导出一个调用正确变体的潜艇。
  • 是的,这就是函数使用的,但据我所知,NativeCall 中没有它的接口
  • 看来我迟到了,但我会试一试
  • 没有什么我可以为这个作品写的。调用history 仅在为它定义了一个子例程时才有效;当有多个时,在内部调用 va_start 时调用它会使 MoarVM 段错误,无论它是使用 Dyncall 还是 LibFFI 构建的。我需要为此发布一个问题

标签: c raku nativecall


【解决方案1】:

你可以用 Raku 函数包装原生函数:

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 )
    }
  }
}

或者您可以将每个版本包装在自己的多重中:

# 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 )
}

您甚至可以根据参数确定要给出的正确标志。这只有在每个签名只有一个标志时才有效。
(所以下面这个例子不遵循上面02具有相同签名的例子。)

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 )
}

您可以编写不同名称的包装器:

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 )
}

您可以通过使用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> {}

您还可以在包装函数中使用 nativecast 和生成的 Signature 对象:

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) );
}

这就是您可以为 C 的 printfscanf 编写包装器的方式。


无论您使用上述哪一种,您都需要某种方式来确定您需要调用哪个变体。

即使在 C 中,也必须有一种方法可以根据当前或先前的参数来确定参数的数量和类型。

printf 中,参数的数量和类型基于第一个参数,即格式。

C 函数可以通过尾随空参数来确定参数的数量。在这种情况下,你必须告诉 Raku。

无论底层外部函数如何处理va_args,您都必须将该算法以某种形式复制到 Raku 中。它不能只是猜测。

如果你有几个外来函数,它们的工作方式都相似,你可以为它们创建一个包装器生成器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-24
    • 2011-01-26
    • 2014-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多