nice-ffi 0.1 → 0.2
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.
- 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
|