class Fiddle::Function - RDoc Documentation

# class Fiddle::Function

## Description¶ ↑

A representation of a C function

## Examples¶ ↑

### ‘strcpy’¶ ↑

    @libc = Fiddle.dlopen "/lib/libc.so.6"
       #=> #<Fiddle::Handle:0x00000001d7a8d8>
    f = Fiddle::Function.new(
      @libc['strcpy'],
      [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
      Fiddle::TYPE_VOIDP)
       #=> #<Fiddle::Function:0x00000001d8ee00>
    buff = "000"
       #=> "000"
    str = f.call(buff, "123")
       #=> #<Fiddle::Pointer:0x00000001d0c380 ptr=0x000000018a21b8 size=0 free=0x00000000000000>
    str.to_s
    => "123"

### ABI check¶ ↑

    @libc = Fiddle.dlopen "/lib/libc.so.6"
       #=> #<Fiddle::Handle:0x00000001d7a8d8>
    f = Fiddle::Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
       #=> #<Fiddle::Function:0x00000001d8ee00>
    f.abi == Fiddle::Function::DEFAULT
       #=> true

### Constants

DEFAULT

  `DEFAULT`

  Default ABI

STDCALL

  `STDCALL`

  FFI implementation of WIN32 stdcall convention

### Attributes

abi[R]

The ABI of the `Function`.

name[R]

The name of this function

ptr[R]

The address of this function

### Public Class Methods

new(ptr, click to toggle source
args,
ret_type,
abi = DEFAULT,
name: nil,
need_gvl: false)

Constructs a `Function` object.

- `ptr` is a referenced function, of a `Fiddle::Handle`

- `args` is an `Array` of arguments, passed to the `ptr` function

- `ret_type` is the return type of the function

- `abi` is the ABI of the function

- `name` is the name of the function

- `need_gvl` is whether GVL is needed to call the function

    static VALUE
    initialize(int argc, VALUE argv[], VALUE self)
    {
        ffi_cif * cif;
        VALUE ptr, arg_types, ret_type, abi, kwargs;
        VALUE name = Qnil;
        VALUE need_gvl = Qfalse;
        int c_ret_type;
        bool is_variadic = false;
        ffi_abi c_ffi_abi;
        void *cfunc;

        rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwargs);
        rb_iv_set(self, "@closure", ptr);

        if (!NIL_P(kwargs)) {
            enum {
                kw_name,
                kw_need_gvl,
                kw_max_,
            };
            static ID kw[kw_max_];
            VALUE args[kw_max_];
            if (!kw[0]) {
                kw[kw_name] = rb_intern_const("name");
                kw[kw_need_gvl] = rb_intern_const("need_gvl");
            }
            rb_get_kwargs(kwargs, kw, 0, kw_max_, args);
            if (args[kw_name] != Qundef) {
                name = args[kw_name];
            }
            if (args[kw_need_gvl] != Qundef) {
                need_gvl = args[kw_need_gvl];
            }
        }
        rb_iv_set(self, "@name", name);
        rb_iv_set(self, "@need_gvl", need_gvl);

        ptr = rb_Integer(ptr);
        cfunc = NUM2PTR(ptr);
        PTR2NUM(cfunc);
        c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
        abi = INT2FIX(c_ffi_abi);
        ret_type = rb_fiddle_type_ensure(ret_type);
        c_ret_type = NUM2INT(ret_type);
        (void)INT2FFI_TYPE(c_ret_type); /* raise */
        ret_type = INT2FIX(c_ret_type);

        arg_types = normalize_argument_types("argument types",
                                             arg_types,
                                             &is_variadic);
    #ifndef HAVE_FFI_PREP_CIF_VAR
        if (is_variadic) {
            rb_raise(rb_eNotImpError,
                     "ffi_prep_cif_var() is required in libffi "
                     "for variadic arguments");
        }
    #endif

        rb_iv_set(self, "@ptr", ptr);
        rb_iv_set(self, "@argument_types", arg_types);
        rb_iv_set(self, "@return_type", ret_type);
        rb_iv_set(self, "@abi", abi);
        rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse);

        TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
        cif->arg_types = NULL;

        return self;
    }

### Public Instance Methods

call(*args) click to toggle source

Calls the constructed `Function`, with `args`. Caller must ensure the underlying function is called in a thread-safe manner if running in a multi-threaded process.

Note that it is not thread-safe to use this method to directly or indirectly call many Ruby C-extension APIs unless you don’t pass +need_gvl: true+ to Fiddle::Function#new.

