【问题标题】:What's the protocol for calling Raku code from C code?从 C 代码调用 Raku 代码的协议是什么?
【发布时间】:2021-05-22 22:37:39
【问题描述】:

假设我有 C 语言中的事件驱动 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 代码”发布,但无论出于何种原因,stackoverflow.com 都不允许我这样做。因此,我编造了这个花哨的标题。

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

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

不管怎样,现在代码运行了,这并不是我真正要求的,因为回调仍在 C 中。

看来我要求的不太可能。

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

我在 Windows 中,不幸的是,即使我将它们放在 C:\Windows\System32 中,Raku 也找不到我的 dll。它找到“msvcrt”(C 运行时),但不是我的 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
    \947BDAB9F96E0E5FCCB383124F923A6BF6F8D76B (NativeCall) line 298
  in block set_callback at D:\tools\raku\share\perl6\core\sources
     \947BDAB9F96E0E5FCCB383124F923A6BF6F8D76B (NativeCall) line 594
  in block <unit> at test-dll.raku line 21

乐版本。

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

【问题讨论】:

  • 我没有沮丧,我很开明:) ...至于加载 dll 的错误,是我的错
  • 对于更多的上下文,在 Lua 中,C 代码可以调用 Lua 代码,所以也许它也可以在 Raku 中做类似的事情......所以,这就是我发布这个问题的原因
  • 根据维基百科,对于 Lua,“......编译字节码的解释器是用 ANSI C 编写的,Lua 有一个相对简单的 C API 可以将其嵌入到应用程序中。”因此,如果您发布的问题更接近 @raiph 建议的标题(即互操作性),那么很自然地假设 Lua/C 互操作性将比 Raku/C 互操作性更容易。
  • @jubilatious1 我提出了一个简单的问题,“是否可以从 C 中调用 Raku?”,就是这样......似乎不可能,我没关系,我不是沮丧,也不生气,这只是我想知道的事情
  • @zentrunix 用户 .@Yatin 已(imo 无助地)编辑了您的问题以删除您的评论,即读者应该看到您的答案。所以现在读者会看到 JJ 的答案是公认的答案,并得出我认为不恰当的结论。 JJ 说他不再坚持原来的答案,但指出他不能删除它(因为你已经接受了)。也许将接受的答案从 JJ 更改为您的答案是否有意义?也就是说,即使你的回答仍然让我觉得你的原始问题没有明确的解决方案。我的下一条评论将说明我的意思。

标签: raku


【解决方案1】:

另一种方法是在 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);
}

然后从乐:

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

【讨论】:

  • 虽然这不是我希望的答案,但它是一个不错的答案,因为它显示了在 Raku 中使用本地调用(对 C)是多么容易
  • @zentrunix。它可能不是您要查找的内容,但它显示从 C 调用 Raku 从 Raku 调用 C。
【解决方案2】:

Raku 是一种编译语言;根据您的实现,它将被编译为MoarVM、JVM 或 Javascript。通过编译,Raku 代码成为对应虚拟机中的字节码。所以它实际上从来都不是二进制代码。

然而,Raku 代码似乎被巧妙地组织成对象实际上是指向 C 端点的指针,正如 Haakon Hagland answer 所证明的那样。

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

