nice-ffi 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.txt +493 -0
- data/README.rdoc +23 -11
- data/docs/usage.rdoc +144 -37
- data/lib/nice-ffi.rb +10 -4
- data/lib/nice-ffi/autorelease.rb +112 -0
- data/lib/nice-ffi/{nicelibrary.rb → library.rb} +73 -45
- data/lib/nice-ffi/opaquestruct.rb +109 -0
- data/lib/nice-ffi/pathset.rb +404 -144
- data/lib/nice-ffi/{nicestruct.rb → struct.rb} +64 -19
- data/lib/nice-ffi/typedpointer.rb +20 -7
- metadata +11 -20
data/README.rdoc
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
2
|
= Nice-FFI
|
3
3
|
|
4
|
-
Version:: 0.
|
5
|
-
Date:: 2009-
|
4
|
+
Version:: 0.2
|
5
|
+
Date:: 2009-10-24
|
6
6
|
|
7
7
|
Homepage:: http://github.com/jacius/nice-ffi/
|
8
8
|
Author:: John Croisant <jacius@gmail.com>
|
@@ -12,22 +12,30 @@ Copyright:: 2009 John Croisant
|
|
12
12
|
== Description
|
13
13
|
|
14
14
|
Nice-FFI is a layer on top of Ruby-FFI [1] (and compatible FFI
|
15
|
-
systems)
|
16
|
-
FFI-based libraries.
|
15
|
+
systems) with features to ease development of FFI-based libraries.
|
17
16
|
|
18
17
|
Nice-FFI currently features:
|
19
18
|
|
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
19
|
* NiceFFI::Library: a stand-in for FFI::Library that provides methods
|
25
20
|
for easily finding and loading libraries on any platform, plus
|
26
21
|
automatic wrapping of functions that return struct pointers.
|
27
22
|
|
23
|
+
* NiceFFI::PathSet: a class with customizable rules for finding
|
24
|
+
library files on multiple operating system. PathSet is used by
|
25
|
+
NiceFFI::Library.load_library.
|
26
|
+
|
27
|
+
* NiceFFI::Struct: a stand-in for FFI::Struct that provides automatic
|
28
|
+
accessors for struct members, optional automatic memory management,
|
29
|
+
more instance initialization options, pretty to_s and inspect
|
30
|
+
methods, and other niceties.
|
31
|
+
|
32
|
+
* NiceFFI::OpaqueStruct: a base class for structs with no user-exposed
|
33
|
+
members. Useful when the struct definition is hidden by the
|
34
|
+
underlying C library.
|
35
|
+
|
28
36
|
Nice-FFI was originally developed as part of Ruby-SDL-FFI [2].
|
29
37
|
|
30
|
-
1. Ruby-FFI: http://
|
38
|
+
1. Ruby-FFI: http://github.com/ffi/ffi
|
31
39
|
2. Ruby-SDL-FFI: http://github.com/jacius/ruby-sdl-ffi/
|
32
40
|
|
33
41
|
|
@@ -46,8 +54,12 @@ match the new API, then you should wait until version 1.0.
|
|
46
54
|
|
47
55
|
== Requirements
|
48
56
|
|
49
|
-
* Ruby-FFI >= 0.
|
50
|
-
|
57
|
+
* Ruby-FFI >= 0.4.0 (or compatible FFI implementation)
|
58
|
+
|
59
|
+
|
60
|
+
== Usage
|
61
|
+
|
62
|
+
See docs/usage.rdoc for usage information.
|
51
63
|
|
52
64
|
|
53
65
|
== License
|
data/docs/usage.rdoc
CHANGED
@@ -30,14 +30,14 @@ of "extend FFI::Library", and use "load_library" instead of "ffi_lib":
|
|
30
30
|
|
31
31
|
|
32
32
|
require 'nice-ffi'
|
33
|
-
|
33
|
+
|
34
34
|
module MyLibraryModule
|
35
35
|
extend NiceFFI::Library
|
36
|
-
|
36
|
+
|
37
37
|
load_library("SDL") # look for libSDL.so, SDL.dll, etc.
|
38
|
-
|
38
|
+
|
39
39
|
# structs, functions, etc. as usual.
|
40
|
-
|
40
|
+
|
41
41
|
end
|
42
42
|
|
43
43
|
|
@@ -47,53 +47,72 @@ As mentioned, load_library uses NiceFFI::PathSet to search for the
|
|
47
47
|
library in likely directories. Specifically, it looks for:
|
48
48
|
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
"/
|
58
|
-
"/
|
59
|
-
|
60
|
-
|
61
|
-
"
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
50
|
+
paths = {
|
51
|
+
/linux|bsd/ => [ "/usr/local/lib/",
|
52
|
+
"/usr/lib/" ],
|
53
|
+
|
54
|
+
/darwin/ => [ "/usr/local/lib/",
|
55
|
+
"/sw/lib/",
|
56
|
+
"/opt/local/lib/",
|
57
|
+
"~/Library/Frameworks/",
|
58
|
+
"/Library/Frameworks/" ],
|
59
|
+
|
60
|
+
/windows/ => [ "C:\\windows\\system32\\",
|
61
|
+
"C:\\windows\\system\\" ]
|
62
|
+
}
|
63
|
+
|
64
|
+
files = {
|
65
|
+
/linux|bsd/ => [ "lib[NAME].so" ],
|
66
|
+
|
67
|
+
/darwin/ => [ "lib[NAME].dylib",
|
68
|
+
"[NAME].framework/[NAME]" ],
|
69
|
+
|
70
|
+
/windows/ => [ "[NAME].dll" ]
|
71
|
+
}
|
72
|
+
|
73
|
+
NiceFFI::PathSet::DEFAULT = NiceFFI::PathSet.new( paths, files )
|
67
74
|
|
68
75
|
|
69
|
-
The
|
76
|
+
The paths hash tells PathSet where to look for libraries, and the
|
77
|
+
files hash tells it the format of the library filename itself. The
|
78
|
+
string "[NAME]" is replaced with whatever string you pass to
|
70
79
|
load_library.
|
71
80
|
|
81
|
+
Each key in the hash should be a Regexp that matches an OS name from
|
82
|
+
FFI::Platform::OS. As of this writing (October 2009), the list of
|
83
|
+
recognized OS names is:
|
84
|
+
|
85
|
+
* "darwin" (MacOS X)
|
86
|
+
* "freebsd"
|
87
|
+
* "linux"
|
88
|
+
* "openbsd"
|
89
|
+
* "solaris"
|
90
|
+
* "windows"
|
91
|
+
|
72
92
|
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
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
93
|
+
first look for "/usr/local/lib/libSDL.so". If it can't find that, it
|
94
|
+
will then look for "/usr/lib/libSDL.so". It would also use those same
|
95
|
+
paths for FreeBSD or OpenBSD, because those OS names also match the
|
96
|
+
regexp /linux|bsd/.
|
97
|
+
|
98
|
+
If the library could not be found in any of the given directories with
|
99
|
+
the given file name formats, load_library will just try loading "SDL"
|
100
|
+
using ffi_lib (which does some platform-appropriate guesses too). If
|
101
|
+
that fails too, LoadError is raised.
|
79
102
|
|
80
103
|
If you want to load from a different path, you can make a custom
|
81
104
|
PathSet and pass it to load_library:
|
82
105
|
|
83
106
|
|
84
|
-
|
107
|
+
libs_dir = File.dirname(__FILE__) + "/libs/"
|
85
108
|
|
86
|
-
|
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
|
-
)
|
109
|
+
pathset = NiceFFI::PathSet::DEFAULT.prepend( libs_dir )
|
91
110
|
|
92
111
|
load_library( "SDL", my_pathset )
|
93
112
|
|
94
113
|
|
95
114
|
The above example prepends (adds in front) the new paths so
|
96
|
-
that load_library will look for the library in
|
115
|
+
that load_library will look for the library in "./libs/" first.
|
97
116
|
See PathSet for other useful methods for modifying PathSets.
|
98
117
|
|
99
118
|
|
@@ -135,8 +154,12 @@ With TypedPointer, the wrapping happens automatically. Just attach
|
|
135
154
|
the function with a TypedPointer instead of :pointer:
|
136
155
|
|
137
156
|
|
138
|
-
attach_function :
|
157
|
+
attach_function :make_my_struct, [:int, :int], NiceFFI::TypedPointer( MyStruct )
|
139
158
|
|
159
|
+
# If MyStruct is based on NiceFFI::Struct, you can do this instead:
|
160
|
+
|
161
|
+
attach_function :make_my_struct, [:int, :int], MyStruct.typed_pointer
|
162
|
+
|
140
163
|
|
141
164
|
Then you automatically get a MyStruct instance when you call the function:
|
142
165
|
|
@@ -148,7 +171,6 @@ Then you automatically get a MyStruct instance when you call the function:
|
|
148
171
|
Voila!
|
149
172
|
|
150
173
|
|
151
|
-
|
152
174
|
== NiceFFI::Struct
|
153
175
|
|
154
176
|
NiceFFI::Struct is a replacement for FFI::Struct. It provides several
|
@@ -252,3 +274,88 @@ struct pointer, but if you specify a TypedPointer instead of
|
|
252
274
|
# Seamlessly unwraps the struct and stores the pointer
|
253
275
|
struct.my = MyStruct.new([-4,-3])
|
254
276
|
|
277
|
+
|
278
|
+
=== Automatic Memory Managment
|
279
|
+
|
280
|
+
Ruby-FFI already provides automatic memory management when you create
|
281
|
+
a FFI::MemoryPointer or FFI::Buffer instance. When those instances are
|
282
|
+
garbage collected, their memory is automatically released so it can be
|
283
|
+
used elsewhere.
|
284
|
+
|
285
|
+
That feature is used by NiceFFI::Struct when you create a new instance
|
286
|
+
by passing a Hash, Array, String, or another instance. In those cases,
|
287
|
+
new memory is allocated for the struct instance, and automatically
|
288
|
+
released when the struct instance is galbage collected.
|
289
|
+
|
290
|
+
NiceFFI::Struct also provides an optional automatic memory management
|
291
|
+
system for normal pointers. To use this system, define a "release"
|
292
|
+
class method in your class. Then if you create a new struct instance
|
293
|
+
with an FFI::Pointer, the release class method will automatically be
|
294
|
+
called when the memory for a struct instance needs to be freed.
|
295
|
+
|
296
|
+
(This also applies to attached functions with TypedPointer return
|
297
|
+
values. The pointers returned from those functions are wrapped in the
|
298
|
+
struct class, so if you have defined the release class method, they
|
299
|
+
will be automatically memory managed.)
|
300
|
+
|
301
|
+
The release class method must accept an FFI::Pointer and call an
|
302
|
+
appropriate function to free the struct's memory. Here's an example
|
303
|
+
from Ruby-SDL-FFI:
|
304
|
+
|
305
|
+
|
306
|
+
class Surface < NiceFFI::Struct
|
307
|
+
|
308
|
+
def self.release( pointer )
|
309
|
+
SDL.FreeSurface( pointer )
|
310
|
+
end
|
311
|
+
|
312
|
+
# ...
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
Note: the release class method should not have any side effects
|
318
|
+
besides freeing the struct's memory. Don't be clever!
|
319
|
+
|
320
|
+
The memory management system keeps a reference count for each pointer
|
321
|
+
address, so the release class method will only be called when all
|
322
|
+
struct instances that are using that memory have been garbage
|
323
|
+
collected. That means it's safe to have many instances sharing the
|
324
|
+
same memory.
|
325
|
+
|
326
|
+
If you want to create an instance that doesn't use the memory
|
327
|
+
management system, you can disable the :autorelease option when
|
328
|
+
creating the instance, like so:
|
329
|
+
|
330
|
+
|
331
|
+
struct = MyStructClass.new( a_pointer, :autorelease => false )
|
332
|
+
|
333
|
+
|
334
|
+
== NiceFFI::OpaqueStruct
|
335
|
+
|
336
|
+
Some C libraries have structs with no publicly-visible layout.
|
337
|
+
Instead, the internal details are hidden, and only modified by calling
|
338
|
+
functions in the library.
|
339
|
+
|
340
|
+
For example, the SDL_mixer library has this definition in its header
|
341
|
+
file:
|
342
|
+
|
343
|
+
|
344
|
+
typedef struct _Mix_Music Mix_Music;
|
345
|
+
|
346
|
+
|
347
|
+
"_Mix_Music" is a struct that is defined within SDL_mixer, but its
|
348
|
+
internals are different depending on what features SDL_mixer was
|
349
|
+
compiled with. The struct members are not revealed in the header file,
|
350
|
+
so they can't be accessed like a normal struct.
|
351
|
+
|
352
|
+
NiceFFI provides a class for handling special cases like this,
|
353
|
+
NiceFFI::OpaqueStruct. OpaqueStruct has no layout and no members, and
|
354
|
+
cannot be created by passing in Hashes, Arrays, etc. It simply holds a
|
355
|
+
pointer to the struct memory. As with NiceStruct (and FFI::Struct),
|
356
|
+
instances of OpaqueStruct-based classes can be passed directly to
|
357
|
+
functions expecting a pointer of the appropriate struct type.
|
358
|
+
|
359
|
+
OpaqueStruct features the same optional memory management system as
|
360
|
+
NiceStruct. Read the "Automatic Memory Management" section above for
|
361
|
+
information about how to use this feature.
|
data/lib/nice-ffi.rb
CHANGED
@@ -28,20 +28,26 @@
|
|
28
28
|
#++
|
29
29
|
|
30
30
|
|
31
|
-
require '
|
31
|
+
require 'ffi'
|
32
32
|
|
33
33
|
|
34
34
|
module NiceFFI
|
35
35
|
end
|
36
36
|
|
37
37
|
|
38
|
+
this_dir = File.expand_path( File.dirname(__FILE__) )
|
39
|
+
|
38
40
|
%w{
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
+
typedpointer
|
43
|
+
pathset
|
44
|
+
library
|
45
|
+
autorelease
|
46
|
+
struct
|
47
|
+
opaquestruct
|
42
48
|
|
43
49
|
}.each do |f|
|
44
50
|
|
45
|
-
|
51
|
+
require File.join( this_dir, 'nice-ffi', f )
|
46
52
|
|
47
53
|
end
|
@@ -0,0 +1,112 @@
|
|
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
|
+
|
32
|
+
#--
|
33
|
+
# Don't scan this module for RDoc/RI.
|
34
|
+
|
35
|
+
|
36
|
+
require 'thread' # for Mutex. JRuby doesn't load this by default.
|
37
|
+
|
38
|
+
# A mixin module to provide automatic memory management for C structs.
|
39
|
+
#
|
40
|
+
module NiceFFI::AutoRelease
|
41
|
+
|
42
|
+
# Sets up the class when this module is included. Adds the class
|
43
|
+
# methods and defines class instance variables.
|
44
|
+
def self.included( klass )
|
45
|
+
class << klass
|
46
|
+
|
47
|
+
# Increment the reference count for this address.
|
48
|
+
def _incr_refcount( address )
|
49
|
+
@ptr_mutex ||= Mutex.new
|
50
|
+
@ptr_mutex.synchronize {
|
51
|
+
@ptr_refcounts ||= Hash.new(0)
|
52
|
+
@ptr_refcounts[address] += 1
|
53
|
+
}
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Decrement the counter for this pointer's address, and free
|
58
|
+
# the memory if the reference count falls below 1.
|
59
|
+
#
|
60
|
+
def _release( pointer )
|
61
|
+
@ptr_mutex ||= Mutex.new
|
62
|
+
@ptr_mutex.synchronize {
|
63
|
+
_decr_refcount(pointer.address)
|
64
|
+
if( @ptr_refcounts[pointer.address] < 1 )
|
65
|
+
release( pointer )
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Decrement the reference count for this address. If the count falls
|
73
|
+
# below 1, the address is removed from Hash altogether.
|
74
|
+
#
|
75
|
+
# Note: this method does not have a Mutex lock by itself, but you
|
76
|
+
# should use a lock in any methods that call it.
|
77
|
+
#
|
78
|
+
def _decr_refcount( address )
|
79
|
+
@ptr_refcounts ||= Hash.new(0)
|
80
|
+
@ptr_refcounts[address] -= 1
|
81
|
+
if( @ptr_refcounts[address] < 1 )
|
82
|
+
@ptr_refcounts.delete(address)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
|
93
|
+
def _make_autopointer( ptr, autorelease=true )
|
94
|
+
if( autorelease and
|
95
|
+
self.class.respond_to?(:release) and
|
96
|
+
ptr.is_a?(FFI::Pointer) and
|
97
|
+
not ptr.is_a?(FFI::MemoryPointer) and
|
98
|
+
not ptr.is_a?(FFI::Buffer) )
|
99
|
+
|
100
|
+
# Increment the reference count for this pointer address
|
101
|
+
self.class._incr_refcount( ptr.address )
|
102
|
+
|
103
|
+
# Wrap in an AutoPointer to call self.class._release when it's GC'd.
|
104
|
+
return FFI::AutoPointer.new( ptr, self.class.method(:_release) )
|
105
|
+
|
106
|
+
else
|
107
|
+
return ptr
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
end
|
@@ -28,49 +28,33 @@
|
|
28
28
|
#++
|
29
29
|
|
30
30
|
|
31
|
-
require 'ffi'
|
32
|
-
|
33
|
-
need{ 'typedpointer' }
|
34
|
-
need{ 'pathset' }
|
35
|
-
|
36
|
-
|
37
31
|
# A module to be used in place of FFI::Library. It acts mostly
|
38
32
|
# like FFI::Library, but with some nice extra features and
|
39
33
|
# conveniences to make life easier:
|
40
34
|
#
|
35
|
+
# * load_library method to help find and load libraries from common
|
36
|
+
# (or custom) places for the current OS. Use it instead of ffi_lib.
|
37
|
+
#
|
41
38
|
# * attach_function accepts TypedPointers as return type,
|
42
39
|
# in which case it wraps the return value of the bound function
|
43
40
|
# in the TypedPointer's type.
|
44
41
|
#
|
42
|
+
# * Shorthand aliases to improve code readability:
|
43
|
+
# * func = attach_function
|
44
|
+
# * var = attach_variable
|
45
|
+
#
|
45
46
|
module NiceFFI::Library
|
46
47
|
|
47
48
|
def self.extend_object( klass )
|
48
49
|
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
50
|
|
69
|
-
|
70
|
-
"C:\\windows\\system\\[NAME].dll",
|
71
|
-
"[NAME]" ]
|
51
|
+
super
|
72
52
|
|
73
|
-
|
53
|
+
class << klass
|
54
|
+
alias :func :attach_function
|
55
|
+
alias :var :attach_variable
|
56
|
+
end
|
57
|
+
end
|
74
58
|
|
75
59
|
|
76
60
|
# Try to find and load a library (e.g. "SDL_ttf") into an FFI
|
@@ -81,22 +65,14 @@ module NiceFFI::Library
|
|
81
65
|
#
|
82
66
|
# Raises LoadError if it could not find or load the library.
|
83
67
|
#
|
84
|
-
def load_library( names, search_paths=NiceFFI::
|
68
|
+
def load_library( names, search_paths=NiceFFI::PathSet::DEFAULT_PATHS )
|
85
69
|
|
86
70
|
names = [names] unless names.kind_of? Array
|
87
71
|
|
88
72
|
paths = search_paths.find( *names )
|
89
73
|
|
90
|
-
|
91
|
-
|
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
|
74
|
+
# Try just the plain library name(s), as last resort.
|
75
|
+
paths += names
|
100
76
|
|
101
77
|
# Try loading each path until one works.
|
102
78
|
loaded = paths.find { |path|
|
@@ -111,8 +87,14 @@ module NiceFFI::Library
|
|
111
87
|
end
|
112
88
|
}
|
113
89
|
|
114
|
-
# Oops, none of them worked.
|
115
90
|
if loaded.nil?
|
91
|
+
# Oops, none of them worked.
|
92
|
+
pretty_names = if names.size == 1
|
93
|
+
names[0]
|
94
|
+
else
|
95
|
+
names[0..-2].join(", ") + ", or " + names[-1]
|
96
|
+
end
|
97
|
+
|
116
98
|
raise( LoadError, "Could not load #{pretty_names}." )
|
117
99
|
else
|
118
100
|
# Return the one that did work
|
@@ -130,10 +112,10 @@ module NiceFFI::Library
|
|
130
112
|
# 2. methname, funcname, args, retrn_type
|
131
113
|
#
|
132
114
|
funcname, args, retrn_type = if arg1.kind_of?(Array)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
115
|
+
[methname, arg1, arg2]
|
116
|
+
else
|
117
|
+
[arg1, arg2, arg3]
|
118
|
+
end
|
137
119
|
|
138
120
|
unless retrn_type.kind_of? NiceFFI::TypedPointer
|
139
121
|
# Normal FFI::Library.attach_function behavior.
|
@@ -169,4 +151,50 @@ module NiceFFI::Library
|
|
169
151
|
end
|
170
152
|
|
171
153
|
end
|
154
|
+
|
155
|
+
|
156
|
+
# Calls the given block, but rescues and prints a warning if
|
157
|
+
# FFI::NotFoundError is raised. If warn_message is nil, the
|
158
|
+
# error message is printed instead.
|
159
|
+
#
|
160
|
+
# This is intended to be used around #attach_function for cases
|
161
|
+
# where the function may not exist (e.g. because the user has an
|
162
|
+
# old version of the library) and the library should continue loading
|
163
|
+
# anyway (with the function missing).
|
164
|
+
#
|
165
|
+
#
|
166
|
+
# Example:
|
167
|
+
#
|
168
|
+
# module Foo
|
169
|
+
# extend NiceFFI::Library
|
170
|
+
#
|
171
|
+
# load_library( "libfoo" )
|
172
|
+
#
|
173
|
+
# optional( "Warning: Your libfoo doesn't have opt_func()" ) do
|
174
|
+
# attach_function :opt_func, [], :int
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
def optional( warn_message=nil, &block )
|
179
|
+
raise LocalJumpError, "no block given" unless block_given?
|
180
|
+
begin
|
181
|
+
block.call()
|
182
|
+
rescue FFI::NotFoundError => e
|
183
|
+
if warn_message
|
184
|
+
puts warn_message
|
185
|
+
else
|
186
|
+
puts "Warning: #{e.message}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# Like #attach_function, but wrapped in #optional.
|
193
|
+
def optional_function( *args )
|
194
|
+
optional { attach_function( *args ) }
|
195
|
+
end
|
196
|
+
|
197
|
+
alias :optfunc :optional_function
|
198
|
+
|
199
|
+
|
172
200
|
end
|