From 015629339f8f92132aa445f429a8c78a8ec3b8dd Mon Sep 17 00:00:00 2001 From: furunkel Date: Fri, 9 Sep 2016 12:00:44 +0200 Subject: [PATCH] Add Pointer#read_array_of, Pointer#write_array_of, Pointer#get_array_of, Pointer#put_array_of --- ext/ffi_c/AbstractMemory.c | 185 +++++++++++++++------------- ext/ffi_c/AbstractMemory.h | 11 +- ext/ffi_c/Type.c | 12 +- ext/ffi_c/Type.h | 1 + lib/ffi/pointer.rb | 22 ++++ spec/ffi/rbx/memory_pointer_spec.rb | 47 ++++++- 6 files changed, 182 insertions(+), 96 deletions(-) diff --git a/ext/ffi_c/AbstractMemory.c b/ext/ffi_c/AbstractMemory.c index 684907bb6..221afdac1 100644 --- a/ext/ffi_c/AbstractMemory.c +++ b/ext/ffi_c/AbstractMemory.c @@ -76,7 +76,6 @@ memory_allocate(VALUE klass) #define VAL(x, swap) (unlikely(((memory->flags & MEM_SWAP) != 0)) ? swap((x)) : (x)) #define NUM_OP(name, type, toNative, fromNative, swap) \ -static void memory_op_put_##name(AbstractMemory* memory, long off, VALUE value); \ static void \ memory_op_put_##name(AbstractMemory* memory, long off, VALUE value) \ { \ @@ -85,7 +84,43 @@ memory_op_put_##name(AbstractMemory* memory, long off, VALUE value) \ checkBounds(memory, off, sizeof(type)); \ memcpy(memory->address + off, &tmp, sizeof(tmp)); \ } \ -static VALUE memory_put_##name(VALUE self, VALUE offset, VALUE value); \ +static VALUE \ +memory_op_get_##name(AbstractMemory* memory, long off) \ +{ \ + type tmp; \ + checkRead(memory); \ + checkBounds(memory, off, sizeof(type)); \ + memcpy(&tmp, memory->address + off, sizeof(tmp)); \ + return fromNative(VAL(tmp, swap)); \ +} \ +static void \ +memory_op_put_array_of_##name(AbstractMemory *memory, long off, VALUE ary) \ +{ \ + long count = RARRAY_LEN(ary); \ + long i; \ + checkWrite(memory); \ + checkBounds(memory, off, count * sizeof(type)); \ + for (i = 0; i < count; i++) { \ + type tmp = (type) VAL(toNative(RARRAY_PTR(ary)[i]), swap); \ + memcpy(memory->address + off + (i * sizeof(type)), &tmp, sizeof(tmp)); \ + } \ +} \ +static VALUE \ +memory_op_get_array_of_##name(AbstractMemory *memory, long off, long count) \ +{ \ + VALUE retVal = rb_ary_new2(count); \ + long i; \ + checkRead(memory); \ + checkBounds(memory, off, count * sizeof(type)); \ + for (i = 0; i < count; ++i) { \ + type tmp; \ + memcpy(&tmp, memory->address + off + (i * sizeof(type)), sizeof(tmp)); \ + rb_ary_push(retVal, fromNative(VAL(tmp, swap))); \ + } \ + return retVal; \ +} \ +static MemoryOp memory_op_##name = { memory_op_get_##name, memory_op_put_##name, memory_op_get_array_of_##name, memory_op_put_array_of_##name }; \ +\ static VALUE \ memory_put_##name(VALUE self, VALUE offset, VALUE value) \ { \ @@ -94,7 +129,6 @@ memory_put_##name(VALUE self, VALUE offset, VALUE value) \ memory_op_put_##name(memory, NUM2LONG(offset), value); \ return self; \ } \ -static VALUE memory_write_##name(VALUE self, VALUE value); \ static VALUE \ memory_write_##name(VALUE self, VALUE value) \ { \ @@ -103,17 +137,6 @@ memory_write_##name(VALUE self, VALUE value) \ memory_op_put_##name(memory, 0, value); \ return self; \ } \ -static VALUE memory_op_get_##name(AbstractMemory* memory, long off); \ -static VALUE \ -memory_op_get_##name(AbstractMemory* memory, long off) \ -{ \ - type tmp; \ - checkRead(memory); \ - checkBounds(memory, off, sizeof(type)); \ - memcpy(&tmp, memory->address + off, sizeof(tmp)); \ - return fromNative(VAL(tmp, swap)); \ -} \ -static VALUE memory_get_##name(VALUE self, VALUE offset); \ static VALUE \ memory_get_##name(VALUE self, VALUE offset) \ { \ @@ -121,7 +144,6 @@ memory_get_##name(VALUE self, VALUE offset) \ Data_Get_Struct(self, AbstractMemory, memory); \ return memory_op_get_##name(memory, NUM2LONG(offset)); \ } \ -static VALUE memory_read_##name(VALUE self); \ static VALUE \ memory_read_##name(VALUE self) \ { \ @@ -129,53 +151,35 @@ memory_read_##name(VALUE self) \ Data_Get_Struct(self, AbstractMemory, memory); \ return memory_op_get_##name(memory, 0); \ } \ -static MemoryOp memory_op_##name = { memory_op_get_##name, memory_op_put_##name }; \ -\ -static VALUE memory_put_array_of_##name(VALUE self, VALUE offset, VALUE ary); \ static VALUE \ memory_put_array_of_##name(VALUE self, VALUE offset, VALUE ary) \ { \ - long count = RARRAY_LEN(ary); \ long off = NUM2LONG(offset); \ AbstractMemory* memory = MEMORY(self); \ - long i; \ - checkWrite(memory); \ - checkBounds(memory, off, count * sizeof(type)); \ - for (i = 0; i < count; i++) { \ - type tmp = (type) VAL(toNative(RARRAY_PTR(ary)[i]), swap); \ - memcpy(memory->address + off + (i * sizeof(type)), &tmp, sizeof(tmp)); \ - } \ + memory_op_put_array_of_##name(memory, off, ary); \ return self; \ } \ -static VALUE memory_write_array_of_##name(VALUE self, VALUE ary); \ static VALUE \ memory_write_array_of_##name(VALUE self, VALUE ary) \ { \ - return memory_put_array_of_##name(self, INT2FIX(0), ary); \ + AbstractMemory* memory = MEMORY(self); \ + memory_op_put_array_of_##name(memory, 0, ary); \ + return self; \ } \ -static VALUE memory_get_array_of_##name(VALUE self, VALUE offset, VALUE length); \ static VALUE \ memory_get_array_of_##name(VALUE self, VALUE offset, VALUE length) \ { \ long count = NUM2LONG(length); \ long off = NUM2LONG(offset); \ AbstractMemory* memory = MEMORY(self); \ - VALUE retVal = rb_ary_new2(count); \ - long i; \ - checkRead(memory); \ - checkBounds(memory, off, count * sizeof(type)); \ - for (i = 0; i < count; ++i) { \ - type tmp; \ - memcpy(&tmp, memory->address + off + (i * sizeof(type)), sizeof(tmp)); \ - rb_ary_push(retVal, fromNative(VAL(tmp, swap))); \ - } \ - return retVal; \ + return memory_op_get_array_of_##name(memory, off, count); \ } \ -static VALUE memory_read_array_of_##name(VALUE self, VALUE length); \ static VALUE \ memory_read_array_of_##name(VALUE self, VALUE length) \ { \ - return memory_get_array_of_##name(self, INT2FIX(0), length); \ + long count = NUM2LONG(length); \ + AbstractMemory* memory = MEMORY(self); \ + return memory_op_get_array_of_##name(memory, 0, count); \ } #define NOSWAP(x) (x) @@ -327,71 +331,74 @@ memory_size(VALUE self) return LONG2NUM(ptr->size); } + +#define MEM_OP_GET_PUT(func, ret, ...) \ + AbstractMemory* ptr; \ + Type *type; \ + VALUE nType = rbffi_Type_Find(typeName); \ + Data_Get_Struct(self, AbstractMemory, ptr); \ + Data_Get_Struct(nType, Type, type); \ + MemoryOp *op = get_memory_op(type); \ + if(op == NULL || op->func == NULL) rbffi_type_raise_invalid(typeName); \ + ret op->func(ptr, __VA_ARGS__); + /* * call-seq: memory.get(type, offset) * Return data of given type contained in memory. - * @param [Symbol, Type] type_name type of data to get + * @param [Symbol, Type] typeName type of data to get * @param [Numeric] offset point in buffer to start from * @return [Object] - * @raise {ArgumentError} if type is not supported + * @raise {TypeError} if type is not supported */ static VALUE -memory_get(VALUE self, VALUE type_name, VALUE offset) +memory_get(VALUE self, VALUE typeName, VALUE offset) { - AbstractMemory* ptr; - VALUE nType; - Type *type; - - nType = rbffi_Type_Lookup(type_name); - if(NIL_P(nType)) goto undefined_type; - - Data_Get_Struct(self, AbstractMemory, ptr); - Data_Get_Struct(nType, Type, type); - - MemoryOp *op = get_memory_op(type); - if(op == NULL) goto undefined_type; - - return op->get(ptr, NUM2LONG(offset)); + MEM_OP_GET_PUT(get, return, NUM2LONG(offset)); +} -undefined_type: { - VALUE msg = rb_sprintf("undefined type '%" PRIsVALUE "'", type_name); - rb_exc_raise(rb_exc_new3(rb_eArgError, msg)); - return Qnil; - } +/* + * call-seq: memory.get_array_of(type, offset) + * Return array of given type contained in memory. + * @param [Symbol, Type] typeName type of data to get + * @param [Numeric] offset point in buffer to start from + * @return [Object] + * @raise {TypeError} if type is not supported + */ +static VALUE +memory_get_array_of(VALUE self, VALUE typeName, VALUE offset, VALUE length) +{ + MEM_OP_GET_PUT(getArray, return, NUM2LONG(offset), NUM2LONG(length)); } /* * call-seq: memory.put(type, offset, value) - * @param [Symbol, Type] type_name type of data to put + * @param [Symbol, Type] typeName type of data to put * @param [Numeric] offset point in buffer to start from - * @return [nil] - * @raise {ArgumentError} if type is not supported + * @return [self] + * @raise {TypeError} if type is not supported */ static VALUE -memory_put(VALUE self, VALUE type_name, VALUE offset, VALUE value) +memory_put(VALUE self, VALUE typeName, VALUE offset, VALUE value) { - AbstractMemory* ptr; - VALUE nType; - Type *type; - - nType = rbffi_Type_Lookup(type_name); - if(NIL_P(nType)) goto undefined_type; + MEM_OP_GET_PUT(put, (void), NUM2LONG(offset), value); +} - Data_Get_Struct(self, AbstractMemory, ptr); - Data_Get_Struct(nType, Type, type); - MemoryOp *op = get_memory_op(type); - if(op == NULL) goto undefined_type; +/* + * call-seq: memory.put_array_of(type, offset, ary) + * @param [Symbol, Type] type type of data to write to pointer's contents + * @param [Numeric] offset point in buffer to start from + * @param [Array] ary array of values + * @return [self] + * @raise {TypeError} if type is not supported + */ +static VALUE +memory_put_array_of(VALUE self, VALUE typeName, VALUE offset, VALUE ary) +{ + MEM_OP_GET_PUT(putArray, (void), NUM2LONG(offset), ary); +} - op->put(ptr, NUM2LONG(offset), value); - return Qnil; -undefined_type: { - VALUE msg = rb_sprintf("unsupported type '%" PRIsVALUE "'", type_name); - rb_exc_raise(rb_exc_new3(rb_eArgError, msg)); - return Qnil; - } -} /* * call-seq: memory.get_string(offset, length=nil) @@ -421,6 +428,7 @@ memory_get_string(int argc, VALUE* argv, VALUE self) (end != NULL ? end - ptr->address - off : len)); } + /* * call-seq: memory.get_array_of_string(offset, count=nil) * Return an array of strings contained in memory. @@ -450,7 +458,7 @@ memory_get_array_of_string(int argc, VALUE* argv, VALUE self) int i; checkBounds(ptr, off, count * sizeof (char*)); - + for (i = 0; i < count; ++i) { const char* strptr = *((const char**) (ptr->address + off) + i); rb_ary_push(retVal, (strptr == NULL ? Qnil : rb_tainted_str_new2(strptr))); @@ -727,8 +735,7 @@ memory_op_put_strptr(AbstractMemory* ptr, long offset, VALUE value) rb_raise(rb_eArgError, "Cannot set :string fields"); } -static MemoryOp memory_op_strptr = { memory_op_get_strptr, memory_op_put_strptr }; - +static MemoryOp memory_op_strptr = { memory_op_get_strptr, memory_op_put_strptr, NULL, NULL}; MemoryOps rbffi_AbstractMemoryOps = { &memory_op_int8, /*.int8 */ @@ -1094,6 +1101,8 @@ rbffi_AbstractMemory_Init(VALUE moduleFFI) rb_define_method(classMemory, "get", memory_get, 2); rb_define_method(classMemory, "put", memory_put, 3); + rb_define_method(classMemory, "get_array_of", memory_get_array_of, 3); + rb_define_method(classMemory, "put_array_of", memory_put_array_of, 3); rb_define_method(classMemory, "clear", memory_clear, 0); rb_define_method(classMemory, "total", memory_size, 0); diff --git a/ext/ffi_c/AbstractMemory.h b/ext/ffi_c/AbstractMemory.h index 11192885a..8c7c918a4 100644 --- a/ext/ffi_c/AbstractMemory.h +++ b/ext/ffi_c/AbstractMemory.h @@ -54,9 +54,16 @@ extern "C" { typedef struct AbstractMemory_ AbstractMemory; +typedef VALUE (*AbstractMemoryGetFunc)(AbstractMemory* ptr, long offset); +typedef void (*AbstractMemoryPutFunc)(AbstractMemory* ptr, long offset, VALUE value); +typedef VALUE (*AbstractMemoryGetArrayFunc)(AbstractMemory* ptr, long offset, long count); +typedef void (*AbstractMemoryPutArrayFunc)(AbstractMemory* ptr, long offset, VALUE ary); + typedef struct { - VALUE (*get)(AbstractMemory* ptr, long offset); - void (*put)(AbstractMemory* ptr, long offset, VALUE value); + AbstractMemoryGetFunc get; + AbstractMemoryPutFunc put; + AbstractMemoryGetArrayFunc getArray; + AbstractMemoryPutArrayFunc putArray; } MemoryOp; typedef struct { diff --git a/ext/ffi_c/Type.c b/ext/ffi_c/Type.c index 034482f2c..30892eb72 100644 --- a/ext/ffi_c/Type.c +++ b/ext/ffi_c/Type.c @@ -214,6 +214,14 @@ rbffi_type_size(VALUE type) } } +void +rbffi_type_raise_invalid(VALUE typeName) +{ + VALUE s = rb_inspect(typeName); + rb_raise(rb_eTypeError, "invalid type, %s", RSTRING_PTR(s)); + RB_GC_GUARD(s); +} + VALUE rbffi_Type_Lookup(VALUE name) { @@ -245,9 +253,7 @@ rbffi_Type_Find(VALUE name) VALUE rbType = rbffi_Type_Lookup(name); if (!RTEST(rbType)) { - VALUE s = rb_inspect(name); - rb_raise(rb_eTypeError, "invalid type, %s", RSTRING_PTR(s)); - RB_GC_GUARD(s); + rbffi_type_raise_invalid(name); } return rbType; diff --git a/ext/ffi_c/Type.h b/ext/ffi_c/Type.h index d5522ee8a..71cff58c5 100644 --- a/ext/ffi_c/Type.h +++ b/ext/ffi_c/Type.h @@ -51,6 +51,7 @@ struct Type_ { }; extern VALUE rbffi_TypeClass; +extern void rbffi_type_raise_invalid(VALUE typeName); extern VALUE rbffi_Type_Lookup(VALUE type); extern VALUE rbffi_Type_Find(VALUE type); diff --git a/lib/ffi/pointer.rb b/lib/ffi/pointer.rb index de4f91f65..960ebab11 100644 --- a/lib/ffi/pointer.rb +++ b/lib/ffi/pointer.rb @@ -103,6 +103,7 @@ def write_string(str, len=nil) # Read an array of +type+ of length +length+. # @example # ptr.read_array_of_type(TYPE_UINT8, :get_uint8, 4) # -> [1, 2, 3, 4] + # @deprecated Use {#read_array_of} instead def read_array_of_type(type, reader, length) ary = [] size = FFI.type_size(type) @@ -121,6 +122,7 @@ def read_array_of_type(type, reader, length) # Write +ary+ in pointer's contents as +type+. # @example # ptr.write_array_of_type(TYPE_UINT8, :put_uint8, [1, 2, 3 ,4]) + # @deprecated Use {#write_array_of} instead def write_array_of_type(type, writer, ary) size = FFI.type_size(type) tmp = self @@ -131,6 +133,26 @@ def write_array_of_type(type, writer, ary) self end + # @param [Type] type type of data to write to pointer's contents + # @param [Array] ary array of values + # @return [self] + # Write +ary+ in pointer's contents as +type+. + # @example + # ptr.write_array_of(:uint8, [1, 2, 3 ,4]) + def write_array_of(type, ary) + put_array_of(type, 0, ary) + end + + # @param [Type] type type of data to read from pointer's contents + # @param [Numeric] length + # @return [Array] + # Read an array of +type+ of length +length+. + # @example + # ptr.read_array_of_type(:uint8, 4) # -> [1, 2, 3, 4] + def read_array_of(type, length) + get_array_of(type, 0, length) + end + # @return [self] def to_ptr self diff --git a/spec/ffi/rbx/memory_pointer_spec.rb b/spec/ffi/rbx/memory_pointer_spec.rb index a869d8c62..1059ac3f9 100644 --- a/spec/ffi/rbx/memory_pointer_spec.rb +++ b/spec/ffi/rbx/memory_pointer_spec.rb @@ -10,6 +10,10 @@ module CTest extend FFI::Library ffi_lib FFI::Library::LIBC + class FooStruct < FFI::Struct + layout :a, :int, :b, :long + end + attach_function :strcat, [:pointer, :pointer], :pointer end @@ -119,11 +123,48 @@ module CTest expect(m.read :fubar2_t).to eq(10) end - it "raises an error if you try to read an undefined type" do + it "allows writing and reading an array of a custom typedef" do + FFI.typedef :int, :fubar_t + FFI.typedef :size_t, :fubar2_t + + values = [0, 1, 2, 3, 0xFFFFFFF, -10] + m = FFI::MemoryPointer.new(:fubar_t, 10) + m.write_array_of(:fubar_t, values) + expect(m.read_array_of :fubar_t, 3).to eq([0, 1, 2]) + expect(m.read_array_of :fubar_t, values.size).to eq(values) + + values = [0, 1, 2, 3, 0xFFFFFFFF] + m = FFI::MemoryPointer.new(:fubar2_t, 10) + m.write_array_of(:fubar2_t, values) + expect(m.read_array_of :fubar2_t, 3).to eq([0, 1, 2]) + expect(m.read_array_of :fubar2_t, values.size).to eq(values) + end + + it "raises an error if you try to write or read an undefined type" do m = FFI::MemoryPointer.new(:long) - expect { m.read(:undefined_type) }.to raise_error(ArgumentError) + expect { m.read(:undefined_type) }.to raise_error(TypeError) + expect { m.read(::CTest::FooStruct) }.to raise_error(TypeError) + + expect { m.write(:undefined_type, 10) }.to raise_error(TypeError) + expect { m.write(::CTest::FooStruct, ::CTest::FooStruct.new) }.to raise_error(TypeError) + + m = FFI::MemoryPointer.new(:long, 10) + expect { m.read_array_of(:undefined_type, 10) }.to raise_error(TypeError) + expect { m.read_array_of(::CTest::FooStruct, 10) }.to raise_error(TypeError) + expect { m.write_array_of(:undefined_type, [1,2,3]) }.to raise_error(TypeError) + expect { m.write_array_of(::CTest::FooStruct, [::CTest::FooStruct.new]) }.to raise_error(TypeError) end - + + it "raises an error if you try to write a value that would overflow memory" do + m = FFI::MemoryPointer.new(:uint8, 1) + expect { m.write(:long, 10) }.to raise_error(IndexError) + expect { m.write_array_of(:long, [10]) }.to raise_error(IndexError) + + m = FFI::MemoryPointer.new(:uint8, 8) + expect { m.write_array_of(:uint64, Array.new(2, 0)) }.to raise_error(IndexError) + expect { m.write_array_of(:uint8, Array.new(9, 0)) }.to raise_error(IndexError) + end + it "raises an error if you try putting a long into a pointer of size 1" do m = FFI::MemoryPointer.new(1) expect { m.write_long(10) }.to raise_error