nice-ffi 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+
2
+ = Nice-FFI
3
+
4
+ Version:: 0.1
5
+ Date:: 2009-07-05
6
+
7
+ Homepage:: http://github.com/jacius/nice-ffi/
8
+ Author:: John Croisant <jacius@gmail.com>
9
+ Copyright:: 2009 John Croisant
10
+
11
+
12
+ == Description
13
+
14
+ Nice-FFI is a layer on top of Ruby-FFI [1] (and compatible FFI
15
+ systems) to augment it with features to aid development of
16
+ FFI-based libraries.
17
+
18
+ Nice-FFI currently features:
19
+
20
+ * NiceFFI::Struct: a stand-in for FFI::Struct that provides automatic
21
+ accessors for struct members, more instance initialization options,
22
+ pretty to_s and inspect methods, and other niceties.
23
+
24
+ * NiceFFI::Library: a stand-in for FFI::Library that provides methods
25
+ for easily finding and loading libraries on any platform, plus
26
+ automatic wrapping of functions that return struct pointers.
27
+
28
+ Nice-FFI was originally developed as part of Ruby-SDL-FFI [2].
29
+
30
+ 1. Ruby-FFI: http://kenai.com/projects/ruby-ffi/
31
+ 2. Ruby-SDL-FFI: http://github.com/jacius/ruby-sdl-ffi/
32
+
33
+
34
+ == Caveats
35
+
36
+ Nice-FFI is still in EARLY DEVELOPMENT STAGES. That means:
37
+
38
+ * It may not work correctly (or at all).
39
+ * It may not be complete.
40
+ * It may change drastically with no advanced notice.
41
+
42
+ As such, this library is currently FOR THE ADVENTUROUS ONLY.
43
+ If you are not willing to continuously update your code to
44
+ match the new API, then you should wait until version 1.0.
45
+
46
+
47
+ == Requirements
48
+
49
+ * Ruby-FFI >= 0.3.0 (or compatible)
50
+ * need >= 1.1.0
51
+
52
+
53
+ == License
54
+
55
+ Nice-FFI is licensed under the following terms (the "MIT License"):
56
+
57
+ Copyright (c) 2009 John Croisant
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining
60
+ a copy of this software and associated documentation files (the
61
+ "Software"), to deal in the Software without restriction, including
62
+ without limitation the rights to use, copy, modify, merge, publish,
63
+ distribute, sublicense, and/or sell copies of the Software, and to
64
+ permit persons to whom the Software is furnished to do so, subject to
65
+ the following conditions:
66
+
67
+ The above copyright notice and this permission notice shall be
68
+ included in all copies or substantial portions of the Software.
69
+
70
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
71
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
72
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
73
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
74
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
75
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
76
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+
2
+ = TODO
3
+
4
+ * Struct
5
+ * Write accessor for nested structs (not struct pointers).
6
+ See comment in NiceFFI::Struct._make_writer for details.
7
+
8
+ * Specs
9
+ * Library (use a mock for ffi_lib.)
10
+ * PathSet#find (use a mock for File.exist?.)
11
+ * Struct
12
+ * TypedPointer
13
+
14
+ * Better documentation comments
15
+ * Library#attach_function
16
+ * Library#load_library
@@ -0,0 +1,254 @@
1
+ = Using Nice-FFI
2
+
3
+ This is a guide on how to use Nice-FFI's features. It assumes that you
4
+ are already somewhat familiar with Ruby-FFI.
5
+
6
+
7
+ == NiceFFI::Library
8
+
9
+ NiceFFI::Library is a drop-in replacement for FFI::Library. It provides
10
+ improved library finding abilities and support for TypedPointer return
11
+ types for attached functions.
12
+
13
+
14
+ In fact, NiceFFI::Library *is* FFI::Library, but with a few extras.
15
+ That means that you can do all the regular FFI::Library stuff as well
16
+ as the stuff described here.
17
+
18
+
19
+ === load_library
20
+
21
+ NiceFFI::Library.load_library is a more convenient replacement for
22
+ FFI::Library.ffi_lib. It uses NiceFFI::PathSet to search for the
23
+ library in the most likely places, depending on the user's operating
24
+ system. For example, on Linux it would look for "lib[NAME].so" in
25
+ "/usr/lib/" (among others), while on Windows it would look for
26
+ "[NAME].dll" in "C:\windows\system32\".
27
+
28
+ Using load_library is easy. Just use "extend NiceFFI::Library" instead
29
+ of "extend FFI::Library", and use "load_library" instead of "ffi_lib":
30
+
31
+
32
+ require 'nice-ffi'
33
+
34
+ module MyLibraryModule
35
+ extend NiceFFI::Library
36
+
37
+ load_library("SDL") # look for libSDL.so, SDL.dll, etc.
38
+
39
+ # structs, functions, etc. as usual.
40
+
41
+ end
42
+
43
+
44
+ ==== Advanced load_library
45
+
46
+ As mentioned, load_library uses NiceFFI::PathSet to search for the
47
+ library in likely directories. Specifically, it looks for:
48
+
49
+
50
+ NiceFFI::Library::DEFAULT_PATHS = NiceFFI::PathSet.new(
51
+
52
+ /linux|bsd/ => [ "/usr/local/lib/lib[NAME].so",
53
+ "/usr/lib/lib[NAME].so",
54
+ "[NAME]" ],
55
+
56
+ /darwin/ => [ "/usr/local/lib/lib[NAME].dylib",
57
+ "/sw/lib/lib[NAME].dylib",
58
+ "/opt/local/lib/lib[NAME].dylib",
59
+ "~/Library/Frameworks/[NAME].framework/[NAME]",
60
+ "/Library/Frameworks/[NAME].framework/[NAME]",
61
+ "[NAME]" ],
62
+
63
+ /win32/ => [ "C:\\windows\\system32\\[NAME].dll",
64
+ "C:\\windows\\system\\[NAME].dll",
65
+ "[NAME]" ]
66
+ )
67
+
68
+
69
+ The string "[NAME]" is replaced with whatever string you pass to
70
+ load_library.
71
+
72
+ So, if the user is running Linux and you try to load "SDL", it will
73
+ first look for "/usr/local/lib/libSDL.so". If it couldn't find the
74
+ first one, it will then look for "/usr/lib/libSDL.so", and finally it
75
+ will just try loading "SDL" using ffi_lib (which does some
76
+ platform-appropriate guesses too). It would also use those same
77
+ paths for FreeBSD, because that OS matches /linux|bsd/, too.
78
+ /darwin/ matches on MacOS X, and /win32/ matches on Windows.
79
+
80
+ If you want to load from a different path, you can make a custom
81
+ PathSet and pass it to load_library:
82
+
83
+
84
+ this_dir = File.dirname(__FILE__)
85
+
86
+ my_pathset = NiceFFI::Library::DEFAULT_PATHS.prepend(
87
+ /linux|bsd/ => [ "#{this_dir}/libs/lib[NAME].so" ],
88
+ /darwin/ => [ "#{this_dir}/libs/lib[NAME].dylib" ],
89
+ /win32/ => [ "#{this_dir}/libs/[NAME].dll" ]
90
+ )
91
+
92
+ load_library( "SDL", my_pathset )
93
+
94
+
95
+ The above example prepends (adds in front) the new paths so
96
+ that load_library will look for the library in in "./libs/" first.
97
+ See PathSet for other useful methods for modifying PathSets.
98
+
99
+
100
+ Another advanced usage tip: If a library has several alternative
101
+ names, you can provide an Array of names:
102
+
103
+
104
+ # It might be called "foo", "foo2", or "Foo".
105
+
106
+ load_library( ["foo", "foo2", "Foo"] )
107
+
108
+
109
+ === attach_function
110
+
111
+ NiceFFI::Library#attach_function behaves similarly to
112
+ FFI::Library#attach_function, except it supports TypedPointer return
113
+ values. For example, suppose you have a C function:
114
+
115
+
116
+ MyStruct *make_my_struct( int x, int y );
117
+
118
+
119
+ This returns a pointer to an instance of MyStruct. With FFI, you'd
120
+ write this to attach it:
121
+
122
+
123
+ attach_function :make_my_struct, [:int, :int], :pointer
124
+
125
+
126
+ And when you called it, it would return an FFI::Pointer, which you
127
+ would then have to manually wrap every time you called the method:
128
+
129
+
130
+ ptr = make_my_struct( 1, 2 )
131
+ mystruct = MyStruct.new( ptr )
132
+
133
+
134
+ With TypedPointer, the wrapping happens automatically. Just attach
135
+ the function with a TypedPointer instead of :pointer:
136
+
137
+
138
+ attach_function :get_my_struct, [:int], NiceFFI::TypedPointer( MyStruct )
139
+
140
+
141
+ Then you automatically get a MyStruct instance when you call the function:
142
+
143
+
144
+ mystruct = make_my_struct( 1, 2 )
145
+ mystruct.instance_of?( MyStruct ) # => Heck yeah it sure is!
146
+
147
+
148
+ Voila!
149
+
150
+
151
+
152
+ == NiceFFI::Struct
153
+
154
+ NiceFFI::Struct is a replacement for FFI::Struct. It provides several
155
+ features in addition to the normal FFI::Struct behavior:
156
+
157
+ * Ability to construct new instances from Array, Hash, another instance,
158
+ or a pointer as usual.
159
+ * Automatic read and write accessors for struct members.
160
+ * Accessors for struct pointer members with TypedPointer.
161
+ * Ability to dump an instance as an Array (#to_ary) or Hash (#to_hash).
162
+ * Pretty and useful #to_s and #inspect for debugging.
163
+
164
+
165
+ === Constructors
166
+
167
+ NiceFFI::Struct allows you to construct a new struct instance from
168
+ a Hash, Array, or another existing instance of the same struct type.
169
+ It can also accept a pointer, just as with FFI::Struct.
170
+
171
+
172
+ class MyStruct < NiceFFI::Struct
173
+ layout :x, :int,
174
+ :y, :int
175
+ end
176
+
177
+ mystruct = MyStruct.new( {:x => 1, :y => 2} ) # from Hash
178
+ mystruct2 = MyStruct.new( [1,2] ) # from Array
179
+ mystruct3 = MyStruct.new( mystruct ) # from another instance
180
+ mystruct4 = MyStruct.new( ptr ) # from Pointer
181
+
182
+
183
+ === Struct Member Accessors
184
+
185
+ Struct members are defined automatically when you use
186
+ NiceFFI::Struct.layout:
187
+
188
+
189
+ class MyStruct < NiceFFI::Struct
190
+ layout :x, :int,
191
+ :y, :int
192
+ end
193
+
194
+ mystruct = MyStruct.new({:x => 1, :y => 2})
195
+
196
+ mystruct.x # => 1
197
+ mystruct.y # => 2
198
+
199
+ mystruct.x = 3
200
+ mystruct.y = -4
201
+
202
+
203
+ Sometimes a struct will have members that should be read-only,
204
+ or completely hidden. In those cases, you can use
205
+ NiceFFI::Struct.read_only and NiceFFI::Struct.hidden.
206
+
207
+
208
+ class MySneakyStruct < NiceFFI::Struct
209
+ layout :readme, :int,
210
+ :readme2, :int,
211
+ :hideme, :pointer,
212
+ :hideme2, :pointer,
213
+ :normal, :uint32
214
+
215
+ read_only :readme, :readme2
216
+ hidden :hideme, :hideme2
217
+ end
218
+
219
+ sneaky = MySneakyStruct.new( ... )
220
+
221
+
222
+ read_only prevents a write accessor from being created (or removes
223
+ it if there is already one). hidden does the same, but for both
224
+ read and write accessors. hidden also prevents the member from
225
+ being shown in #to_s and #inspect.
226
+
227
+ read_only and hidden can go before or after layout (or both),
228
+ and you can safely call them multiple times if you need to.
229
+
230
+
231
+ === TypedPointer Struct Member Accessors
232
+
233
+ Some struct members are :pointers that point to other structs.
234
+ With FFI::Struct, you'd have to manually wrap and unwrap the
235
+ struct pointer, but if you specify a TypedPointer instead of
236
+ :pointer, NiceFFI::Struct will wrap and unwrap it automatically:
237
+
238
+
239
+ class StructWithPtr < NiceFFI::Struct
240
+ layout :x, :int,
241
+ :y, :int,
242
+ :my, NiceFFI::TypedPointer( MyStruct )
243
+ end
244
+
245
+ struct = StructWithPtr.new( :x => -1,
246
+ :y => -2,
247
+ :my => MyStruct.new([1,2]) )
248
+
249
+ # Seamlessly wraps the pointer in a struct
250
+ struct.my.kind_of? MyStruct # true
251
+
252
+ # Seamlessly unwraps the struct and stores the pointer
253
+ struct.my = MyStruct.new([-4,-3])
254
+
@@ -0,0 +1,47 @@
1
+ #--
2
+ #
3
+ # This file is one part of:
4
+ #
5
+ # Nice-FFI - Convenience layer atop Ruby-FFI
6
+ #
7
+ # Copyright (c) 2009 John Croisant
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ #++
29
+
30
+
31
+ require 'need'
32
+
33
+
34
+ module NiceFFI
35
+ end
36
+
37
+
38
+ %w{
39
+
40
+ nicelibrary
41
+ nicestruct
42
+
43
+ }.each do |f|
44
+
45
+ need { File.join( 'nice-ffi', f ) }
46
+
47
+ end
@@ -0,0 +1,172 @@
1
+ #--
2
+ #
3
+ # This file is one part of:
4
+ #
5
+ # Nice-FFI - Convenience layer atop Ruby-FFI
6
+ #
7
+ # Copyright (c) 2009 John Croisant
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ #++
29
+
30
+
31
+ require 'ffi'
32
+
33
+ need{ 'typedpointer' }
34
+ need{ 'pathset' }
35
+
36
+
37
+ # A module to be used in place of FFI::Library. It acts mostly
38
+ # like FFI::Library, but with some nice extra features and
39
+ # conveniences to make life easier:
40
+ #
41
+ # * attach_function accepts TypedPointers as return type,
42
+ # in which case it wraps the return value of the bound function
43
+ # in the TypedPointer's type.
44
+ #
45
+ module NiceFFI::Library
46
+
47
+ def self.extend_object( klass )
48
+ klass.extend FFI::Library
49
+ super
50
+ end
51
+
52
+
53
+ # The default paths to look for libraries. See PathSet
54
+ # and #load_library.
55
+ #
56
+ DEFAULT_PATHS = NiceFFI::PathSet.new(
57
+
58
+ /linux|bsd/ => [ "/usr/local/lib/lib[NAME].so",
59
+ "/usr/lib/lib[NAME].so",
60
+ "[NAME]" ],
61
+
62
+ /darwin/ => [ "/usr/local/lib/lib[NAME].dylib",
63
+ "/sw/lib/lib[NAME].dylib",
64
+ "/opt/local/lib/lib[NAME].dylib",
65
+ "~/Library/Frameworks/[NAME].framework/[NAME]",
66
+ "/Library/Frameworks/[NAME].framework/[NAME]",
67
+ "[NAME]" ],
68
+
69
+ /win32/ => [ "C:\\windows\\system32\\[NAME].dll",
70
+ "C:\\windows\\system\\[NAME].dll",
71
+ "[NAME]" ]
72
+
73
+ )
74
+
75
+
76
+ # Try to find and load a library (e.g. "SDL_ttf") into an FFI
77
+ # wrapper module (e.g. SDL::TTF). This method searches in
78
+ # different locations depending on your OS. See PathSet.
79
+ #
80
+ # Returns the path to the library that was loaded.
81
+ #
82
+ # Raises LoadError if it could not find or load the library.
83
+ #
84
+ def load_library( names, search_paths=NiceFFI::Library::DEFAULT_PATHS )
85
+
86
+ names = [names] unless names.kind_of? Array
87
+
88
+ paths = search_paths.find( *names )
89
+
90
+ pretty_names = if names.size == 1
91
+ names[0]
92
+ else
93
+ names[0..-2].join(", ") + ", or " + names[-1]
94
+ end
95
+
96
+ # Oops, couldn't find it anywhere.
97
+ if paths.empty?
98
+ raise LoadError, "Could not find #{pretty_names}. Is it installed?"
99
+ end
100
+
101
+ # Try loading each path until one works.
102
+ loaded = paths.find { |path|
103
+ begin
104
+ self.module_eval {
105
+ ffi_lib path
106
+ }
107
+ rescue LoadError
108
+ false
109
+ else
110
+ true
111
+ end
112
+ }
113
+
114
+ # Oops, none of them worked.
115
+ if loaded.nil?
116
+ raise( LoadError, "Could not load #{pretty_names}." )
117
+ else
118
+ # Return the one that did work
119
+ return loaded
120
+ end
121
+ end
122
+
123
+
124
+ def attach_function( methname, arg1, arg2, arg3=nil )
125
+
126
+ # To match the normal attach_function's weird syntax.
127
+ # The arguments can be either:
128
+ #
129
+ # 1. methname, args, retrn_type (funcname = methname)
130
+ # 2. methname, funcname, args, retrn_type
131
+ #
132
+ funcname, args, retrn_type = if arg1.kind_of?(Array)
133
+ [methname, arg1, arg2]
134
+ else
135
+ [arg1, arg2, arg3]
136
+ end
137
+
138
+ unless retrn_type.kind_of? NiceFFI::TypedPointer
139
+ # Normal FFI::Library.attach_function behavior.
140
+ super
141
+ else
142
+
143
+ # Create the raw FFI binding, which returns a pointer.
144
+ # We call it __methname because it's not meant to be called
145
+ # by users. We also make it private below.
146
+ #
147
+ super( "__#{methname}".to_sym, funcname, args, :pointer )
148
+
149
+
150
+ # CAUTION: Metaclass hackery ahead! Handle with care!
151
+
152
+ metaklass = class << self; self; end
153
+ metaklass.instance_eval {
154
+
155
+ # Create the nice method, which calls __methname and wraps the
156
+ # return value (a pointer) the appropriate class using
157
+ # TypedPointer#wrap. This is the one that users should call,
158
+ # so we don't prepend the name with _'s.
159
+ #
160
+ define_method( methname ) do |*args|
161
+ retrn_type.wrap( send("__#{methname}".to_sym, *args) )
162
+ end
163
+
164
+ # __methname is private.
165
+ private "__#{methname}".to_sym
166
+
167
+ }
168
+
169
+ end
170
+
171
+ end
172
+ end