Search code examples
raku

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


2023 update The last person to edit this Q deleted the critically important "LATEST LATEST UPDATE" part that @zentrunix had added near the top. I'm reinstating it.

LATEST LATEST UPDATE

Please see my answer below.

Thanks to everyone who took the time to answer and understand this question.

Original question

Say I have my event-driven TCP communications library in C.

From my Raku application, I can call a function in the C library using NativeCall.

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

Now, from my callback in C (say onAccept) I want to call out to a Raku function in my application (say on-accept(connection) where connection will be a pointer to a C struct).

So, how can I do that: call my Raku function on-accept from my C function onAccept ?

ps. I tried posting using a simple title "How to call Raku code from C code", but for whatever reason stackoverflow.com wouldn't let me do it. Because of that I concocted this fancy title.

I was creating a 32-bit DLL. We have to explicitly tell CMake to configure a 64-bit build.

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

Anyway, now that the code runs, it's not really what I asked for, because the callback is still in C.

It seems that what I asked for it's not really possible.

I tried to use the approach suggested by Haakon, though I'm afraid I don't understand how it would work.

I'm in Windows, and unfortunately, Raku can't find my dlls, even if I put them in C:\Windows\System32. It finds "msvcrt" (C runtime), but not my dlls.

The dll code (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 code for the

CMAKE_MINIMUM_REQUIRED (VERSION 3.1)
add_library (my_c_dll SHARED my_c_dll.c)

Raku code.

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

When I run

$ 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 version.

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

Solution

  • Another approach could be to save a callback statically in the C library, for example (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);
    }
    

    Then from 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();
    

    Output:

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