By: Zeus81
Introduction
This script almost does nothing, it's a script for scripters, so if you're not you can pass your way, it won't interest you.
This module allows you to create/read/alter C typed data directly in Ruby and it's easier to use than Strings with pack/unpack, everything being done automatically.
If you intend to do a big script that use complicated structures or need to communicate in both ways with a dll then that's for you.
Script
Ruby:
module C # v 5.1 by Zeus81
RtlMoveMemory = Win32API.new('kernel32', 'RtlMoveMemory', 'iii', '')
def self.memcpy(destination, source, size)
RtlMoveMemory.call(
destination.is_a?(String) ? string_address(destination) : destination,
source.is_a?(String) ? string_address(source) : source, size)
destination
rescue raise($!, $!.message, caller)
end
def self.string_address(string) [string].pack('p').unpack('L')[0]
rescue raise($!, $!.message, caller)
end
@anonymous_class_count = 0
def self.name_anonymous_class(type)
if type.name.empty?
type.superclass.name =~ /.*::(.*)/
const_set("Anonymous#{$1}_%02X" % @anonymous_class_count+=1, type)
end
rescue raise($!, $!.message, caller)
end
def self.make_accessors(klass, data, child=nil)
data.each_with_index do |d,i|
if d[2] != nil
a = d[2] == :[] ? 'i,' : ''
b = d[2] == :[] ? '[i]' : child ? ".#{d[2]}" : "[#{i}]"
b = "[#{child}]#{b}" if child
c = b + (child ? '=' : '.set')
b << '.get' unless child or d[0] < CData
klass << "
define_method(:#{d[2]}) {|#{a[0,1]}| @children#{b}}
define_method(:#{d[2]}=) {|#{a}v| @children#{c}(v)}"
break if d[2] == :[]
else make_accessors(klass, d[0].data, child || i)
end
end
rescue raise($!, $!.message, caller)
end
class CType
def self.format() self::FORMAT end
def format() type.format end
def self.size() self::SIZE end
def size() type.size end
def initialize(*v, &b)
@address = b ? b.call.to_int : C.string_address(@string=("\0"*size).freeze)
@children = data.map {|t,o| t.new {to_int+o}} if type < C::CData
set(*v) unless v.empty?
rescue raise($!, $!.message, caller)
end
def get() unpack
rescue raise($!, $!.message, caller)
end
def set(v) pack(v)
rescue raise($!, $!.message, caller)
end
def unpack() to_str.unpack(format)[0].freeze
rescue raise($!, $!.message, caller)
end
def pack(v) C.memcpy(self, v.is_a?(type) ? v : [v].pack(format), size)
rescue raise($!, $!.message, caller)
end
def to_int() @address
rescue raise($!, $!.message, caller)
end
def to_str() @string or C.memcpy(("\0"*size).freeze, self, size)
rescue raise($!, $!.message, caller)
end
end
class CData < CType
def self.data() self::DATA end
def data() type.data end
def get() @children.map {|c| c.get}.freeze
rescue raise($!, $!.message, caller)
end
def set(*v)
if v[0].is_a?(type); super(v[0])
elsif v[0].is_a?(Hash)
v[0].each {|n,v| n.is_a?(Integer) ? @children[n].set(*v) : send("#{n}=",v)}
else v.each_with_index {|v,i| @children[i].set(*v)}
end
rescue raise($!, $!.message, caller)
end
def [](i) data[i][0] < CData ? @children[i] : @children[i].get
rescue raise($!, $!.message, caller)
end
def []=(i,v) @children[i].set(*v)
rescue raise($!, $!.message, caller)
end
end
CStruct, CUnion, CArray = Class.new(CData), Class.new(CData), Class.new(CData)
def self.Class(type, klass)
raise(TypeError,"Type expected, got #{type.type}") unless type.is_a?(Class) and
type <= CType
Class.new(type) {class_eval(klass, __FILE__, __LINE__)}
rescue raise($!, $!.message, caller)
end
def self.Type(format, size)
raise(TypeError,"String expected, got #{format.type}") unless format.is_a?(String)
raise(TypeError,"Integer expected, got #{size.type}") unless size.is_a?(Integer)
Class(CType, "SIZE, FORMAT = #{size}, '#{format}'.freeze")
rescue raise($!, $!.message, caller)
end
def self.Enum(type, *variables)
h, v = {}, -1
variables.each_with_index do |n,i|
next unless n.is_a?(Symbol)
h[n] = v = (next_v=variables[i+1]).is_a?(Symbol) ? v.succ : next_v
end
Class(type, "DATA = #{h.inspect}.freeze
def self.method_missing(sym, *args) DATA[sym] or super
rescue raise($!, $!.message, caller)
end")
rescue raise($!, $!.message, caller)
end
def self.Data(type, variables, array_size=nil)
size, data, t = 0, [], nil
variables.each_with_index do |n,i|
if n.is_a?(Class) and n < CType
t, n = n, nil
next if variables[i+1].is_a?(Symbol)
end
next unless t != nil and (n==nil or n.is_a?(Symbol))
name_anonymous_class(t)
if type == CStruct
data << [t,size,n]
size = size+t.size
elsif type == CUnion
data << [t,0,n]
size = t.size if t.size > size
elsif type == CArray
data.replace(Array.new(array_size) {|j| [t,j*t.size,n]})
size = t.size*array_size
end
end
klass = "SIZE, FORMAT, DATA = #{size}, 'a#{size}'.freeze, #{data.inspect}.freeze"
make_accessors(klass, data) unless type == CArray
Class(type, klass)
rescue raise($!, $!.message, caller)
end
def self.Struct(*variables) Data(CStruct, variables)
rescue raise($!, $!.message, caller)
end
def self.Union (*variables) Data(CUnion , variables)
rescue raise($!, $!.message, caller)
end
def self.Array (type, *dimensions)
dimensions.reverse_each {|s| type=Data(CArray , [type,:[]], s)}
type
rescue raise($!, $!.message, caller)
end
CHAR = Type('c',1)
UCHAR = BYTE = Type('C',1)
SHORT = Type('s',2)
USHORT = WORD = Type('S',2)
INT = Type('i',4)
UINT = Type('I',4)
LONG = Type('l',4)
ULONG = DWORD = Type('L',4)
LONGLONG = Type('q',8)
ULONGLONG = DWORDLONG = Type('Q',8)
FLOAT = Type('f',4)
DOUBLE = Type('d',8)
BOOLEAN = Type('C',1)
BOOL = Type('i',4)
POINTER = Type('L',4)
class BOOLEAN
def unpack() super==0 ? false : true
rescue raise($!, $!.message, caller)
end
def pack(v) super(!v || v==0 ? 0 : 1)
rescue raise($!, $!.message, caller)
end
end
class BOOL
def unpack() super==0 ? false : true
rescue raise($!, $!.message, caller)
end
def pack(v) super(!v || v==0 ? 0 : 1)
rescue raise($!, $!.message, caller)
end
end
class POINTER
def unpack() @pointer
rescue raise($!, $!.message, caller)
end
def pack(v) super(@pointer=v ? v.to_int : 0)
rescue raise($!, $!.message, caller)
end
end
end
Instructions
Type:
There are 15 predefined data types and there is no need for more.
CHAR = C signed char
UCHAR = BYTE = C unsigned char
SHORT = C signed short
USHORT = WORD = C unsigned short
INT = C signed int
UINT = C unsigned int
LONG = C signed long
ULONG = DWORD = C unsigned long
LONGLONG = C signed long long
ULONGLONG = DWORDLONG = C unsigned long long
FLOAT = C float
DOUBLE = C double
BOOLEAN which is an UCHAR but that can handle Ruby's true & false.
BOOL which is an INT but that can handle Ruby's true & false.
POINTER which can point on any object given by the C module.
Enum:
C.Enum(type, [name, [value, [name, [value...]]]])
It's not indispensable, it's just to simplify the creation of constants.
type is the Type the Enum will have in case we use it in a Struct (usualy it's INT).
The names must be Symbols.
The values must be Integers.
If a value is omitted it'll take the previous one + 1, or 0 if it's the first.
equals:
and to read a value we do:
Color.green
Array:
C.Array(type, [dimension1, [dimension2 ...]])
type is the Type all the elements of the array will have.
We can specify as much dimensions as we want with Integers.
I4 = C.Array(C::INT, 4)
tab = I4.new
I4 is the class that will allow me to create a 4 int array.
Generally for arrays we'll use anonymous classes by directly doing:
tab = C.Array(C::INT, 4).new
By default an array is initialized with everything to 0, there is many methods to change values.
Immediately during instantiation by passing parameters in order:
tab = I4.new(60, 61, 62, ...)
in disorder:
tab = I4.new(2=>62, 0=>60, ...)
or with an object of the same class:
tab2 = I4.new(tab1)
Once the object is created we can modify values with the set method (which works like new) or with the operator []= :
tab[0] = 60
To read values we use [] :
tab[0] # => 60
or get method which returns a Ruby array of all values:
tab.get # => [60, 61, 62, 63]
Multidimensional Arrays are in fact Arrays of Arrays:
C.Array(C::INT, 4, 3)
equals:
C.Array(C.Array(C::INT, 3), 4)
And so we can change values in several ways:
tab = C.Array(C::INT, 4, 3).new
tab[0][2] = 1
tab[1].set(2, 3, 4)
tab[2] = [5, 6, 7]
tab.set(3=>[8, 9])
tab.get # => [[0, 0, 1], [2, 3, 4], [5, 6, 7], [8, 9, 0]]
I know, I explained very poorly, but normally it should be fairly logical.
Struct:
C.Struct([type, [name, [type, [name, ...]]]])
type can be an INT, CHAR, etc. ... but also Struct, Union, Enum Array.
Names should be passed as symbols.
If a type is omitted the variable will have the previous type.
If a name is omitted the variable will be anonymous.
If the anonymous variable is a Struct, Union or Array, its functions will be inherited (as in C).
Basically a Struct is like an Array except that each element has a different type and name.
Even with a Struct get still returns a Ruby array of values:
apple.get # => [5.8, true]
Please note, if a Struct contains another Struct you must group the data as for Multidimensional Arrays.
Union:
It works like Struct except a few details.
Example of an Union with anonymous Struct and Array:
Pointer:
A short piece about the pointers because it's not obvious.
A = C.Struct(C::POINTER, :ptr)
a = A.new
b = C::CHAR.new(7) # char*
a.ptr = b
a.ptr.get # => 7
b.set(45)
a.ptr.get # => 45
a.ptr.set(-16)
b.get # => -16
I think that it is clear enough.
Another example of what would correspond to a double indirection:
a.ptr = C::POINTER.new(C::INT.new(128)) # int**
a.ptr.get.get # => 128
And as said before, a pointer can point to any object of the C module:
a.ptr = a
a.ptr.get # => a
Another thing about pointers, if you retrieve a pointer via an API:
ptr = Win32API.new('dll', 'GetPointer', '', 'i').call
We can read / change the value to which it points, whatever the type is, by passing the address into a block of a new object.
For example if it's a float:
f = C::FLOAT.new {ptr}
f.get # => retrieves the value directly from memory
f.set(0.1) # change it
And it also works with Struct/Array/Union:
MyStruct = C.Struct(...)
s = MyStruct.new {ptr}
Of course MyStruct must be an exact replica of the C struct to which it points.
However, beware to memory allocations, if I get the pointer to a data of the dll which was released there may be problems.
There are 15 predefined data types and there is no need for more.
CHAR = C signed char
UCHAR = BYTE = C unsigned char
SHORT = C signed short
USHORT = WORD = C unsigned short
INT = C signed int
UINT = C unsigned int
LONG = C signed long
ULONG = DWORD = C unsigned long
LONGLONG = C signed long long
ULONGLONG = DWORDLONG = C unsigned long long
FLOAT = C float
DOUBLE = C double
BOOLEAN which is an UCHAR but that can handle Ruby's true & false.
BOOL which is an INT but that can handle Ruby's true & false.
POINTER which can point on any object given by the C module.
Enum:
C.Enum(type, [name, [value, [name, [value...]]]])
It's not indispensable, it's just to simplify the creation of constants.
type is the Type the Enum will have in case we use it in a Struct (usualy it's INT).
The names must be Symbols.
The values must be Integers.
If a value is omitted it'll take the previous one + 1, or 0 if it's the first.
Color = C.Enum(C::INT, :red , 0,
:green, 1,
:blue , 2)
equals:
Color = C.Enum(C::INT, :red ,
:green,
:blue )
and to read a value we do:
Color.green
Array:
C.Array(type, [dimension1, [dimension2 ...]])
type is the Type all the elements of the array will have.
We can specify as much dimensions as we want with Integers.
I4 = C.Array(C::INT, 4)
tab = I4.new
I4 is the class that will allow me to create a 4 int array.
Generally for arrays we'll use anonymous classes by directly doing:
tab = C.Array(C::INT, 4).new
By default an array is initialized with everything to 0, there is many methods to change values.
Immediately during instantiation by passing parameters in order:
tab = I4.new(60, 61, 62, ...)
in disorder:
tab = I4.new(2=>62, 0=>60, ...)
or with an object of the same class:
tab2 = I4.new(tab1)
Once the object is created we can modify values with the set method (which works like new) or with the operator []= :
tab[0] = 60
To read values we use [] :
tab[0] # => 60
or get method which returns a Ruby array of all values:
tab.get # => [60, 61, 62, 63]
Multidimensional Arrays are in fact Arrays of Arrays:
C.Array(C::INT, 4, 3)
equals:
C.Array(C.Array(C::INT, 3), 4)
And so we can change values in several ways:
tab = C.Array(C::INT, 4, 3).new
tab[0][2] = 1
tab[1].set(2, 3, 4)
tab[2] = [5, 6, 7]
tab.set(3=>[8, 9])
tab.get # => [[0, 0, 1], [2, 3, 4], [5, 6, 7], [8, 9, 0]]
I know, I explained very poorly, but normally it should be fairly logical.
Struct:
C.Struct([type, [name, [type, [name, ...]]]])
type can be an INT, CHAR, etc. ... but also Struct, Union, Enum Array.
Names should be passed as symbols.
If a type is omitted the variable will have the previous type.
If a name is omitted the variable will be anonymous.
If the anonymous variable is a Struct, Union or Array, its functions will be inherited (as in C).
Basically a Struct is like an Array except that each element has a different type and name.
Fruit = C.Struct(C::FLOAT, :diameter,
C::BOOL , :seeds)
As for an Array there are several ways to configure a Struct:
apple = Fruit.new
apple.set(5.8, true)
apple.setdiameter=>5.8, :seeds=>true)
apple.set(0=>5.8, 1=>true)
apple[0] = 5.8 # Beware if the Struct contains an anonymous Array, this is not possible.
apple.seeds = true
apple1.set(apple2)
Even with a Struct get still returns a Ruby array of values:
apple.get # => [5.8, true]
Please note, if a Struct contains another Struct you must group the data as for Multidimensional Arrays.
Tree = C.Struct(C::INT, :height,
Fruit , :fruit,
Color , :color)
Tree.new(9, 5.8, true, Color.green) # => error
Tree.new(9, [5.8, true], Color.green) # => ok
Tree.new(9, apple, Color.green) # => ok
Union:
It works like Struct except a few details.
Example of an Union with anonymous Struct and Array:
MATRIX = C.Union(C.Struct(FLOAT, :_11, :_12, :_13, :_14,
:_21, :_22, :_23, :_24,
:_31, :_32, :_33, :_34,
:_41, :_42, :_43, :_44),
C.Array(FLOAT,4,4))
m = MATRIX.new(0=>[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])
# You must choose according to which format of the Union you will modify the data,
# since both are anonymous I do with the id, here 0 is the Struct, for the Array I should have done:
# m = MATRIX.new(1=>[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
m._43 == m[3][2] # => true
# Because the elements are anonymous their functions are inherited
# m[3] don't call the element 3 of m but the row 3 of the Array.
m.get # => [ [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],
# [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
# ]
# the get of an Union returns an array containing all its formats.
Pointer:
A short piece about the pointers because it's not obvious.
A = C.Struct(C::POINTER, :ptr)
a = A.new
b = C::CHAR.new(7) # char*
a.ptr = b
a.ptr.get # => 7
b.set(45)
a.ptr.get # => 45
a.ptr.set(-16)
b.get # => -16
I think that it is clear enough.
Another example of what would correspond to a double indirection:
a.ptr = C::POINTER.new(C::INT.new(128)) # int**
a.ptr.get.get # => 128
And as said before, a pointer can point to any object of the C module:
a.ptr = a
a.ptr.get # => a
Another thing about pointers, if you retrieve a pointer via an API:
ptr = Win32API.new('dll', 'GetPointer', '', 'i').call
We can read / change the value to which it points, whatever the type is, by passing the address into a block of a new object.
For example if it's a float:
f = C::FLOAT.new {ptr}
f.get # => retrieves the value directly from memory
f.set(0.1) # change it
And it also works with Struct/Array/Union:
MyStruct = C.Struct(...)
s = MyStruct.new {ptr}
Of course MyStruct must be an exact replica of the C struct to which it points.
However, beware to memory allocations, if I get the pointer to a data of the dll which was released there may be problems.
Simple and concrete example of use, if I want to reproduce this structure and use this Api I do:
POINT = C.Struct(C::LONG, :x, :y)
GetCursorPos = Win32API.new('user32', 'GetCursorPos', 'i', 'i') # we put 'i' for the struct and not 'p ' to avoid copies in some cases
$cursor = POINT.new
GetCursorPos.call($cursor) # we pass the object directly.
$cursor.x # returns the x position of the cursor
Well of course in this example it's not really intredasting to use my script, but that's for those who want to do things far more complicated.