For an example see `Fiddle::Function`

    static VALUE
    function_call(int argc, VALUE argv[], VALUE self)
    {
        struct nogvl_ffi_call_args args = { 0 };
        fiddle_generic *generic_args;
        VALUE cfunc;
        VALUE abi;
        VALUE arg_types;
        VALUE cPointer;
        VALUE is_variadic;
        VALUE need_gvl;
        int n_arg_types;
        int n_fixed_args = 0;
        int n_call_args = 0;
        int i;
        int i_call;
        VALUE converted_args = Qnil;
        VALUE alloc_buffer = 0;

        cfunc    = rb_iv_get(self, "@ptr");
        abi      = rb_iv_get(self, "@abi");
        arg_types = rb_iv_get(self, "@argument_types");
        cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
        is_variadic = rb_iv_get(self, "@is_variadic");
        need_gvl = rb_iv_get(self, "@need_gvl");

        n_arg_types = RARRAY_LENINT(arg_types);
        n_fixed_args = n_arg_types;
        if (RTEST(is_variadic)) {
            if (argc < n_arg_types) {
                rb_error_arity(argc, n_arg_types, UNLIMITED_ARGUMENTS);
            }
            if (((argc - n_arg_types) % 2) != 0) {
                rb_raise(rb_eArgError,
                         "variadic arguments must be type and value pairs: "
                         "%"PRIsVALUE,
                         rb_ary_new_from_values(argc, argv));
            }
            n_call_args = n_arg_types + ((argc - n_arg_types) / 2);
        }
        else {
            if (argc != n_arg_types) {
                rb_error_arity(argc, n_arg_types, n_arg_types);
            }
            n_call_args = n_arg_types;
        }
        Check_Max_Args("the number of arguments", n_call_args);

        TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif);

        if (is_variadic && args.cif->arg_types) {
            xfree(args.cif->arg_types);
            args.cif->arg_types = NULL;
        }

        if (!args.cif->arg_types) {
            VALUE fixed_arg_types = arg_types;
            VALUE return_type;
            int c_return_type;
            ffi_type *ffi_return_type;
            ffi_type **ffi_arg_types;
            ffi_status result;

            arg_types = rb_ary_dup(fixed_arg_types);
            for (i = n_fixed_args; i < argc; i += 2) {
              VALUE arg_type = argv[i];
              int c_arg_type;
              arg_type = rb_fiddle_type_ensure(arg_type);
              c_arg_type = NUM2INT(arg_type);
              (void)INT2FFI_TYPE(c_arg_type); /* raise */
              rb_ary_push(arg_types, INT2FIX(c_arg_type));
            }

            return_type = rb_iv_get(self, "@return_type");
            c_return_type = FIX2INT(return_type);
            ffi_return_type = INT2FFI_TYPE(c_return_type);

            ffi_arg_types = xcalloc(n_call_args + 1, sizeof(ffi_type *));
            for (i_call = 0; i_call < n_call_args; i_call++) {
                VALUE arg_type;
                int c_arg_type;
                arg_type = RARRAY_AREF(arg_types, i_call);
                c_arg_type = FIX2INT(arg_type);
                ffi_arg_types[i_call] = INT2FFI_TYPE(c_arg_type);
            }
            ffi_arg_types[i_call] = NULL;

            if (is_variadic) {
    #ifdef HAVE_FFI_PREP_CIF_VAR
                result = ffi_prep_cif_var(args.cif,
                                          FIX2INT(abi),
                                          n_fixed_args,
                                          n_call_args,
                                          ffi_return_type,
                                          ffi_arg_types);
    #else
                /* This code is never used because ffi_prep_cif_var()
                 * availability check is done in #initialize. */
                result = FFI_BAD_TYPEDEF;
    #endif
            }
            else {
                result = ffi_prep_cif(args.cif,
                                      FIX2INT(abi),
                                      n_call_args,
                                      ffi_return_type,
                                      ffi_arg_types);
            }
            if (result != FFI_OK) {
                xfree(ffi_arg_types);
                args.cif->arg_types = NULL;
                rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
            }
        }

        generic_args = ALLOCV(alloc_buffer,
                              sizeof(fiddle_generic) * n_call_args +
                              sizeof(void *) * (n_call_args + 1));
        args.values = (void **)((char *)generic_args +
                                sizeof(fiddle_generic) * n_call_args);

        for (i = 0, i_call = 0;
             i < argc && i_call < n_call_args;
             i++, i_call++) {
            VALUE arg_type;
            int c_arg_type;
            VALUE original_src;
            VALUE src;
            arg_type = RARRAY_AREF(arg_types, i_call);
            c_arg_type = FIX2INT(arg_type);
            if (i >= n_fixed_args) {
                i++;
            }
            src = argv[i];

            if (c_arg_type == TYPE_VOIDP) {
                if (NIL_P(src)) {
                    src = INT2FIX(0);
                }
                else if (cPointer != CLASS_OF(src)) {
                    src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
                    if (NIL_P(converted_args)) {
                        converted_args = rb_ary_new();
                    }
                    rb_ary_push(converted_args, src);
                }
                src = rb_Integer(src);
            }

            original_src = src;
            VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]);
            if (src != original_src) {
                if (NIL_P(converted_args)) {
                    converted_args = rb_ary_new();
                }
                rb_ary_push(converted_args, src);
            }
            args.values[i_call] = (void *)&generic_args[i_call];
        }
        args.values[i_call] = NULL;
        args.fn = (void(*)(void))NUM2PTR(cfunc);

        if (RTEST(need_gvl)) {
            ffi_call(args.cif, args.fn, &(args.retval), args.values);
        }
        else {
            (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);
        }

        {
            int errno_keep = errno;
    #if defined(_WIN32)
            DWORD error = WSAGetLastError();
            int socket_error = WSAGetLastError();
            rb_funcall(mFiddle, rb_intern("win32_last_error="), 1,
                       ULONG2NUM(error));
            rb_funcall(mFiddle, rb_intern("win32_last_socket_error="), 1,
                       INT2NUM(socket_error));
    #endif
            rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep));
        }

        ALLOCV_END(alloc_buffer);

        return GENERIC2VALUE(rb_iv_get(self, "@return_type"), args.retval);
    }

need_gvl?() click to toggle source

Whether GVL is needed to call this function

    # File ext/fiddle/lib/fiddle/function.rb, line 14
    def need_gvl?
      @need_gvl
    end

to_i() click to toggle source

The integer memory location of this function

    # File ext/fiddle/lib/fiddle/function.rb, line 19
    def to_i
      ptr.to_i
    end

to_proc() click to toggle source

Turn this function in to a proc

    # File ext/fiddle/lib/fiddle/function.rb, line 24
    def to_proc
      this = self
      lambda { |*args| this.call(*args) }
    end