【讨论】:

  • 在我的问题中使用的情况下,连接也将由 C 代码处理,但是是的,我认为你是对的,回调(尤其是多线程应用程序生成的异步回调)必须由 C 代码处理...... Raku 代码和 C 代码之间的交互必须序列化(如不是多线程),但我认为没有其他方法可以做到这一点(事实上,我相信这就是node.js 工作)...感谢您的输入
  • @zentrunix "Raku 代码和 C 代码之间的交互必须序列化" 有办法做到这一点。我在我的 nanswer 中提到了一种方法 (Actors)。
  • @jjmerelo 鉴于九个 cmets(请参阅我的 nanswer),您是否仍然坚持您的结论“这是不可能的”?猜测它的工作方式是 C 代码不直接调用 Raku 函数,而是调用 MoarVM 中的 C 函数,该函数通过某种对 Raku 函数的引用来调用。然后,该 MoarVM 函数将调用转发到目标 Raku 函数。或类似的东西。不管细节如何,九个似乎都在说它有效,而且我知道 Inline::Perl6(通过 C 将 Raku 嵌入 perl)有效。
  • @raiph 我确实知道“演员”的概念,尽管我对它的经验为零;现在,在我写完上面的评论之后,我意识到,小心一点,也许 Raku 代码可以以非序列化的方式与 C 代码交互:比如说,Raku 中有 2 个“开始”块,每个调用不同的 C 函数,等待 C 代码的其他部分传递的事件;至于C直接调用Raku函数,我不知道是否有一种简单的方法可以做到这一点(正如我上面所说)Lua允许它:见here函数lua_pcall
  • @raiph 请注意,我不是 Raku 和 Lua 方面的专家 :)
【解决方案3】:

首先,我向@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 不是很棒吗? :)

【讨论】:

  • 真的是:-)
  • 很酷,你让它在 Windows 上工作,我现在在 Windows 上尝试,但是当我运行脚本时我得到“无法找到本机库'(null)':错误 0xc1”。你是如何编译共享库的?我从开发人员命令提示符使用 Visual Studio 2019 编译:cl.exe /c my_c_dll.c 然后link /DLL /OUT:my_c_dll.dll my_c_dll.obj
  • @HåkonHægland 您正在创建一个 32 位 dll ... 0xc1 ---> 193 msdn ... raku 想要一个 64 位 dll ...我使用 cmake 来创建解决方案,所以我不得不告诉 cmake 创建一个 64 位解决方案......在你的情况下,你必须打开一个 64 位开发人员提示......我有 VS2017,它有 5 个“开发人员提示”开始菜单...其中之一肯定是 Visual Studio 的 64 位 shell
  • @zentrunix 有趣!是的,开始菜单中有很多提示,我现在尝试了一个名为 "x86 Native Tools Command Prompt for VS 2019" 的提示,但这里也出现了同样的错误..
  • @zentrunix 糟糕,我需要使用 "x64 Native Tools Command Prompt for VS 2019"(当然不是 x86)它现在可以工作了!我发了一个总结here供参考
【解决方案4】:

TL;DR 参考这个 SO,一位在多线程上下文中嵌入 MoarVM 和从 C 运行 Raku 代码的领先 Raku(do) 专家说“回调确实是走”。我目前认为@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。九个replied

回调确实是要走的路。 Inline::Perl6 是如何嵌入 MoarVM 并从 C 运行 Raku 代码的示例


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

也就是说,我知道 9 很久以前就处理过在多线程场景中将 Raku 嵌入 C 中。

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

直到@zentrunix 或其他人确认 Raku(do) 实际上可以 与@zentrunix 的特定场景一起使用,或者最终不可行,我将继续对它是否可行持中立观点或不。但这种中立观点包括认为@jjmerelo 和@zentrunix 没有正确证明他们的结论是Raku(do) 不能按照@zentrunix 的意愿行事。

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

到目前为止,我一直对 @zentrunix 的 cmets 在此 SO 的演变的每个阶段感到困惑。我试图通过在他们的问题和现有答案上写 cmets 来消除我的困惑,但这并没有帮助。所以我删除了它们并写了这个 nanswer。

我在 C 中的回调(比如 onAccept)我想呼叫 Raku [回调]

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

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

协议

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

Raku 代码和 C 代码之间的交互必须序列化……看来我所要求的似乎不太可能。

为什么不呢?例如,Actors 不工作吗?

【讨论】:

  • 好吧,让我感到惊讶。删除。谢谢。
猜你喜欢
  • 2012-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-29
  • 1970-01-01
  • 2015-10-17
相关资源
最近更新 更多