366 lines
11 KiB
C
366 lines
11 KiB
C
/*
|
|
* Copyright (c) 2008-2010 Wayne Meissner
|
|
* Copyright (C) 2009 Aman Gupta <aman@tmm1.net>
|
|
*
|
|
* Copyright (c) 2008-2013, Ruby FFI project contributors
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the Ruby FFI project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef _MSC_VER
|
|
# include <stdint.h>
|
|
# include <stdbool.h>
|
|
#else
|
|
# include "win32/stdbool.h"
|
|
# include "win32/stdint.h"
|
|
#endif
|
|
#include <limits.h>
|
|
#include <ruby.h>
|
|
#include "rbffi.h"
|
|
#include "rbffi_endian.h"
|
|
#include "AbstractMemory.h"
|
|
|
|
#define BUFFER_EMBED_MAXLEN (8)
|
|
typedef struct Buffer {
|
|
AbstractMemory memory;
|
|
|
|
union {
|
|
VALUE rbParent; /* link to parent buffer */
|
|
char* storage; /* start of malloc area */
|
|
long embed[BUFFER_EMBED_MAXLEN / sizeof(long)]; /* storage for tiny allocations */
|
|
} data;
|
|
} Buffer;
|
|
|
|
static VALUE buffer_allocate(VALUE klass);
|
|
static VALUE buffer_initialize(int argc, VALUE* argv, VALUE self);
|
|
static void buffer_release(Buffer* ptr);
|
|
static void buffer_mark(Buffer* ptr);
|
|
static VALUE buffer_free(VALUE self);
|
|
|
|
static VALUE BufferClass = Qnil;
|
|
|
|
static VALUE
|
|
buffer_allocate(VALUE klass)
|
|
{
|
|
Buffer* buffer;
|
|
VALUE obj;
|
|
|
|
obj = Data_Make_Struct(klass, Buffer, NULL, buffer_release, buffer);
|
|
buffer->data.rbParent = Qnil;
|
|
buffer->memory.flags = MEM_RD | MEM_WR;
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void
|
|
buffer_release(Buffer* ptr)
|
|
{
|
|
if ((ptr->memory.flags & MEM_EMBED) == 0 && ptr->data.storage != NULL) {
|
|
xfree(ptr->data.storage);
|
|
ptr->data.storage = NULL;
|
|
}
|
|
|
|
xfree(ptr);
|
|
}
|
|
|
|
/*
|
|
* call-seq: initialize(size, count=1, clear=false)
|
|
* @param [Integer, Symbol, #size] Type or size in bytes of a buffer cell
|
|
* @param [Fixnum] count number of cell in the Buffer
|
|
* @param [Boolean] clear if true, set the buffer to all-zero
|
|
* @return [self]
|
|
* @raise {NoMemoryError} if failed to allocate memory for Buffer
|
|
* A new instance of Buffer.
|
|
*/
|
|
static VALUE
|
|
buffer_initialize(int argc, VALUE* argv, VALUE self)
|
|
{
|
|
VALUE rbSize = Qnil, rbCount = Qnil, rbClear = Qnil;
|
|
Buffer* p;
|
|
int nargs;
|
|
|
|
Data_Get_Struct(self, Buffer, p);
|
|
|
|
nargs = rb_scan_args(argc, argv, "12", &rbSize, &rbCount, &rbClear);
|
|
p->memory.typeSize = rbffi_type_size(rbSize);
|
|
p->memory.size = p->memory.typeSize * (nargs > 1 ? NUM2LONG(rbCount) : 1);
|
|
|
|
if (p->memory.size > BUFFER_EMBED_MAXLEN) {
|
|
p->data.storage = xmalloc(p->memory.size + 7);
|
|
if (p->data.storage == NULL) {
|
|
rb_raise(rb_eNoMemError, "Failed to allocate memory size=%lu bytes", p->memory.size);
|
|
return Qnil;
|
|
}
|
|
|
|
/* ensure the memory is aligned on at least a 8 byte boundary */
|
|
p->memory.address = (void *) (((uintptr_t) p->data.storage + 0x7) & (uintptr_t) ~0x7UL);
|
|
|
|
if (p->memory.size > 0 && (nargs < 3 || RTEST(rbClear))) {
|
|
memset(p->memory.address, 0, p->memory.size);
|
|
}
|
|
|
|
} else {
|
|
p->memory.flags |= MEM_EMBED;
|
|
p->memory.address = (void *) &p->data.embed[0];
|
|
}
|
|
|
|
if (rb_block_given_p()) {
|
|
return rb_ensure(rb_yield, self, buffer_free, self);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq: initialize_copy(other)
|
|
* @return [self]
|
|
* DO NOT CALL THIS METHOD.
|
|
*/
|
|
static VALUE
|
|
buffer_initialize_copy(VALUE self, VALUE other)
|
|
{
|
|
AbstractMemory* src;
|
|
Buffer* dst;
|
|
|
|
Data_Get_Struct(self, Buffer, dst);
|
|
src = rbffi_AbstractMemory_Cast(other, BufferClass);
|
|
if ((dst->memory.flags & MEM_EMBED) == 0 && dst->data.storage != NULL) {
|
|
xfree(dst->data.storage);
|
|
}
|
|
dst->data.storage = xmalloc(src->size + 7);
|
|
if (dst->data.storage == NULL) {
|
|
rb_raise(rb_eNoMemError, "failed to allocate memory size=%lu bytes", src->size);
|
|
return Qnil;
|
|
}
|
|
|
|
dst->memory.address = (void *) (((uintptr_t) dst->data.storage + 0x7) & (uintptr_t) ~0x7UL);
|
|
dst->memory.size = src->size;
|
|
dst->memory.typeSize = src->typeSize;
|
|
|
|
/* finally, copy the actual buffer contents */
|
|
memcpy(dst->memory.address, src->address, src->size);
|
|
|
|
return self;
|
|
}
|
|
|
|
static VALUE
|
|
buffer_alloc_inout(int argc, VALUE* argv, VALUE klass)
|
|
{
|
|
return buffer_initialize(argc, argv, buffer_allocate(klass));
|
|
}
|
|
|
|
static VALUE
|
|
slice(VALUE self, long offset, long len)
|
|
{
|
|
Buffer* ptr;
|
|
Buffer* result;
|
|
VALUE obj = Qnil;
|
|
|
|
Data_Get_Struct(self, Buffer, ptr);
|
|
checkBounds(&ptr->memory, offset, len);
|
|
|
|
obj = Data_Make_Struct(BufferClass, Buffer, buffer_mark, -1, result);
|
|
result->memory.address = ptr->memory.address + offset;
|
|
result->memory.size = len;
|
|
result->memory.flags = ptr->memory.flags;
|
|
result->memory.typeSize = ptr->memory.typeSize;
|
|
result->data.rbParent = self;
|
|
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* call-seq: + offset
|
|
* @param [Numeric] offset
|
|
* @return [Buffer] a new instance of Buffer pointing from offset until end of previous buffer.
|
|
* Add a Buffer with an offset
|
|
*/
|
|
static VALUE
|
|
buffer_plus(VALUE self, VALUE rbOffset)
|
|
{
|
|
Buffer* ptr;
|
|
long offset = NUM2LONG(rbOffset);
|
|
|
|
Data_Get_Struct(self, Buffer, ptr);
|
|
|
|
return slice(self, offset, ptr->memory.size - offset);
|
|
}
|
|
|
|
/*
|
|
* call-seq: slice(offset, length)
|
|
* @param [Numeric] offset
|
|
* @param [Numeric] length
|
|
* @return [Buffer] a new instance of Buffer
|
|
* Slice an existing Buffer.
|
|
*/
|
|
static VALUE
|
|
buffer_slice(VALUE self, VALUE rbOffset, VALUE rbLength)
|
|
{
|
|
return slice(self, NUM2LONG(rbOffset), NUM2LONG(rbLength));
|
|
}
|
|
|
|
/*
|
|
* call-seq: inspect
|
|
* @return [String]
|
|
* Inspect a Buffer.
|
|
*/
|
|
static VALUE
|
|
buffer_inspect(VALUE self)
|
|
{
|
|
char tmp[100];
|
|
Buffer* ptr;
|
|
|
|
Data_Get_Struct(self, Buffer, ptr);
|
|
|
|
snprintf(tmp, sizeof(tmp), "#<FFI:Buffer:%p address=%p size=%ld>", ptr, ptr->memory.address, ptr->memory.size);
|
|
|
|
return rb_str_new2(tmp);
|
|
}
|
|
|
|
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
# define SWAPPED_ORDER BIG_ENDIAN
|
|
#else
|
|
# define SWAPPED_ORDER LITTLE_ENDIAN
|
|
#endif
|
|
|
|
/*
|
|
* Set or get endianness of Buffer.
|
|
* @overload order
|
|
* @return [:big, :little]
|
|
* Get endianness of Buffer.
|
|
* @overload order(order)
|
|
* @param [:big, :little, :network] order
|
|
* @return [self]
|
|
* Set endianness of Buffer (+:network+ is an alias for +:big+).
|
|
*/
|
|
static VALUE
|
|
buffer_order(int argc, VALUE* argv, VALUE self)
|
|
{
|
|
Buffer* ptr;
|
|
|
|
Data_Get_Struct(self, Buffer, ptr);
|
|
if (argc == 0) {
|
|
int order = (ptr->memory.flags & MEM_SWAP) == 0 ? BYTE_ORDER : SWAPPED_ORDER;
|
|
return order == BIG_ENDIAN ? ID2SYM(rb_intern("big")) : ID2SYM(rb_intern("little"));
|
|
} else {
|
|
VALUE rbOrder = Qnil;
|
|
int order = BYTE_ORDER;
|
|
|
|
if (rb_scan_args(argc, argv, "1", &rbOrder) < 1) {
|
|
rb_raise(rb_eArgError, "need byte order");
|
|
}
|
|
if (SYMBOL_P(rbOrder)) {
|
|
ID id = SYM2ID(rbOrder);
|
|
if (id == rb_intern("little")) {
|
|
order = LITTLE_ENDIAN;
|
|
|
|
} else if (id == rb_intern("big") || id == rb_intern("network")) {
|
|
order = BIG_ENDIAN;
|
|
}
|
|
}
|
|
if (order != BYTE_ORDER) {
|
|
Buffer* p2;
|
|
VALUE retval = slice(self, 0, ptr->memory.size);
|
|
|
|
Data_Get_Struct(retval, Buffer, p2);
|
|
p2->memory.flags |= MEM_SWAP;
|
|
return retval;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
}
|
|
|
|
/* Only used to free the buffer if the yield in the initializer throws an exception */
|
|
static VALUE
|
|
buffer_free(VALUE self)
|
|
{
|
|
Buffer* ptr;
|
|
|
|
Data_Get_Struct(self, Buffer, ptr);
|
|
if ((ptr->memory.flags & MEM_EMBED) == 0 && ptr->data.storage != NULL) {
|
|
xfree(ptr->data.storage);
|
|
ptr->data.storage = NULL;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
buffer_mark(Buffer* ptr)
|
|
{
|
|
rb_gc_mark(ptr->data.rbParent);
|
|
}
|
|
|
|
void
|
|
rbffi_Buffer_Init(VALUE moduleFFI)
|
|
{
|
|
VALUE ffi_AbstractMemory = rbffi_AbstractMemoryClass;
|
|
|
|
/*
|
|
* Document-class: FFI::Buffer < FFI::AbstractMemory
|
|
*
|
|
* A Buffer is a function argument type. It should be use with functions playing with C arrays.
|
|
*/
|
|
BufferClass = rb_define_class_under(moduleFFI, "Buffer", ffi_AbstractMemory);
|
|
|
|
/*
|
|
* Document-variable: FFI::Buffer
|
|
*/
|
|
rb_global_variable(&BufferClass);
|
|
rb_define_alloc_func(BufferClass, buffer_allocate);
|
|
|
|
/*
|
|
* Document-method: alloc_inout
|
|
* call-seq: alloc_inout(*args)
|
|
* Create a new Buffer for in and out arguments (alias : <i>new_inout</i>).
|
|
*/
|
|
rb_define_singleton_method(BufferClass, "alloc_inout", buffer_alloc_inout, -1);
|
|
/*
|
|
* Document-method: alloc_out
|
|
* call-seq: alloc_out(*args)
|
|
* Create a new Buffer for out arguments (alias : <i>new_out</i>).
|
|
*/
|
|
rb_define_singleton_method(BufferClass, "alloc_out", buffer_alloc_inout, -1);
|
|
/*
|
|
* Document-method: alloc_in
|
|
* call-seq: alloc_in(*args)
|
|
* Create a new Buffer for in arguments (alias : <i>new_in</i>).
|
|
*/
|
|
rb_define_singleton_method(BufferClass, "alloc_in", buffer_alloc_inout, -1);
|
|
rb_define_alias(rb_singleton_class(BufferClass), "new_in", "alloc_in");
|
|
rb_define_alias(rb_singleton_class(BufferClass), "new_out", "alloc_out");
|
|
rb_define_alias(rb_singleton_class(BufferClass), "new_inout", "alloc_inout");
|
|
|
|
rb_define_method(BufferClass, "initialize", buffer_initialize, -1);
|
|
rb_define_method(BufferClass, "initialize_copy", buffer_initialize_copy, 1);
|
|
rb_define_method(BufferClass, "order", buffer_order, -1);
|
|
rb_define_method(BufferClass, "inspect", buffer_inspect, 0);
|
|
rb_define_alias(BufferClass, "length", "total");
|
|
rb_define_method(BufferClass, "+", buffer_plus, 1);
|
|
rb_define_method(BufferClass, "slice", buffer_slice, 2);
|
|
}
|
|
|