Oniromancie: Astuces - [Ruby] module C


Comment ça marche?

Aëdemphia
Par Sylvanor

Fighting Robots Quest
Par Boulon

Forstale
Par Ødd Clock

Geex
Par roys

Inexistence
Par Falco

La Légende d'Ibabou
Par Zaitan

Lije
Par Gaetz

LoveMaster
Par Cuddlefox

Sarcia
Par Kaëlar

Super Mario RPG - Lost Souls
Par Omegabowser

News: Concours des Alex d'Or 2017-18: (...) / News: MegaMaker : créez votre propre (...) / News: Test de Tinker Quarry / Sorties: Leave the Room / Jeux: Leave the Room /

Chat  (26 connectés)

Bienvenue
visiteur !








Statistiques

Liste des
membres


Contact

54 connectés actuellement

9179879 visiteurs
depuis l'ouverture

953 visiteurs
aujourd'hui

Groupe Facebook

Barre de séparation

Partenaires




TOP
GAMEMAKING


Les 5 plus
visités

Lunae, le baz'arts d'Emz0

Le studio du chat vert

Pixelandgame

Tashiroworld

HeyMakeGames

Au hasard

Kingdom Ultimate

The Forum Of Zelda

Skaaz - Création de MMORP

Les deux derniers

Lunae, le baz'arts d'Emz0

Le studio du chat vert

Nos autres partenaires

Devenir
partenaire


Barre de séparation

Un site du réseau
War Paradise

Annuaires référenceurs





[Ruby] module C
Ecrit par Zeus81

A la base j'ai fait ça pour moi et mes prochains scripts ultimes mais comme je vois qu'il y en a certains dans le coin qui font des scripts utilisant des dll en C je le partage avant l'heure.
Ce script permet de créer/lire/altérer des données typées C en Ruby.
Les avantages sont que c'est plus simple et efficace à utiliser que des String avec pack/unpack, les données pouvant être lues ET modifiées aussi bien en C qu'en Ruby et puis tout est automatique.
Et tout cela avec ce script de seulement 203 lignes, vous me direz sûrement "Jamais 203" et je vous répondrait "Oh que si !".

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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





Type :
On a 15 types de données prédéfinis et il n'y en a pas besoin de plus.
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 qui en fait est un UCHAR mais qui gère true et false de Ruby.
BOOL qui en fait est un INT mais qui gère true et false de Ruby.
POINTER qui peut pointer sur n'importe quel objet fourni par le module C.
N'importe quel autre type peut être converti en un de ceux là (vous pouvez vous aider de ça).



Enum :
C.Enum(type, [nom, [valeur, [nom, [valeur ...]]]])
C'est pas indispensable, c'est juste pour faciliter la création de constantes.
type est le Type qu'aura l'Enum au cas où on l'utiliserait dans un Struct (généralement c'est INT).
Les noms doivent être passés sous forme de symbole.
Les valeurs doivent être des entiers.
Si une valeur est omise la variable prendra la valeur précédente+1, ou 0 si c'est la première.

Portion de code : Tout sélectionner

1
2
3
Couleur = C.Enum(C::INT, :rouge, 0,
                         :vert , 1,
                         :bleu , 2)

équivaut à :

Portion de code : Tout sélectionner

1
2
3
Couleur = C.Enum(C::INT, :rouge,
                         :vert,
                         :bleu)


et pour lire une valeur on fait :

Portion de code : Tout sélectionner

1
Couleur.vert





Array :
C.Array(type, [dimension1, [dimension2 ...]])
type c'est n'importe quel Type du module C pour tous les éléments du tableau.
On peut spécifier autant de dimensions qu'on veut avec des entiers.

Portion de code : Tout sélectionner

1
2
I4 = C.Array(C::INT, 4)
tab = I4.new


I4 est la classe qui me permet de créer des tableaux de 4 entiers.
En général pour les tableaux on utilisera des classes anonymes en faisant directement :

Portion de code : Tout sélectionner

1
tab = C.Array(C::INT, 4).new


Par défaut un tableau est initialisé avec tout à 0, il existe plusieurs moyens de changer les valeurs.
Directement lors de l'initialisation on peut passer des paramètres dans l'ordre :

Portion de code : Tout sélectionner

1
tab = I4.new(60, 61, 62, ...)


dans le désordre :

Portion de code : Tout sélectionner

1
tab = I4.new(2=>62, 0=>60, ...)


ou avec un autre tableau de la même classe :

Portion de code : Tout sélectionner

1
tab2 = I4.new(tab1)


