win32ole-ffi 0.1.0
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.
- checksums.yaml +7 -0
- data/BSDL +22 -0
- data/CHANGELOG.md +10 -0
- data/COPYING +56 -0
- data/README.md +49 -0
- data/lib/win32ole/ffi/array.rb +312 -0
- data/lib/win32ole/ffi/dispatch.rb +125 -0
- data/lib/win32ole/ffi/event.rb +800 -0
- data/lib/win32ole/ffi/fiddle_compat.rb +49 -0
- data/lib/win32ole/ffi/method.rb +237 -0
- data/lib/win32ole/ffi/param.rb +111 -0
- data/lib/win32ole/ffi/record.rb +289 -0
- data/lib/win32ole/ffi/type.rb +337 -0
- data/lib/win32ole/ffi/typeinfo.rb +488 -0
- data/lib/win32ole/ffi/typelib.rb +384 -0
- data/lib/win32ole/ffi/variable.rb +117 -0
- data/lib/win32ole/ffi/variant.rb +351 -0
- data/lib/win32ole/ffi/version.rb +5 -0
- data/lib/win32ole/ffi/win32.rb +907 -0
- data/lib/win32ole/ffi/win32ole.rb +827 -0
- data/lib/win32ole/ffi.rb +48 -0
- data/sig/win32ole/ffi.rbs +308 -0
- metadata +84 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fiddle'
|
|
4
|
+
require 'win32ole/ffi/win32'
|
|
5
|
+
require 'win32ole/ffi/array'
|
|
6
|
+
|
|
7
|
+
class WIN32OLE
|
|
8
|
+
module VariantType
|
|
9
|
+
VT_EMPTY = 0
|
|
10
|
+
VT_NULL = 1
|
|
11
|
+
VT_I2 = 2
|
|
12
|
+
VT_I4 = 3
|
|
13
|
+
VT_R4 = 4
|
|
14
|
+
VT_R8 = 5
|
|
15
|
+
VT_CY = 6
|
|
16
|
+
VT_DATE = 7
|
|
17
|
+
VT_BSTR = 8
|
|
18
|
+
VT_DISPATCH = 9
|
|
19
|
+
VT_ERROR = 10
|
|
20
|
+
VT_BOOL = 11
|
|
21
|
+
VT_VARIANT = 12
|
|
22
|
+
VT_UNKNOWN = 13
|
|
23
|
+
VT_DECIMAL = 14
|
|
24
|
+
VT_I1 = 16
|
|
25
|
+
VT_UI1 = 17
|
|
26
|
+
VT_UI2 = 18
|
|
27
|
+
VT_UI4 = 19
|
|
28
|
+
VT_I8 = 20
|
|
29
|
+
VT_UI8 = 21
|
|
30
|
+
VT_INT = 22
|
|
31
|
+
VT_UINT = 23
|
|
32
|
+
VT_VOID = 24
|
|
33
|
+
VT_HRESULT = 25
|
|
34
|
+
VT_PTR = 26
|
|
35
|
+
VT_SAFEARRAY = 27
|
|
36
|
+
VT_CARRAY = 28
|
|
37
|
+
VT_USERDEFINED = 29
|
|
38
|
+
VT_LPSTR = 30
|
|
39
|
+
VT_LPWSTR = 31
|
|
40
|
+
VT_RECORD = 36
|
|
41
|
+
VT_INT_PTR = 37
|
|
42
|
+
VT_UINT_PTR = 38
|
|
43
|
+
VT_FILETIME = 64
|
|
44
|
+
VT_BLOB = 65
|
|
45
|
+
VT_STREAM = 66
|
|
46
|
+
VT_STORAGE = 67
|
|
47
|
+
VT_STREAMED_OBJECT = 68
|
|
48
|
+
VT_STORED_OBJECT = 69
|
|
49
|
+
VT_BLOB_OBJECT = 70
|
|
50
|
+
VT_CF = 71
|
|
51
|
+
VT_CLSID = 72
|
|
52
|
+
VT_VERSIONED_STREAM = 73
|
|
53
|
+
VT_BSTR_BLOB = 0xFFF
|
|
54
|
+
VT_VECTOR = 0x1000
|
|
55
|
+
VT_ARRAY = 0x2000
|
|
56
|
+
VT_BYREF = 0x4000
|
|
57
|
+
VT_RESERVED = 0x8000
|
|
58
|
+
VT_ILLEGAL = 0xFFFF
|
|
59
|
+
VT_ILLEGALMASKED = 0xFFF
|
|
60
|
+
VT_TYPEMASK = 0xFFF
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
VARIANT = VariantType
|
|
64
|
+
|
|
65
|
+
class Variant
|
|
66
|
+
W = Win32
|
|
67
|
+
SA = SafeArray
|
|
68
|
+
VT = VariantType
|
|
69
|
+
private_constant :W, :SA, :VT
|
|
70
|
+
|
|
71
|
+
def initialize(val, vartype = nil, _reserved = nil)
|
|
72
|
+
if vartype.nil?
|
|
73
|
+
to_free = []
|
|
74
|
+
@var = own(WIN32OLE.ruby_value_to_variant_bytes(val, to_free), to_free)
|
|
75
|
+
track_realvar(@var)
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
raise ArgumentError, 'WIN32OLE::Variant does not support VT_RECORD; use WIN32OLE::Record instead' if (vartype & VT::VT_TYPEMASK) == VT::VT_RECORD
|
|
80
|
+
|
|
81
|
+
# ole_val_ary2variant_ary: a plain VT_ARRAY is an array of VARIANTs.
|
|
82
|
+
vartype |= VT::VT_VARIANT if (vartype & ~VT::VT_BYREF) == VT::VT_ARRAY
|
|
83
|
+
base_vt = vartype & VT::VT_TYPEMASK
|
|
84
|
+
byref = (vartype & VT::VT_BYREF) != 0
|
|
85
|
+
|
|
86
|
+
realvar_bytes =
|
|
87
|
+
if (vartype & VT::VT_ARRAY) != 0
|
|
88
|
+
# nil: the type with no SAFEARRAY behind it, as C leaves it
|
|
89
|
+
psa = val.nil? ? 0 : with_temporaries { |to_free| SA.ruby_array_to_safearray(val, base_vt, to_free).to_i }
|
|
90
|
+
W.pack_variant(vartype & ~VT::VT_BYREF, W.pack_pointer(psa))
|
|
91
|
+
else
|
|
92
|
+
scalar_realvar(val, vartype)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@realvar = byref ? persist_realvar(realvar_bytes) : realvar_bytes
|
|
96
|
+
@var = byref ? W.pack_byref(vartype & ~VT::VT_BYREF, @realvar) : @realvar
|
|
97
|
+
byref ? commit_realvar(@realvar) : track_realvar(@realvar)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def value
|
|
101
|
+
vt, = W.unpack_variant(@var)
|
|
102
|
+
base_vt = vt & VT::VT_TYPEMASK
|
|
103
|
+
if (vt & VT::VT_ARRAY) != 0 && base_vt == VT::VT_UI1
|
|
104
|
+
psa = current_array_ptr
|
|
105
|
+
return SA.ui1_safearray_to_bytes(psa) if !psa.zero? && SA.safe_array_get_dim.call(psa) == 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The Variant keeps owning what @var holds (folevariant_value copies).
|
|
109
|
+
WIN32OLE.variant_bytes_to_ruby_value(@var, owned: false)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def value=(val)
|
|
113
|
+
vt, = W.unpack_variant(@var)
|
|
114
|
+
base_vt = vt & VT::VT_TYPEMASK
|
|
115
|
+
byref = (vt & VT::VT_BYREF) != 0
|
|
116
|
+
realvar_bytes =
|
|
117
|
+
if (vt & VT::VT_ARRAY) != 0
|
|
118
|
+
unless val.is_a?(::String) && base_vt == VT::VT_UI1
|
|
119
|
+
raise WIN32OLE::RuntimeError, 'array value can only be replaced with a String for a VT_UI1|VT_ARRAY Variant'
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
psa = SA.ruby_array_to_safearray(val, base_vt)
|
|
123
|
+
W.pack_variant(vt & ~VT::VT_BYREF, W.pack_pointer(psa.to_i))
|
|
124
|
+
else
|
|
125
|
+
scalar_realvar(val, vt)
|
|
126
|
+
end
|
|
127
|
+
@realvar = byref ? persist_realvar(realvar_bytes) : realvar_bytes
|
|
128
|
+
@var = byref ? W.pack_byref(vt & ~VT::VT_BYREF, @realvar) : @realvar
|
|
129
|
+
byref ? commit_realvar(@realvar) : track_realvar(@realvar)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def vartype
|
|
133
|
+
vt, = W.unpack_variant(@var)
|
|
134
|
+
vt
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.array(dims, vt)
|
|
138
|
+
raise TypeError, "wrong argument type #{dims.class} (expected Array)" unless dims.is_a?(::Array)
|
|
139
|
+
|
|
140
|
+
bounds = dims.flat_map { |n| [n, 0] }.pack('L2' * dims.size)
|
|
141
|
+
psa = SA.safe_array_create.call(vt & VT::VT_TYPEMASK, dims.size, bounds)
|
|
142
|
+
raise ::RuntimeError, 'memory allocation error' if psa.nil? || psa.to_i.zero?
|
|
143
|
+
|
|
144
|
+
allocate.tap { |v| v.send(:set_array_var, psa, vt) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def [](*indices)
|
|
148
|
+
base_vt, psa = array_state
|
|
149
|
+
dim = SA.safe_array_get_dim.call(psa)
|
|
150
|
+
raise ArgumentError, 'unmatch number of indices' unless indices.size == dim
|
|
151
|
+
|
|
152
|
+
hr = SA.safe_array_lock.call(psa)
|
|
153
|
+
raise WIN32OLE::RuntimeError, "failed to SafeArrayLock: #{W.hr_hex(hr)}" if W.failed?(hr)
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
index_buf = indices.pack('l' * indices.size)
|
|
157
|
+
elem_ptr_out = ("\x00" * W::PTR_SIZE).b
|
|
158
|
+
hr = SA.safe_array_ptr_of_index.call(psa, index_buf, elem_ptr_out)
|
|
159
|
+
raise WIN32OLE::RuntimeError, "failed to SafeArrayPtrOfIndex: #{W.hr_hex(hr)}" if W.failed?(hr)
|
|
160
|
+
|
|
161
|
+
SA.read_element(elem_ptr_out.unpack1(W::PACK_PTR), base_vt)
|
|
162
|
+
ensure
|
|
163
|
+
SA.safe_array_unlock.call(psa)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def []=(*args)
|
|
168
|
+
val = args.pop
|
|
169
|
+
indices = args
|
|
170
|
+
base_vt, psa = array_state
|
|
171
|
+
dim = SA.safe_array_get_dim.call(psa)
|
|
172
|
+
raise ArgumentError, 'unmatch number of indices' unless indices.size == dim
|
|
173
|
+
|
|
174
|
+
hr = SA.safe_array_lock.call(psa)
|
|
175
|
+
raise WIN32OLE::RuntimeError, "failed to SafeArrayLock: #{W.hr_hex(hr)}" if W.failed?(hr)
|
|
176
|
+
|
|
177
|
+
begin
|
|
178
|
+
with_temporaries { |to_free| SA.write_element(psa, indices.pack('l' * indices.size), base_vt, val, to_free) }
|
|
179
|
+
val
|
|
180
|
+
ensure
|
|
181
|
+
SA.safe_array_unlock.call(psa)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def current_array_ptr
|
|
188
|
+
vt, payload = W.unpack_variant(@var)
|
|
189
|
+
addr = W.unpack_pointer(payload)
|
|
190
|
+
return addr if (vt & VT::VT_BYREF).zero?
|
|
191
|
+
|
|
192
|
+
Fiddle::Pointer.new(addr)[0, W::PTR_SIZE].unpack1(W::PACK_PTR)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# ext/win32ole/win32ole_variant.c's ole_val2olevariantdata, non-array
|
|
196
|
+
# branch: nil is the zero of the requested type; VT_ERROR takes the
|
|
197
|
+
# SCODE as is; anything else is converted to its natural VARIANT (I8/UI8
|
|
198
|
+
# packed directly, as ole_val2variant_ex does) and, when that type
|
|
199
|
+
# differs, coerced with VariantChangeTypeEx. VT_VARIANT keeps the
|
|
200
|
+
# natural VARIANT.
|
|
201
|
+
def scalar_realvar(val, vt)
|
|
202
|
+
base_vt = vt & ~VT::VT_BYREF
|
|
203
|
+
return error_realvar(val) if base_vt == VT::VT_ERROR
|
|
204
|
+
if val.nil?
|
|
205
|
+
return W.pack_variant(VT::VT_EMPTY, W.pack_empty) if vt == (VT::VT_VARIANT | VT::VT_BYREF)
|
|
206
|
+
|
|
207
|
+
return W.pack_variant(base_vt, W.pack_empty)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
to_free = []
|
|
211
|
+
natural =
|
|
212
|
+
case base_vt
|
|
213
|
+
when VT::VT_I8 then W.pack_variant(base_vt, W.pack_i8(val))
|
|
214
|
+
when VT::VT_UI8 then W.pack_variant(base_vt, W.pack_ui8(val))
|
|
215
|
+
else WIN32OLE.ruby_value_to_variant_bytes(val, to_free)
|
|
216
|
+
end
|
|
217
|
+
natural_vt, = W.unpack_variant(natural)
|
|
218
|
+
return own(natural, to_free) if base_vt == VT::VT_VARIANT || natural_vt == base_vt
|
|
219
|
+
|
|
220
|
+
# VariantChangeTypeEx makes a VARIANT of its own.
|
|
221
|
+
with_temporaries(to_free) { W.change_variant_type(natural, base_vt) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Runs the block with a list for the temporary allocations of the
|
|
225
|
+
# VARIANTs it builds, and frees them once it's done.
|
|
226
|
+
def with_temporaries(to_free = [])
|
|
227
|
+
yield to_free
|
|
228
|
+
ensure
|
|
229
|
+
to_free.each(&:call)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# +var+ as a VARIANT owning what it holds: a VariantCopy, when the
|
|
233
|
+
# temporaries in +to_free+ (freed here) own it.
|
|
234
|
+
def own(var, to_free)
|
|
235
|
+
return var if to_free.empty?
|
|
236
|
+
|
|
237
|
+
with_temporaries(to_free) { W.variant_copy(var) }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# ole_val2variant_err: an Integer SCODE (or nil for 0), possibly held
|
|
241
|
+
# in another Variant.
|
|
242
|
+
def error_realvar(val)
|
|
243
|
+
scode = val.is_a?(Variant) ? val.value : val
|
|
244
|
+
unless scode.nil? || scode.is_a?(::Integer)
|
|
245
|
+
raise WIN32OLE::RuntimeError, "failed to convert VT_ERROR VARIANT:`#{scode.inspect}'"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
W.pack_variant(VT::VT_ERROR, W.pack_error(scode || 0))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def set_array_var(psa, vt)
|
|
252
|
+
vt |= VT::VT_ARRAY
|
|
253
|
+
realvar_bytes = W.pack_variant(vt & ~VT::VT_BYREF, W.pack_pointer(psa.to_i))
|
|
254
|
+
byref = (vt & VT::VT_BYREF) != 0
|
|
255
|
+
@realvar = byref ? persist_realvar(realvar_bytes) : realvar_bytes
|
|
256
|
+
@var = byref ? W.pack_byref(vt & ~VT::VT_BYREF, @realvar) : @realvar
|
|
257
|
+
byref ? commit_realvar(@realvar) : track_realvar(@realvar)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Allocates a persistent buffer for bytes and returns it -- does NOT
|
|
261
|
+
# touch @realvar_finalizer_state yet. Call commit_realvar with the
|
|
262
|
+
# result once @var/@realvar have been rebuilt to reference it, so the
|
|
263
|
+
# old buffer (if any) is only freed after nothing references it
|
|
264
|
+
# anymore -- freeing it first (the naive order) leaves a window where
|
|
265
|
+
# @var's own embedded bytes point at already-freed memory, and if
|
|
266
|
+
# allocation itself raises, freeing the old buffer up front would
|
|
267
|
+
# leave the finalizer holding an already-freed pointer (double-free
|
|
268
|
+
# at GC time).
|
|
269
|
+
def persist_realvar(bytes)
|
|
270
|
+
realvar_state
|
|
271
|
+
W.persistent_pointer_for(bytes)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def realvar_state
|
|
275
|
+
@realvar_finalizer_state ||= {}.tap do |state|
|
|
276
|
+
ObjectSpace.define_finalizer(self, self.class.realvar_finalizer(state))
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# The non-VT_BYREF counterpart of commit_realvar: +bytes+ is now the
|
|
281
|
+
# realvar, and what the one before held is cleared.
|
|
282
|
+
def track_realvar(bytes)
|
|
283
|
+
old = realvar_state[:bytes]
|
|
284
|
+
realvar_state[:bytes] = bytes
|
|
285
|
+
self.class.clear_realvar(old) if old
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Stores ptr as the current realvar buffer and frees whatever was
|
|
289
|
+
# there before -- call this only after @var/@realvar already
|
|
290
|
+
# reference ptr, not before.
|
|
291
|
+
def commit_realvar(ptr)
|
|
292
|
+
old_ptr = @realvar_finalizer_state[:ptr]
|
|
293
|
+
@realvar_finalizer_state[:ptr] = ptr
|
|
294
|
+
return unless old_ptr
|
|
295
|
+
|
|
296
|
+
self.class.clear_realvar(old_ptr)
|
|
297
|
+
Fiddle.free(old_ptr)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# olevariant_free: VariantClear what the realvar holds now (for
|
|
301
|
+
# VT_BYREF, whatever the buffer holds -- a callee may have replaced it),
|
|
302
|
+
# then free the buffer. Runs at most once.
|
|
303
|
+
# Not deferred like the Release finalizers: as C's dfree does, this
|
|
304
|
+
# calls VariantClear on whatever thread GC picks (on JRuby, one without
|
|
305
|
+
# OLE).
|
|
306
|
+
def self.realvar_finalizer(state)
|
|
307
|
+
proc do
|
|
308
|
+
if (ptr = state.delete(:ptr))
|
|
309
|
+
clear_realvar(ptr)
|
|
310
|
+
Fiddle.free(ptr)
|
|
311
|
+
end
|
|
312
|
+
bytes = state.delete(:bytes)
|
|
313
|
+
clear_realvar(bytes) if bytes
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# VariantClear, skipped for the types that own nothing.
|
|
318
|
+
def self.clear_realvar(var)
|
|
319
|
+
bytes = var.is_a?(::String) ? var : Fiddle::Pointer.new(var.to_i)[0, W::VARIANT_SIZE]
|
|
320
|
+
vt, = W.unpack_variant(bytes)
|
|
321
|
+
owns = (vt & VT::VT_ARRAY) != 0 ||
|
|
322
|
+
[VT::VT_BSTR, VT::VT_DISPATCH, VT::VT_UNKNOWN, VT::VT_RECORD].include?(vt & VT::VT_TYPEMASK)
|
|
323
|
+
W.variant_clear(var) if owns
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def array_state
|
|
327
|
+
vt, payload = W.unpack_variant(@var)
|
|
328
|
+
addr = W.unpack_pointer(payload)
|
|
329
|
+
addr = Fiddle::Pointer.new(addr)[0, W::PTR_SIZE].unpack1(W::PACK_PTR) if (vt & VT::VT_BYREF) != 0
|
|
330
|
+
[vt & VT::VT_TYPEMASK, addr]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
DISP_E_PARAMNOTFOUND = -2147352572 # 0x80020004
|
|
334
|
+
|
|
335
|
+
# Empty/Null/Nothing/NoParam are lazy, not eager: NoParam's construction
|
|
336
|
+
# calls VariantChangeTypeEx (a real Fiddle.dlopen('oleaut32') on first
|
|
337
|
+
# use), and every other native call in this codebase is lazy/memoized so
|
|
338
|
+
# that simply requiring the gem is harmless on any platform. const_missing
|
|
339
|
+
# preserves the exact same WIN32OLE::Variant::Empty-style constant-access
|
|
340
|
+
# API MRI provides, while deferring construction to first actual access.
|
|
341
|
+
def self.const_missing(name)
|
|
342
|
+
case name
|
|
343
|
+
when :Empty then const_set(:Empty, new(nil, VariantType::VT_EMPTY))
|
|
344
|
+
when :Null then const_set(:Null, new(nil, VariantType::VT_NULL))
|
|
345
|
+
when :Nothing then const_set(:Nothing, new(nil, VariantType::VT_DISPATCH))
|
|
346
|
+
when :NoParam then const_set(:NoParam, new(DISP_E_PARAMNOTFOUND, VariantType::VT_ERROR))
|
|
347
|
+
else super
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|