/*
* Copyright (c) 2008-2010 Wayne Meissner
*
* All rights reserved.
*
* This file is part of ruby-ffi.
*
* This code is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License version 3 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see .
*/
#ifndef _MSC_VER
#include
#else
typedef int bool;
#define true 1
#define false 0
#endif
#ifndef _MSC_VER
#include
#endif
#include
#include
#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), "#", 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 endinaness 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)
{
/*
* 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", rbffi_AbstractMemoryClass);
/*
* 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 : new_inout).
*/
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 : new_out).
*/
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 : new_in).
*/
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);
}