Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion ext/ffi_c/Function.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,11 @@ static VALUE
function_attach(VALUE self, VALUE module, VALUE name)
{
Function* fn;
FunctionType* info;
char var[1024];

Data_Get_Struct(self, Function, fn);
Data_Get_Struct(fn->rbFunctionInfo, FunctionType, info);

if (fn->info->parameterCount == -1) {
rb_raise(rb_eRuntimeError, "cannot attach variadic functions");
Expand All @@ -403,10 +405,13 @@ function_attach(VALUE self, VALUE module, VALUE name)
rb_define_singleton_method(module, StringValueCStr(name),
rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1);


rb_define_method(module, StringValueCStr(name),
rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1);

rb_hash_aset(rb_cv_get(module, "@@ffi_functions"),
ID2SYM(rb_intern(StringValueCStr(name))),
rb_ary_new3(2, info->rbParameterTypes, info->rbReturnType));

return self;
}

Expand Down
11 changes: 11 additions & 0 deletions ext/ffi_c/MappedType.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ mapped_from_native(int argc, VALUE* argv, VALUE self)
return rb_funcall2(m->rbConverter, id_from_native, argc, argv);
}

static VALUE
mapped_converter(VALUE self)
{
MappedType*m = NULL;

Data_Get_Struct(self, MappedType, m);

return m->rbConverter;
}

void
rbffi_MappedType_Init(VALUE moduleFFI)
{
Expand All @@ -164,5 +174,6 @@ rbffi_MappedType_Init(VALUE moduleFFI)
rb_define_method(rbffi_MappedTypeClass, "native_type", mapped_native_type, 0);
rb_define_method(rbffi_MappedTypeClass, "to_native", mapped_to_native, -1);
rb_define_method(rbffi_MappedTypeClass, "from_native", mapped_from_native, -1);
rb_define_method(rbffi_MappedTypeClass, "converter", mapped_converter, 0);
}

11 changes: 10 additions & 1 deletion ext/ffi_c/Variadic.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE
return retval;
}

static VALUE
variadic_result_type(VALUE self)
{
VariadicInvoker* invoker;

Data_Get_Struct(self, VariadicInvoker, invoker);
return invoker->rbReturnType;
}

static VALUE
variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues)
{
Expand Down Expand Up @@ -299,5 +308,5 @@ rbffi_Variadic_Init(VALUE moduleFFI)

rb_define_method(classVariadicInvoker, "initialize", variadic_initialize, 4);
rb_define_method(classVariadicInvoker, "invoke", variadic_invoke, 2);
rb_define_method(classVariadicInvoker, "result_type", variadic_result_type, 0);
}

23 changes: 22 additions & 1 deletion lib/ffi/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,26 @@ module Library
# Test if extended object is a Module. If not, raise RuntimeError.
def self.extended(mod)
raise RuntimeError.new("must only be extended by module") unless mod.kind_of?(::Module)
end

return if mod.respond_to?(:attached_functions)

mod.module_eval <<-code, __FILE__, __LINE__
@@ffi_getters = {}
def self.attached_getters
@@ffi_getters
end

@@ffi_setters = {}
def self.attached_setters
@@ffi_setters
end

@@ffi_functions = {}
def self.attached_functions
@@ffi_functions
end
code
end

