Search code examples
dartdart-native-extension

Get pointer to a struct from a Dart_NativeArguments struct in C


I'm trying to wrap a C library using Dart. I call into a C function from dart and pass in the arguments through a Dart_NativeArguments struct in C:

    void _sayHello(Dart_NativeArguments arguments) {
    string from;
    Dart_Handle seed_object = HandleError(Dart_GetNativeArgument(arguments, 0));
    if (Dart_IsString(seed_object)) {
        const char* seed;
        HandleError(Dart_StringToCString(seed_object, &seed));
        from = seed;
    }
    num = (int)Dart_GetNativeArgument(arguments, 1);

    Dart_SetReturnValue(arguments, HandleError(Dart_NewStringFromCString(sayHello(from, num).c_str())));
}

In Dart, I call the function and pass in the necessary arguments

String sayHello(String from) native "sayHello";

main() {
  print(sayHello("Dart"));
}

I was wondering how I could pass in pointers (to a struct I made) instead of just strings and ints as arguments. There are functions in Dart to convert Dart_Handles into Strings and ints but not pointers. What is the internal structure of the Dart_Handle and how would I go about converting it back to a pointer? For example:

Dart code:

String sayHello(info from) native "sayHello";


class info
{
  String message;
  int num;
}

main() {
  info tester = new info();
  tester.message = "Dart";
  tester.num = 2;
  print(sayHello(tester));
}

C Code:

void sayHello(Dart_NativeArguments arguments) {
    /*What do I do here to get back a pointe to the struct/class I passed
      in as an argument in Dart?*/
}

Solution

  • Your Dart_NativeArguments will consist of just one item, which will be an instance - the instance of the class info that you created with new info(). You can test whether it's an instance with bool Dart_IsInstance(Dart_Handle object). So what you have is an handle to an instance of info. This allows you to access its instance fields (message and num) to get and set them, using Dart_GetField and Dart_SetField.

    Dart_Handle instance = Dart_GetNativeArgument(arguments, 0);
    Dart_Handle message_handle = Dart_GetField(retobj, NewString("message"));
    char* message;
    Dart_StringToCString(message_handle, &message);
    Dart_Handle number_handle = Dart_GetField(retobj, NewString("num"));
    int64_t number;
    Dart_IntegerToInt64(number_handle, &number);
    // message contains the string, number contains the number
    // use them, copy them etc
    

    I know this is just an example, but it might be easier to redefine sayHello to take 2 arguments (a string and an int) rather than passing an object instance. There isn't a way to access the fields of a class in one step, you need to access them individually. Consider these two versions of the Dart code, one passing an object instance and one just the values. The second version is simpler at the Dart and C side (no GetField steps). The first version is more powerful, though, because you could update the fields using SetField, which you couldn't in the second.

    class Info {
      String message;
      int num;
    
      Info(this.message, this.num);
    }
    
    version1() {
      sayHelloV1(new Info('Dart', 2));
    }
    
    version2() {
      sayHelloV2('Dart', 2);
    }
    

    If your C API requires you to pass in a struct you will have to create that in your C code by copying the values you extract using Dart_IntegerToInt64etc into it, then pass the pointer to your C struct to the API.

    If your API is very precise about the packing/padding of the data into the struct, you could use Dart typed_data to pack the Dart types into a ByteData and pass the underlying byte array.