Une fois l'objet créé on peut toujours modifier ses données avec la fonction set (qui fonctionne comme new) ou avec l'opérateur []=

Portion de code : Tout sélectionner

1
tab[0] = 60


Pour lire les données on utilise []

Portion de code : Tout sélectionner

1
tab[0] # => 60


ou la méthode get qui retournera un tableau Ruby de toutes les valeurs

Portion de code : Tout sélectionner

1
tab.get # => [60, 61, 62, 63]


Les tableaux multidimensionnels ne sont en fait que des tableaux de tableaux :

Portion de code : Tout sélectionner

1
C.Array(C::INT, 4, 3)

équivaut à :

Portion de code : Tout sélectionner

1
C.Array(C.Array(C::INT, 3), 4)


Et donc on peut modifier les données de plusieurs façons différentes :

Portion de code : Tout sélectionner

1
2
3
4
5
6
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]]


Je sais, j'explique très mal, mais normalement ça devrait être assez logique. :F



Struct :
C.Struct([type, [nom, [type, [nom, ...]]]])
type peut être un INT, CHAR, etc... mais aussi Struct, Union, Enum, Array.
Les noms doivent être passés sous forme de symbole.
Si un type est omis la variable prendra le type précédent.
Si un nom est omis la variable sera anonyme.
Si la variable anonyme est un Struct, Union ou Array, ses fonctions seront hérités (comme en C).

En gros un struct c'est comme un Array sauf que chaque élément a un type et un nom différent.

Portion de code : Tout sélectionner

1
2
Fruit = C.Struct(C::FLOAT, :diametre,
                 C::BOOL , :pepins)


Comme pour un Array il y a plusieurs manières de configurer un Struct :

Portion de code : Tout sélectionner

1
2
3
4
5
6
pomme = Fruit.new
pomme.set(5.8, true)
pomme.set(:diametre=>5.8, :pepins=>true)
pomme.set(0=>5.8, 1=>true)
pomme[0] = 5.8 # Attention si le Struct contient un Array anonyme, cela n'est plus possible
pomme.pepins = true


Même avec un Struct get renvoie toujours un tableau des données :

Portion de code : Tout sélectionner

1
pomme.get # => [5.8, true]


Attention, si un Struct contient un autre Struct il faudra grouper les données comme pour les tableaux multidimensionnels.

Portion de code : Tout sélectionner

1
2
3
4
5
6
Arbre = C.Struct(C::INT , :taille,
                 Fruit  , :fruit,
                 Couleur, :couleur)
Arbre.new(9, 5.8, true, Couleur.vert) # => erreur
Arbre.new(9, [5.8, true], Couleur.vert) # => ok
Arbre.new(9, pomme, Couleur.vert) # => ok





Union :
Marche comme Struct à quelques détails près.
Exemple d'un Union avec Struct et Array anonymes :

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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])
# Attention, il faut choisir selon quel élément de l'Union on va modifier les données,
# vu que les deux sont anonymes je fais avec les id, ici 0 c'est le Struct, pour l'Array j'aurais dû faire :
# 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
                 # Vu que les éléments sont anonymes leurs fonctions sont hérités.
                 # m[3] fait non pas appel à l'élément 3 de m mais à la ligne 3 de l'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]] ]
      # le get d'un Union retourne un tableau contenant tous ses formats.





Pointer :
Un petit topo sur les pointeurs vu que c'est pas forcément évident.

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
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

Je pense que c'est suffisamment explicite.

Autre exemple sur ce qui correspondrait à une double indirection :

Portion de code : Tout sélectionner

1
2
a.ptr = C::POINTER.new(C::INT.new(128)) # int**
a.ptr.get.get # => 128


Et comme dit plus haut, un pointeur peut pointer sur n'importe quel objet du module C :

Portion de code : Tout sélectionner

1
2
a.ptr = a
a.ptr.get # => a


Autre chose sur les pointeurs, si on récupère un pointeur via une Api :

Portion de code : Tout sélectionner

1
ptr = Win32API.new('dll', 'GetPointer', '', 'i').call


On peut récupérer/modifier la valeur vers où il pointe quel que soit le type, par exemple si c'est un float :

Portion de code : Tout sélectionner

1
2
3
f = C::FLOAT.new {ptr}
f.get # => récupère la valeur en mémoire
f.set(0.1) # la modifie


Et ça marche aussi avec les Struct/Array/Union :

Portion de code : Tout sélectionner

1
2
MonStruct = C.Struct(...)
s = MonStruct.new {ptr}


