Skip to content

Add option to expose function pointers & typedefs #521

Closed
dart-archive/ffigen
#175

Description

@dcharkes

For some users it might be useful to expose the function pointers instead of Dart wrapper functions. In the use case where they want to pass these function pointers to C.

We should probably have a similar exclude/include mechanism specifying which functions should be exposed as function pointers.

Activity

  1. changed the title [-]Add option to expose function pointers [/-] [+]Add option to expose function pointers & typedefs[/+] on Jul 23, 2020
  2. dcharkes commented on Jul 23, 2020

    @dcharkes
    CollaboratorAuthor

    Also not having the typedefs private would be useful in that situation.

  3. mortenboye commented on Oct 30, 2020

    @mortenboye

    Does this issue cover the option of choosing whether generated structs should be private or not?
    I'm trying to wrap ALSA on linux and get a bunch of
    [WARNING]: Generated declaration '_snd_pcm' start's with '_' and therefore will be private.
    and similar.

    I would like to access the struct types (snd_pcm) from the outside.

  4. mannprerak2 commented on Oct 30, 2020

    @mannprerak2
    Contributor

    Hi @mortenboye
    For your purposes, you can rename declarations, using which you should be able to remove the underscore prefix.

    Here's an FAQ regarding that -
    https://github.com/dart-lang/ffigen#can-ffigen-be-used-for-removing-underscores-or-renaming-declarations

    It's fairly simple regexp based renaming, but let me know if you need more assistance on that.

  5. mortenboye commented on Oct 30, 2020

    @mortenboye

    @mannprerak2 Thanks! That was very helpful, and solved my issue.

  6. maks commented on Jan 19, 2021

    @maks

    I'm not sure if my usecase is covered by this issue so feel free to tell me to file a new issue 🙂

    So what I'm trying to do is use libfuse which basically boils down to passing in to its fuse_main() a huge struct full of function pointers that are callbacks for various filesystem operations (getattr, read, write, mkdir, etc) that I as the user of libfuse implement.

    So what I was hoping ffigen could do foor me is generate all the Dart function typedefs that I need for the callback struct so I could then implement functions of those typedefs in my hand written code, use something like ffi.Pointer.fromFunction(myGetAttrCallback, X) to get a pointer to each once, stuff it in the struct and pass that struct in when calling fuse's fuse_main()

    But thats not quite what I end up with in my generated code as ffigen seems to generate mechanically named typedefs for the functions in the struct, eg. for mkdir:

     external ffi.Pointer<ffi.NativeFunction<_typedefC_5>> mkdir;
    
    //...
    
    typedef _typedefC_5 = ffi.Int32 Function(
      ffi.Pointer<ffi.Int8>,
      ffi.Uint32,
    );

    and it also includes all the wrapper functions to call into C code which I don't actually need because there are no C implementations as they are callbacks that I want to implement in Dart.

    While as pointed out in previous comments, the actual typedefs I'd like to implement are actually private, eg

    typedef _dart_fuse_fs_mkdir = int Function(
      ffi.Pointer<fuse_fs> fs,
      ffi.Pointer<ffi.Int8> path,
      int mode,
    );

    I hope that makes sense?

    So I'm not sure if I'm "holding it wrong" here or if my usecase is not really a scenerio that ffigen is meant to work with at the moment?

  7. mannprerak2 commented on Jan 19, 2021

    @mannprerak2
    Contributor

    Hi @maks, we could consider generating the dart types, but in my opinion, those typedefs will not be of a lot of help, once you know the dart type (which you can deduce see via the C type) you need to define a top-level function. E.g -

    For this typedef -

    typedef _typedefC_5 = ffi.Int32 Function(
      ffi.Pointer<ffi.Int8>,
      ffi.Uint32,
    );

    Even if the dart type is generated you would do this -

    typedef DartType = int Function(Pointer<Int8>, int);
    
    DartType yourFunc = (Pointer<Int8> ptr, int i){
     ...
    }

    which is not a lot different than doing -

    int yourFunc(Pointer<Int8> ptr, int i){
        ...
    }

    It is very simple to deduce the dart type from the C type which has been generated, you simply

    • Change Uint8, Int8, Uint16, ... Uint32 to int. Float\Double to double, Void to void, etc.
    • Let Pointers be pointers. (E.g Pointer<Int8> will remain the same)

    And the static type system will always let you know of a type mismatch.

  8. maks commented on Jan 20, 2021

    @maks

    @mannprerak2 thanks that is a very good point! With a primarily callback based library like libfuse, I'm not really going to get much of the benefits of using ffigen, though using the generated code, even as it is now will be a better starting point than writing it all from scratch so thanks for your work on ffigen and your help on this and the other issue I opened 👍

  9. dcharkes commented on Mar 4, 2021

    @dcharkes
    CollaboratorAuthor

    We need the function pointer for the resource-release functions used with native finalizers:

    https://github.com/google/webcrypto.dart/blob/e5c87ad597e21226180f2f7d0391f3c1104af87d/lib/src/impl_ffi/impl_ffi.utils.dart#L32

    Both the function pointer and the Dart function are exposed in the handwritten bindings:

    https://github.com/google/webcrypto.dart/blob/e5c87ad597e21226180f2f7d0391f3c1104af87d/lib/src/boringssl/evp.dart#L38-L52

    We cannot use a fromFunction because finalizers cannot execute Dart code.

  10. mannprerak2 commented on Mar 4, 2021

    @mannprerak2
    Contributor

    We can add a new config for this with an include/exclude mechanism and if the function name matches, we expose the function pointer, C, and Dart typedefs.

    Syntax -

    expose:
      include:
        - 'string_.*'
      exclude:
        - '.*system'
    1. We either expose all three things (func pointer, C and Dart typedef) or none.
    2. We should update the default names too, E.g- If we have a function called divide. This will generate the following
      • Public function ==> Before => divide, After ======> divide.
      • Dart Function Pointer => Before => _divide, After =====> _divide_ptr or divide_ptr
      • C typedef ======> Before => _c_divide, After ===> _divide_c or divide_c.
      • Dart typedef ====> Before => _dart_divide, After => _divide_dart or divide_dart.

    wdyt? @dcharkes

  11. mannprerak2 commented on Mar 4, 2021

    @mannprerak2
    Contributor

    Should we also expose this =>_lookup<ffi.NativeFunction<_c_divide>>('divide');.
    or is this => _lookup<ffi.NativeFunction<_c_divide>>('divide') .asFunction<_dart_dividePercision>() enough?

    Then we can add a C Function pointer

    • Dart Function Pointer => Before => _divide, After =====> _divide_dart_ptr or divide_dart_ptr
    • C Function Pointer => Before => None, After =====> _divide_c_ptr or divide_c_ptr
  12. dcharkes commented on Mar 4, 2021

    @dcharkes
    CollaboratorAuthor

    Should we also expose this =>_lookup<ffi.NativeFunction<_c_divide>>('divide');.

    Yes, this is what we need.

    How about an api such as this:

    Pointer<NativeFunction<Native_divide>> divide_ptr = ...

    Dart Function Pointer => Before => _divide, After =====> _divide_ptr or divide_ptr

    We don't have to expose the Dart function pointer, because we can just use a tear-off from the public function (Bindings.divide).

    Dart typedef ====> Before => _dart_divide, After => _divide_dart or divide_dart.

    This is also not needed for using the C function pointers.

    As for config, do we want or need to distinguish function pointers vs non-function pointers? If we only support function pointers, we might nest it under function: in the config.

    Maybe instead of expose, pointer or symbol-location or symbol-address?

    function:
      include:
        - ...
      symbol-address:
        include:
        - ...
  13. mannprerak2 commented on Mar 4, 2021

    @mannprerak2
    Contributor

    As for config, do we want or need to distinguish function pointers vs non-function pointers? If we only support function pointers, we might nest it under function: in the config.

    Great, yeah we can nest the config key. This key can then be used for function and global declarations to expose the underlying C pointer (and typedefs).

    Maybe instead of expose, pointer or symbol-location or symbol-address

    The config basically is exposing some things which were private, so how about expose-pointer?

    Does this seem okay -

    int sum(int a, int b);
    class NativeLibrary {
      ...
      int sum(int a, int b) {
        return _sum(a, b);
      }
    
      late final sum_ptr = _lookup<ffi.NativeFunction<sum_c>>('sum');
      late final sum_dart _sum = sum_ptr.asFunction<sum_dart>(); // This will always be private.
    }
    
    typedef sum_c = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
    typedef sum_dart = int Function(int a, int b);
    

    Using late final will make the type non-nullable and will let the user know that this is final.

  14. dcharkes commented on Mar 4, 2021

    @dcharkes
    CollaboratorAuthor
    class NativeLibrary {
      ...
      int sum(int a, int b) {
        return _sum(a, b);
      }
    
      late final sum_ptr = _lookup<ffi.NativeFunction<Native_sum>>('sum');
      late final sum_dart _sum = sum_ptr.asFunction<_sum_dart>(); // This will always be private.
    }
    
    typedef Native_sum = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
    typedef _sum_dart = int Function(int a, int b); // This will always be private.

    Or to prevent concatenating c symbols with _ptr

    class NativeLibrary {
      ...
      int sum(int a, int b) {
        return _sum(a, b);
      }
    
      late final _sum_ptr = _lookup<ffi.NativeFunction<Native_sum>>('sum');
      late final sum_dart _sum = sum_ptr.asFunction<_sum_dart>(); // This will always be private.
    
      late final NativeLibraryAddresses addresses = NativeLibraryAddresses(this);
    }
    
    class NativeLibraryAddresses {
      final NativeLibrary _library;
    
      get sum => _library._sum_ptr;
    }

    The config basically is exposing some things which were private, so how about expose-pointer?

    That is thought from an implementation point of view. It should have a name from a users point of view. The dlsym documentation:

    The function dlsym() takes a "handle" of a dynamic library returned by dlopen() and the null-terminated symbol name, returning the address where that symbol is loaded into memory.

    So I'd prefer symbol-address.

  15. mannprerak2 commented on Mar 4, 2021

    @mannprerak2
    Contributor

    So I'd prefer symbol-address

    Cool.

    class NativeLibraryAddresses {
      final NativeLibrary _library;
    
      get sum => _library._sum_ptr;
    }

    Great suggestion! Also, we can keep this class private. (_SymbolAddresses)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions