从 C 代码调用 Raku 代码的协议是什么?

What's the protocol for calling Raku code from C code?

假设我在 C 中有 event-driven TCP 通信库。

在我的 Raku 应用程序中,我可以使用 NativeCall 调用 C 库中的函数。

my $server = create-server("127.0.0.1", 4000);

现在,从我在 C 中的回调 (say onAccept) 我想在我的应用程序中调出一个 Raku 函数(比如 on-accept(connection),其中 connection 将是一个指向C 结构)。

那么,我该怎么做:从我的 C 函数 onAccept 调用我的 Raku 函数 on-accept ?

ps。我尝试使用一个简单的标题“如何从 C 代码调用 Raku 代码”来发帖,但出于某种原因 whosebug.com 不让我这样做。因此,我编造了这个花哨的标题。

我正在创建一个 32 位 DLL。 我们必须明确告诉 CMake 配置 64 位构建。

cmake -G "Visual Studio 14 2015 Win64" ..

无论如何,现在的代码 运行s 并不是我真正要求的,因为回调仍在 C.

看来我所要求的确实不可能

我尝试使用 Haakon 建议的方法,但恐怕我不明白它是如何工作的。

我在 Windows,不幸的是,Raku 找不到我的 dll,即使我把它们放在 C:\Windows\System32。它找到“msvcrt”(C 运行time),但找不到我的 dll。

dll代码(Visual Studio 2015).

#include <stdio.h>

#define EXPORTED __declspec(dllexport)

typedef int (*proto)(const char*);

proto raku_callback;

extern EXPORTED void set_callback(proto);
extern EXPORTED void foo(void);

void set_callback(proto arg)
{
  printf("In set_callback()..\n");
  raku_callback = arg;
}

void foo(void)
{
  printf("In foo()..\n");
  int res = raku_callback("hello");
  printf("Raku return value: %d\n", res);
}

的 Cmake 代码
CMAKE_MINIMUM_REQUIRED (VERSION 3.1)
add_library (my_c_dll SHARED my_c_dll.c)

乐库代码。

use v6.d;

use NativeCall;

sub set_callback(&callback (Str --> int32))
  is native("./my_c_dll"){ * }

sub foo()
  is native("./my_c_dll"){ * }

sub callback(Str $str --> Int) {
  say "Raku callback.. got string: {$str} from C";
  return 32;
}

## sub _getch() returns int32 is native("msvcrt") {*};
## print "-> ";
## say "got ", _getch();

set_callback(&callback);
# foo();

当我运行

$ raku test-dll.raku
Cannot locate native library '(null)': error 0xc1
  in method setup at D:\tools\raku\share\perl6\core\sources
    7BDAB9F96E0E5FCCB383124F923A6BF6F8D76B (NativeCall) line 298
  in block set_callback at D:\tools\raku\share\perl6\core\sources
     7BDAB9F96E0E5FCCB383124F923A6BF6F8D76B (NativeCall) line 594
  in block <unit> at test-dll.raku line 21

Raku 版本。

$ raku -v
This is Rakudo version 2020.05.1 built on MoarVM version 2020.05
implementing Raku 6.d.

Raku is a compiled language; depending on the implementation you've got, it will be compiled to MoarVM、JVM 或 Javascript。通过编译,Raku代码在相应的虚拟机中成为字节码。所以它实际上从来都不是二进制代码。

但是,Raku 代码的组织方式似乎很巧妙,对象实际上是指向 C 端点的指针,Haakon Hagland .

证明了这一点

WRT 你最近的问题,请记住你调用的不是路径,而是转换为原始共享库名称并使用 local library path conventions 查找它们的名称(它是 Windows 上的“PATH”)。因此,如果找不到它,请添加本地路径,只需将 DLL 复制到搜索到的目录之一即可。

另一种方法是在 C 库中静态保存回调,例如 (libmylib.c):

#include <stdio.h>

static int (*raku_callback)(char *arg);

void set_callback(int (*callback)(char * arg)) {
    printf("In set_callback()..\n");
    raku_callback = callback;
}

void foo() {
    printf("In foo()..\n");
    int res = raku_callback("hello");
    printf("Raku return value: %d\n", res);
}

然后从 Raku:

use v6;
use NativeCall;

sub set_callback(&callback (Str --> int32)) is native('./libmylib.so') { * }
sub foo() is native('./libmylib.so') { * }

sub callback(Str $str --> Int) {
    say "Raku callback.. got string: {$str} from C";
    return 32;
}

set_callback(&callback);
foo();

输出:

In set_callback()..
In foo()..
Raku callback.. got string: hello from C
Raku return value: 32

TL;DR 在参考这个 SO 时,领先的 Raku(do) 专家在多线程上下文中嵌入 MoarVM 和来自 C 的 运行 Raku 代码曾说过“回调确实是要走的路”。我目前认为@jjmerelo 的回答是不正确的,并且没有理由认为@zentrunix 的场景无法工作。

关于前两个答案