Bien sûr il faut que MonStruct soit l'exacte réplique du struct C vers qui il pointe.
Attention cependant aux allocations de mémoire, si je récupère le pointeur vers une donnée de la dll qui a été libérée il risque d'y avoir des problèmes.



Exemple simple et concret d'utilisation, si je veux reproduire cette structure et utiliser cette Api je fais :

Portion de code : Tout sélectionner

1
2
3
4
5
POINT = C.Struct(C::LONG, :x, :y)
GetCursorPos = Win32API.new('user32', 'GetCursorPos', 'i', 'i') # on met 'i' pour le struct et non 'p' pour éviter les copies dans certains cas
$cursor = POINT.new
GetCursorPos.call($cursor) # on passe directement l'objet
$cursor.x # retourne la position x du curseur


Bon bien sûr ça vaut pas vraiment le coût dans cet exemple d'utiliser mon script de 203 lignes (si si) alors qu'avec un string il nous en faudrait 4 mais c'est pour ceux qui veulent faire des trucs hautement plus compliqués.

J'avais un peu la flemme de tout expliquer en détail alors si vous avez des questions (sur le module uniquement) n'hésitez pas à les poser les ici.


Tata Monos - posté le 15/04/2011 à 18:22:02. (56278 messages postés) - admin

Vive le making libre

On va peut être y arriver un jour à régler ce bug xd

Pixner|Offgame | Le comptoire des clikeurs


Sylvanor - posté le 15/04/2011 à 18:30:06. (20167 messages postés) - webmaster

Le gars chiant qui rigole jamais (il paraît)

GG!
Presque aussi bien qu'un tuto de Zeus81!

Les croissants croâssent en croix, s'ancrent ou à cent croîssent sans crocs à sang. Crois! Sens! ౡ


Tata Monos - posté le 15/04/2011 à 18:35:50. (56278 messages postés) - admin

Vive le making libre

Merci. xd

Edit : la il va me maudire nonor xd

Pixner|Offgame | Le comptoire des clikeurs


charles ingalls - posté le 15/04/2011 à 18:45:59. (999 messages postés)

Citation:

avec ce script de seulement 203 lignes, vous me direz sûrement "Jamais 203" et je vous répondrait "Oh que si !


:lol elle est pas mal celle là


Tata Monos - posté le 15/04/2011 à 19:05:38. (56278 messages postés) - admin

Vive le making libre

yeah c'est corrigé aller nouga.

Pixner|Offgame | Le comptoire des clikeurs


Zeus[ ] - posté le 15/04/2011 à 19:44:29. (11072 messages postés) - bocauxharam

Amen. :lei
Bon maintenant faudrait penser à supprimer l'ancien. :F


Tata Monos - posté le 15/04/2011 à 20:10:45. (56278 messages postés) - admin

Vive le making libre

:D
Suite au prochain épisode.

Pixner|Offgame | Le comptoire des clikeurs

Suite à de nombreux abus, le post en invités a été désactivé. Veuillez vous inscrire si vous souhaitez participer à la conversation.

Haut de page

Merci de ne pas reproduire le contenu de ce site sans autorisation.
Contacter l'équipe

Plan du site:

Activité: Accueil | News | Forum | Flash-news | Chat | Commentaires | Galerie | Screen de la semaine | Sorties | Articles perso | Livre d'or | Recherche
Jeux: Index jeux séparés | Top Classiques | Top Originaux | Les autres | RPG Maker 95 | RPG Maker 2000 | RPG Maker 2003 | RPG Maker XP | RPG Maker VX | RPG Maker VX Ace | RPG Maker MV | Autres | Jeux complets | Proposer
Rubriques: Le Wiki | Collection Oniro | Tutoriaux | Scripts | Guides | Gaming-Live | Tests | Making-of | Interviews | Articles perso | OST | L'Annuaire | Divers | Palmarès
Hébergés: Aëdemphia | Fighting Robots Quest | Forstale | Geex | Inexistence | La Légende d'Ibabou | Lije | LoveMaster | Sarcia | Super Mario RPG - Lost Souls
Ressources: Jeux | Programmes | Packs de ressources | Midis | Eléments séparés | Sprites
RPG Maker 2000/2003: Chipsets | Charsets | Panoramas | Backdrops | Facesets | Battle anims | Battle charsets | Monstres | Systems | Templates
RPG Maker XP: Tilesets | Autotiles | Characters | Battlers | Window skins | Icônes | Transitions | Fogs | Templates
RPG Maker VX: Tilesets | Charsets | Facesets | Systèmes
RPG Maker MV: Tilesets | Characters | Faces | Systèmes | Title | Battlebacks | Animations | SV/Ennemis