corefoundation 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License
2
+ Copyright (c) 2012 Frederick Cheung
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+
2
+ CoreFoundation
3
+ ==============
4
+
5
+ FFI based wrappers for a subset of core foundation: various bits of CFString, CFData, CFArray, CFDictionary are available. Not that useful on its own but a useful building block for writing ffi wrappers of other OS X libraries.
6
+
7
+ Although the CF collection classes can store arbitrary pointer sized values this wrapper only supports storing CFTypes.
8
+
9
+ The CF namespace has the raw FFI generated method calls but it's usually easier to use the wrapper classes: `CF::String`, `CF::Date`, `CF::Array`, `CF::Dictionary`, `CF::Boolean` which try to present a rubyish view of the world (for example `CF::Array` implements `Enumerable`)
10
+
11
+ These implement methods for creating new instances from ruby objects (eg `CF::String.from_string("hello world")`) but you can also pass build them from an `FFI::Pointer`).
12
+
13
+ Converting
14
+ ===========
15
+
16
+ `CF::Base` objects has a `to_ruby` that creates a ruby object of the most approprite type (`String` for `CF::String`, `Time` for `CF::Date`, `Integer` or `Float` for `CF::Number` etc). The collection classes call `to_ruby` on their contents too.
17
+
18
+ In addition to the methods on the wrapper classes themselves, the ruby classes are extended with a `to_cf` method. Because CoreFoundation strings aren't arbitrary collections of bytes, `String#to_cf` will return a `CF::Data` if the string has the ASCII-8BIT encoding and a `CF::String` if not.
19
+
20
+ If you have an `FFI::Pointer` or a raw address then you can create a wrapper by passing it to `new`, for example `CF::String.new(some_pointer)`. This does *not* check that the pointer is actually a `CFString`. You can use `CF::Base.typecast` to construct an instance of the appropriate subclass, for example `CF::Base.typecast(some_pointer)` would return a `CF::String` if `some_pointer` was in fact a `CFStringRef`.
21
+
22
+ Memory Management
23
+ =================
24
+
25
+ The convenience methods for creating CF objects will release the cf object when they are garbage collected. Methods on the convenience classes will usually retain the result and mark it for releasing when they are garbage collected (for example `CF::Dictionary#[]` retains the returned value). You don't need to do any extra memory management on these.
26
+
27
+ If you pass an `FFI::Pointer` to `new` or `typecast` no assumptions are made for you. You should call `retain` or `release` to manage it manually. As an alternative to calling `release` manually, the `release_on_gc` method adds a finalizer to the wrapper that will call `CFRelease` on the Core Foundation object when the wrapper is garbage collected. You will almost certainly crash your ruby interpreter if you overrelease an object and you will leak memory if you overretain one.
28
+
29
+ If you use the raw api (eg `CF.CFArrayCreate`) then you're on your own.
30
+
31
+
32
+ Compatibility
33
+ =============
34
+ Requires ruby 1.9 due to use of encoding related methods. Should work in MRI and jruby. Not compatible with rubinius due to rubinius' ffi implemenation not supporting certain features.
35
+
36
+ License
37
+ =======
38
+
39
+ Released under the MIT license. See LICENSE
@@ -0,0 +1,11 @@
1
+ require 'ffi'
2
+ require 'corefoundation/base'
3
+ require 'corefoundation/null'
4
+ require 'corefoundation/string'
5
+ require 'corefoundation/array'
6
+ require 'corefoundation/boolean'
7
+ require 'corefoundation/data'
8
+ require 'corefoundation/dictionary'
9
+ require 'corefoundation/number'
10
+ require 'corefoundation/date'
11
+ require 'corefoundation/extensions'
@@ -0,0 +1,123 @@
1
+ module CF
2
+ typedef :pointer, :cfarrayref
3
+
4
+ # @private
5
+ class ArrayCallbacks < FFI::Struct
6
+ layout :version, :cfindex, #cfindex
7
+ :retain, :pointer,
8
+ :release, :pointer,
9
+ :copyDescription, :pointer,
10
+ :equal, :pointer
11
+ end
12
+
13
+ attach_variable :kCFTypeArrayCallBacks, ArrayCallbacks
14
+ attach_function :CFArrayCreate, [:pointer, :pointer, :cfindex, :pointer], :cfarrayref
15
+ attach_function :CFArrayCreateMutable, [:pointer, :cfindex, :pointer], :cfarrayref
16
+ attach_function :CFArrayGetValueAtIndex, [:pointer, :cfindex], :pointer
17
+ attach_function :CFArraySetValueAtIndex, [:pointer, :cfindex, :pointer], :void
18
+ attach_function :CFArrayAppendValue, [:pointer, :pointer], :void
19
+ attach_function :CFArrayGetCount, [:pointer], :cfindex
20
+
21
+ callback :each_applier, [:pointer, :pointer], :void
22
+
23
+ attach_function :CFArrayApplyFunction, [:cfarrayref, CF::Range.by_value, :each_applier, :pointer], :void
24
+
25
+
26
+ # Wrapper class for CFArrayRef. It implements enumberable so you can use a lot of your favourite ruby methods on it.
27
+ #
28
+ # Values returned by the accessor methods or yielded by the block are retained and marked as releasable on garbage collection
29
+ # This means you can safely use the returned values even if the CFArray itself has been destroyed
30
+ #
31
+ # Unlike ruby arrays you cannot set arbitary array indexes - You can only set indexes in the range 0..length
32
+ class Array < Base
33
+ include Enumerable
34
+ register_type("CFArray")
35
+
36
+ # Whether the array is mutable
37
+ #
38
+ # WARNING: this only works for arrays created by CF::Array, there is no public api for telling whether an arbitrary
39
+ # CFTypeRef is a mutable array or not
40
+ def mutable?
41
+ @mutable
42
+ end
43
+
44
+ # Iterates over the array yielding the value to the block
45
+ # The value is wrapped in the appropriate CF::Base subclass and retained (but marked for releasing upon garbage collection)
46
+ # @return self
47
+ def each
48
+ range = CF::Range.new
49
+ range[:location] = 0
50
+ range[:length] = length
51
+ callback = lambda do |value, _|
52
+ yield Base.typecast(value).retain.release_on_gc
53
+ end
54
+ CF.CFArrayApplyFunction(self, range, callback, nil)
55
+ self
56
+ end
57
+
58
+ # Creates a new, immutable CFArray from a ruby array of cf objects
59
+ # @param [Array<CF::Base>] array The objects to place in the array. They must inherit from CF::Base
60
+ # @return [CF::Array] A CF::Array containing the objects, setup to release the array upon garbage collection
61
+ def self.immutable(array)
62
+ if bad_element = array.detect {|value| !value.is_a?(CF::Base)}
63
+ raise TypeError, "Array contains non cftype #{bad_element.inspect}"
64
+ end
65
+ m = FFI::MemoryPointer.new(:pointer, array.length)
66
+ m.write_array_of_pointer(array)
67
+ new(CF.CFArrayCreate(nil,m,array.length,CF::kCFTypeArrayCallBacks.to_ptr)).release_on_gc
68
+ end
69
+
70
+ # Creates a new, empty mutable CFArray
71
+ # @return [CF::Array] A mutable CF::Array containing the objects, setup to release the array upon garbage collection
72
+ def self.mutable
73
+ result = new(CF.CFArrayCreateMutable nil, 0, CF::kCFTypeArrayCallBacks.to_ptr).release_on_gc
74
+ result.instance_variable_set(:@mutable, true)
75
+ result
76
+ end
77
+
78
+ # Returns the object at the index
79
+ # @param [Integer] index the 0 based index of the item to retrieve. Behaviour is undefined if it is not in the range 0...size
80
+ # @return [CF::Base] a subclass of CF::Base
81
+ def [](index)
82
+ Base.typecast(CF.CFArrayGetValueAtIndex(self, index)).retain.release_on_gc
83
+ end
84
+
85
+ # Sets object at the index
86
+ # @param [Integer] index the 0 based index of the item to retrieve. Behaviour is undefined if it is not in the range 0..size
87
+ # It is legal to set the value at index n of a n item array - this is equivalent to appending the object
88
+ #
89
+ # @param [CF::Base] value the value to store
90
+ # @return [CF::Base] the store value
91
+ def []=(index, value)
92
+ raise TypeError, "instance is not mutable" unless mutable?
93
+ self.class.check_cftype(value)
94
+ CF.CFArraySetValueAtIndex(self, index, value)
95
+ value
96
+ end
97
+
98
+ # Appends a value to the array
99
+ #
100
+ # @return [CF::Array] self
101
+ def <<(value)
102
+ raise TypeError, "instance is not mutable" unless mutable?
103
+ self.class.check_cftype(value)
104
+ CF.CFArrayAppendValue(self, value)
105
+ self
106
+ end
107
+
108
+ # Returns a ruby array containing the result of calling to_ruby on each of the array's elements
109
+ #
110
+ def to_ruby
111
+ collect(&:to_ruby)
112
+ end
113
+
114
+ alias_method :push, :<<
115
+
116
+ # Returns the number of elements the array contains
117
+ # @return [Integer]
118
+ def length
119
+ CF.CFArrayGetCount(self)
120
+ end
121
+ alias_method :size, :length
122
+ end
123
+ end
@@ -0,0 +1,197 @@
1
+
2
+ # The top level namespace for the corefoundation library. The raw FFI generated methods are attached here
3
+ #
4
+ #
5
+ module CF
6
+ extend FFI::Library
7
+ ffi_lib '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation'
8
+
9
+ if FFI::Platform::ARCH == 'x86_64'
10
+ typedef :long_long, :cfindex
11
+ typedef :long_long, :cfcomparisonresult
12
+ typedef :ulong_long, :cfoptionflags
13
+ typedef :ulong_long, :cftypeid
14
+ typedef :ulong_long, :cfhashcode
15
+ else
16
+ typedef :long, :cfindex
17
+ typedef :long, :cfcomparisonresult
18
+ typedef :ulong, :cfoptionflags
19
+ typedef :ulong, :cftypeid
20
+ typedef :ulong, :cfhashcode
21
+ end
22
+
23
+
24
+ # @private
25
+ class Range < FFI::Struct
26
+ layout :location, :cfindex,
27
+ :length, :cfindex
28
+ end
29
+
30
+ typedef :pointer, :cftyperef
31
+
32
+ #general utility functions
33
+ attach_function :show, 'CFShow', [:cftyperef], :void
34
+ attach_function :release, 'CFRelease', [:cftyperef], :void
35
+ attach_function :retain, 'CFRetain', [:cftyperef], :cftyperef
36
+ attach_function 'CFEqual', [:cftyperef, :cftyperef], :char
37
+ attach_function 'CFHash', [:cftyperef], :cfhashcode
38
+ attach_function 'CFCopyDescription', [:cftyperef], :cftyperef
39
+ attach_function 'CFGetTypeID', [:cftyperef], :cftypeid
40
+
41
+ # The base class for all of the wrapper classes
42
+ #
43
+ # @abstract
44
+ class Base
45
+ @@type_map = {}
46
+
47
+ # @private
48
+ class Releaser
49
+ def initialize(ptr)
50
+ @address = ptr.address
51
+ end
52
+
53
+ def call *ignored
54
+ if @address != 0
55
+ CF.release(@address)
56
+ @address = 0
57
+ end
58
+ end
59
+ end
60
+
61
+ class << self
62
+ # Raises an exception if the argument does not inherit from CF::Base
63
+ #
64
+ # @param cftyperef object to check
65
+ def check_cftype(cftyperef)
66
+ raise TypeError, "#{cftyperef.inspect} is not a cftype" unless cftyperef.is_a?(CF::Base)
67
+ end
68
+
69
+ # Wraps an FFI::Pointer with the appropriate subclass of CF::Base
70
+ # If the pointer is not a CFTypeRef behaviour is undefined
71
+ # @param [FFI::Pointer] cftyperef Object to wrap
72
+ # @return A wrapper object inheriting from CF::Base
73
+ def typecast(cftyperef)
74
+ klass = klass_from_cf_type cftyperef
75
+ klass.new(cftyperef)
76
+ end
77
+
78
+ private
79
+ def klass_from_cf_type cftyperef
80
+ klass = @@type_map[CF.CFGetTypeID(cftyperef)]
81
+ if !klass
82
+ raise TypeError, "No class registered for cf type #{cftyperef.inspect}"
83
+ end
84
+ klass
85
+ end
86
+
87
+ def register_type(type_name)
88
+ CF.attach_function "#{type_name}GetTypeID", [], :cftypeid
89
+ @@type_map[CF.send("#{type_name}GetTypeID")] = self
90
+ end
91
+
92
+ end
93
+
94
+ #
95
+ #
96
+ # @param [FFI::Pointer] ptr The pointer to wrap
97
+ def initialize(ptr)
98
+ @ptr = FFI::Pointer.new(ptr)
99
+ end
100
+
101
+ # Whether the instance is the CFNull singleton
102
+ #
103
+ # @return [Boolean]
104
+ def null?
105
+ equals?(CF::NULL)
106
+ end
107
+
108
+ # Returns the wrapped pointer
109
+ # @return [FFI::Pointer]
110
+ def to_ptr
111
+ @ptr
112
+ end
113
+
114
+ # Sets the wrapper pointer. You should only do this rarely. If you have used release_on_gc then that
115
+ # finalizer will still be called on the original pointer value
116
+ #
117
+ def ptr= ptr
118
+ @ptr = ptr
119
+ end
120
+
121
+ # Calls CFRetain on the wrapped pointer
122
+ # @return Returns self
123
+ def retain
124
+ CF.retain(self)
125
+ self
126
+ end
127
+
128
+ # Calls CFRelease on the wrapped pointer
129
+ # @return Returns self
130
+ def release
131
+ CF.release(self)
132
+ self
133
+ end
134
+
135
+ # Installs a finalizer on the wrapper object that will cause it to call CFRelease on the pointer
136
+ # when the wrapper is garbage collected
137
+ # @return Returns self
138
+ def release_on_gc
139
+ ObjectSpace.define_finalizer(@ptr, Releaser.new(@ptr))
140
+ self
141
+ end
142
+
143
+ # Returns a ruby string containing the output of CFCopyDescription for the wrapped object
144
+ #
145
+ # @return [String]
146
+ def inspect
147
+ cf = CF::String.new(CF.CFCopyDescription(self))
148
+ cf.to_s.tap {cf.release}
149
+ end
150
+
151
+ # Uses CFHash to return a hash code
152
+ #
153
+ # @return [Integer]
154
+ def hash
155
+ CF.CFHash(self)
156
+ end
157
+
158
+ # eql? (and ==) are implemented using CFEqual
159
+ #
160
+ #
161
+ def eql?(other)
162
+ if other.is_a?(CF::Base)
163
+ CF.CFEqual(self, other) != 0
164
+ else
165
+ false
166
+ end
167
+ end
168
+
169
+ # equals? is defined as returning true if the wrapped pointer is the same
170
+ #
171
+ #
172
+ def equals?(other)
173
+ if other.is_a?(CF::Base)
174
+ @ptr.address == other.to_ptr.address
175
+ else
176
+ false
177
+ end
178
+ end
179
+
180
+ alias_method :==, :eql?
181
+
182
+ # Converts the CF object into a ruby object. Subclasses typically override this to return
183
+ # an appropriate object (for example CF::String returns a String)
184
+ #
185
+ def to_ruby
186
+ self
187
+ end
188
+
189
+ # to_cf is a no-op on CF::Base and its descendants - it always returns self
190
+ #
191
+ #
192
+ def to_cf
193
+ self
194
+ end
195
+ end
196
+
197
+ end
@@ -0,0 +1,25 @@
1
+ module CF
2
+ attach_variable 'kCFBooleanTrue', :pointer
3
+ attach_variable 'kCFBooleanFalse', :pointer
4
+ attach_function 'CFBooleanGetValue', [:pointer], :uchar
5
+
6
+ # Wrapper for CFBooleanRef.
7
+ # Typically you use the CF::Boolean::TRUE and CF::Boolean::FALSE constants
8
+ #
9
+ class Boolean < Base
10
+ register_type("CFBoolean")
11
+ # A constant containing kCFBooleanTrue
12
+ TRUE = new(CF.kCFBooleanTrue)
13
+ # A constant containing kCFBooleanFalse
14
+ FALSE = new(CF.kCFBooleanFalse)
15
+
16
+ # returns a ruby true/false value
17
+ #
18
+ # @return [Boolean]
19
+ def value
20
+ CF.CFBooleanGetValue(self) != 0
21
+ end
22
+
23
+ alias_method :to_ruby, :value
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module CF
2
+ typedef :pointer, :cfdataref
3
+
4
+ attach_function 'CFDataCreate', [:pointer, :buffer_in, :cfindex], :cfdataref
5
+ attach_function 'CFDataGetLength', [:cfdataref], :cfindex
6
+ attach_function 'CFDataGetBytePtr', [:cfdataref], :pointer
7
+
8
+ # Wrapper for CFData
9
+ #
10
+ #
11
+ class Data < Base
12
+ register_type("CFData")
13
+
14
+ # Creates a CFData from a ruby string
15
+ # @param [String] s the string to use
16
+ # @return [CF::Data]
17
+ def self.from_string(s)
18
+ new(CF.CFDataCreate(nil, s, s.bytesize)).release_on_gc
19
+ end
20
+
21
+ # Creates a ruby string from the wrapped data. The encoding will always be ASCII_8BIT
22
+ #
23
+ # @return [String]
24
+ def to_s
25
+ ptr = CF.CFDataGetBytePtr(self)
26
+ ptr.read_string(CF.CFDataGetLength(self)).force_encoding(Encoding::ASCII_8BIT)
27
+ end
28
+
29
+ # The size in bytes of the CFData
30
+ # @return [Integer]
31
+ def size
32
+ CF.CFDataGetLength(self)
33
+ end
34
+
35
+ alias_method :to_ruby, :to_s
36
+ end
37
+ end
38
+