也许显示的方法不足以满足@zentrunix 的特定需求,但 Håkon Hægland 的回答证明可以从 C 调用 Raku 代码。

也许@jjmerelo 的回答是我不明白的意思,但这似乎与 Håkon Hægland 中明确证明的内容相矛盾。

“回调确实是要走的路”

nine 可能是在多线程上下文中从 C 调用 Raku 的领先 Raku(do) 专家。

I asked about @zentrunix's question on #raku. nine replied:

callbacks are indeed the way to go. Inline::Perl6 is an example of how to embed MoarVM and run Raku code from C


我不是 100% 清楚 9 是在谈论@HåkonHægland 的回答中的解决方案,还是在积极确认它也适用于@zentrunix 的特定场景。

也就是说,我确实知道九很久以前就在多线程场景中处理了将 Raku 嵌入 C 中的问题。

我认为对 nine 的评论的直接解释很可能是 Raku(do) 将适用于@zentrunix 的场景,要么使用@HåkonHægland 的答案中看起来简单的方法,要么使用更复杂的外观上面链接的 Inline::Perl6 模块中的方法。

直到@zentrunix 或其他人确认 Raku(do) 确实适用于@zentrunix 的特定场景,或者最终没有,我将继续持有中立观点与它是否可行有关。但是这种中立的观点包括认为@jjmerelo 和@zentrunix 没有适当地证明他们的结论是 Raku(do) 不能像@zentrunix 想要的那样做。

[@HåkonHægland 的解决方案]并不是我真正要求的,因为回调仍在 C 中

到目前为止,@zentrunix 在这个 SO 发展的每个阶段的评论都让我感到困惑。我试图通过对他们的问题和现有答案发表评论来消除我的困惑,但这没有帮助。所以我删除了它们并改写了这个 nanswer。

my callback in C (say onAccept) I want to call out to a Raku [callback]

所以,首先,回调调用回调。其次,更重要的是,调用 Raku 代码的 C 代码是 异步执行的,没有与 Rakudo.

协调

这行得通吗?我不知道。但我的印象是九号说它会,也许使用他们链接的代码作为模板,或者甚至可能只是使用@HåkonHægland 的回答中显示的简单代码。

协议

来自@zentrunix 对@JJ 回答的评论:

interaction between Raku code and C code would have to serialized ... It seems that what I asked for it's not really possible.

为什么不呢?例如,Actors 行不通吗?

首先,我向@Håkon 和@raiph 表示歉意。 抱歉这么迟钝。 :)

Håkon 的回答确实回答了我的问题,尽管出于某种原因我直到现在才看到它。

现在是我为了理解 Håkon 的解决方案而玩过的代码。


// my_c_dll.c
// be sure to create a 64-bit dll

#include <stdio.h>

#define EXPORTED __declspec(dllexport)

typedef int (*proto)(const char*);

proto raku_function;

extern EXPORTED void install_raku_function(proto);
extern EXPORTED void start_c_processing(void);

void install_raku_function(proto arg)
{
  printf("installing raku function\n");
  raku_function = arg;
}

void start_c_processing(void)
{
  printf("* ----> starting C processing..\n");

  for (int i = 0; i < 100; i++)
  {
    printf("* %d calling raku function\n", i);
    int res = raku_function("hello");
    printf("* %d raku function returned: %d\n", i, res);
    Sleep(1000);
  }
}

# test-dll.raku

use v6.d;

use NativeCall;

sub install_raku_function(&raku_function (Str --> int32))
  is native("./my_c_dll.dll") { * }

sub start_c_processing()
  is native("./my_c_dll.dll") { * }

sub my_raku_function(Str $str --> Int)
{
  say "@ raku function called from C with parameter [{$str}]";
  return 32;
}

install_raku_function &my_raku_function;

start { start_c_processing; }

for ^1000 -> $i
{
  say "# $i idling in raku";
  sleep 1;
}                               

$ raku test-dll.raku                   
installing raku function               
# 0 idling in raku                     
* ----> starting C processing..        
* 0 calling raku function              
@ 0 raku function called from C with parameter [hello]
* 0 raku function returned: 32         
# 1 idling in raku                     
* 1 calling raku function              
@ 1 raku function called from C with parameter [hello]
* 1 raku function returned: 32         
# 2 idling in raku                     
* 2 calling raku function              
@ 2 raku function called from C with parameter [hello]
* 2 raku function returned: 32         
# 3 idling in raku                     
* 3 calling raku function              
@ 3 raku function called from C with parameter [hello]
* 3 raku function returned: 32         
# 4 idling in raku                     
* 4 calling raku function              
@ 4 raku function called from C with parameter [hello]
* 4 raku function returned: 32         
# 5 idling in raku                     
* 5 calling raku function              
@ 5 raku function called from C with parameter [hello]
* 5 raku function returned: 32         
^CTerminate batch job (Y/N)?           
^C                                     

令我惊讶的是,my_raku_function 的 Raku 签名可以清晰地映射到 C 签名……Raku 不是很棒吗? :)