# @param [Array] names names of libraries to load
# @return [Array<DynamicLibrary>]
Expand Down Expand Up @@ -348,6 +366,7 @@ def attach_variable(mname, a1, a2 = nil)
def self.#{mname}
@@ffi_gvar_#{mname}
end
@@ffi_getters[:#{mname}] = type
code

else
Expand All @@ -365,6 +384,8 @@ def self.#{mname}
def self.#{mname}=(value)
@@ffi_gvar_#{mname}[:gvar] = value
end
@@ffi_getters[:#{mname}] = type
@@ffi_setters[:#{mname}=] = type
code

end
Expand Down
5 changes: 5 additions & 0 deletions lib/ffi/variadic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@ def self.#{mname}(#{params})
def #{mname}(#{params})
@@#{mname}.#{call}(#{params})
end
@@ffi_functions[:#{mname}] = [invoker.fixed_param_types + [:varargs], invoker.result_type]
code
invoker
end

def fixed_param_types
@fixed
end
end
end
172 changes: 172 additions & 0 deletions lib/tapioca/dsl/compilers/ffi.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# frozen_string_literal: true

begin
require "ffi"
rescue LoadError
return
end

module Tapioca
module Dsl
module Compilers
# `Tapioca::Dsl::Compilers::FFI` generate types for functions and
# variables bound by Ruby-FFI for dynamically-linked native libraries.
# Ruby-FFI dynamically defines constants and methods at runtime. For
# example, given a module:
#
# module MyModule
# extend FFI::Library
# ffi_lib 'mylib'
#
# attach_function :func, [:int, :pointer], :void
# attach_variable :var, :string
# end
#
# This will result in the following methods being defined:
#
# func, var, var=
#
class FFI < Compiler
def decorate
root.create_path(constant) do |mod|
constant.attached_functions.each do |method_name, (param_types, return_type)|
varargs = param_types[-1] == :varargs
param_types.pop if varargs
mod.create_method(
method_name,
parameters: params_for(param_types) +
if varargs
[create_rest_param("rest", type: "T.untyped")]
else
[]
end,
return_type: return_type_for(return_type),
class_method: true,
)
end

constant.attached_getters.each do |getter, type|
return_type = return_type_for(type)
mod.create_method(
getter,
parameters: [],
return_type: return_type,
class_method: true,
)
end

constant.attached_setters.each do |setter, type|
mod.create_method(
setter,
parameters: [create_param("value", type: type_for(type))],
return_type: return_type,
class_method: true,
)
end
end
end

private

MAPPING = T.let({
::FFI::Type::BOOL => "T::Boolean",
::FFI::Type::BUFFER_IN => "::FFI::Pointer",
::FFI::Type::BUFFER_INOUT => "::FFI::Pointer",
::FFI::Type::BUFFER_OUT => "::FFI::Pointer",
::FFI::Type::CHAR => "Integer",
::FFI::Type::DOUBLE => "Float",
::FFI::Type::FLOAT => "Float",
::FFI::Type::FLOAT32 => "Float",
::FFI::Type::FLOAT64 => "Float",
::FFI::Type::INT => "Integer",
::FFI::Type::INT16 => "Integer",
::FFI::Type::INT32 => "Integer",
::FFI::Type::INT64 => "Integer",
::FFI::Type::INT8 => "Integer",
::FFI::Type::LONG => "Integer",
::FFI::Type::LONGDOUBLE => "Float",
::FFI::Type::LONG_LONG => "Integer",
::FFI::Type::POINTER => "::FFI::Pointer",
::FFI::Type::SCHAR => "Integer",
::FFI::Type::SHORT => "Integer",
::FFI::Type::SINT => "Integer",
::FFI::Type::SLONG => "Integer",
::FFI::Type::SLONG_LONG => "Integer",
::FFI::Type::SSHORT => "Integer",
::FFI::Type::STRING => "String",
::FFI::Type::UCHAR => "Integer",
::FFI::Type::UINT => "Integer",
::FFI::Type::UINT16 => "Integer",
::FFI::Type::UINT32 => "Integer",
::FFI::Type::UINT64 => "Integer",
::FFI::Type::UINT8 => "Integer",
::FFI::Type::ULONG => "Integer",
::FFI::Type::ULONG_LONG => "Integer",
::FFI::Type::USHORT => "Integer",
::FFI::Type::VOID => "void",
}, T::Hash[::FFI::Type, String])

def return_type_for(ffi_type)
type_for(ffi_type, for_return: true)
end

def type_for(ffi_type, for_return: false)
if ffi_type == ::FFI::Type::POINTER && !for_return
# Special case, pointers as params can be one of several
"T.nilable(T.any(::FFI::Pointer, String, Integer))"
elsif ffi_type.is_a?(::FFI::Type::Mapped)
mapped_type_for(ffi_type)
elsif ffi_type.is_a?(::FFI::StructByValue)
ffi_type.struct_class.class_name
elsif ffi_type.is_a?(::FFI::Type::Array)
"[#{Array.new(ffi_type.length, type_for(ffi_type.elem_type)).join(", ")}]"
elsif ffi_type.is_a?(::FFI::Type::Function)
params = ffi_type.param_types.map { |param_type| type_for(param_type).to_s }.join(", ")
result = (
if ffi_type.result_type == ::FFI::Type::VOID
"void"
else
"returns(#{return_type_for(ffi_type.result_type)})"
end
)
"T.proc.params(#{params}).#{result}"
else
MAPPING.fetch(ffi_type, "T::Untyped")
end
end

def mapped_type_for(ffi_type)
converter = ffi_type.converter

if converter.is_a?(::FFI::StructByReference)
converter.struct_class.class_name
elsif converter == ::FFI::StrPtrConverter
"[String, ::FFI::Pointer]"
else
"T::Untyped"
end
end

def params_for(ffi_types)
ffi_types.map.with_index do |ffi_type, index|
create_param("arg#{index}", type: type_for(ffi_type))
end
end

class << self
def gather_constants
# Find all Modules that are:
all_modules.select do |mod|
# named (i.e. not anonymous)
name_of(mod) &&
# not singleton classes
!mod.singleton_class? &&
# extend ActiveSupport::Concern, and
mod.singleton_class < ::FFI::Library
end
end
end
end
end
end
end
5 changes: 4 additions & 1 deletion spec/ffi/function_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ module LibTest
end

it 'can be attached to a module' do
module Foo; end
module Foo
class_variable_set(:@@ffi_functions, {})
end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
fp.attach(Foo, 'add')
expect(Foo.add(10, 10)).to eq(20)
Expand All @@ -63,6 +65,7 @@ module Foo; end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
foo = Object.new
class << foo
class_variable_set(:@@ffi_functions, {})
def singleton_class
class << self; self; end
end
Expand Down
13 changes: 13 additions & 0 deletions spec/ffi/library_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,17 @@ class StructUCDP < FFI::Struct
end
expect(mod.bool_return_true).to be true
end

it "can reveal the function type" do
mod = Module.new do |m|
m.extend FFI::Library
ffi_lib File.expand_path(TestLibrary::PATH)
attach_function :bool_return_true, [ :string ], :bool
end
expect(mod.attached_functions).to eq(
{ bool_return_true: [[FFI::Type::STRING], FFI::Type::BOOL] }
)
end
end

def gvar_lib(name, type)
Expand Down Expand Up @@ -320,6 +331,8 @@ class GlobalStruct < FFI::Struct
lib.gvar[:data] = i
val = GlobalStruct.new(lib.get)
expect(val[:data]).to eq(i)

expect(lib.attached_getters).to eq({ gvar: GlobalStruct })
end
end
end
6 changes: 5 additions & 1 deletion spec/ffi/struct_by_ref_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,9 @@

expect { @api.struct_test(other_class.new) }.to raise_error(TypeError)
end
end

it "can reveal the mapped type converter" do
param_type = @api.attached_functions[:struct_test][0][0]
expect(param_type.converter).to be_a(FFI::StructByReference)
end
end
4 changes: 4 additions & 0 deletions spec/ffi/variadic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ module LibTest
expect(LibTest.pack_varargs2(buf, :c1, "ii", :int, :c3, :int, :c4)).to eq(:c2)
end

it "can reveal its return" do
expect(LibTest.attached_functions[:testBlockingWRva]).to eq([[FFI::Type::POINTER, FFI::Type::CHAR, :varargs], FFI::Type::INT8])
end

it 'can wrap a blocking function with varargs' do
handle = LibTest.testBlockingOpen
expect(handle).not_to be_null
Expand Down