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,800 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'win32ole/ffi/win32'
|
|
4
|
+
require 'win32ole/ffi/typeinfo'
|
|
5
|
+
require 'win32ole/ffi/win32ole'
|
|
6
|
+
|
|
7
|
+
class WIN32OLE
|
|
8
|
+
class Event
|
|
9
|
+
W = Win32
|
|
10
|
+
TI = TypeInfo
|
|
11
|
+
private_constant :W, :TI
|
|
12
|
+
|
|
13
|
+
def initialize(ole, itf = nil)
|
|
14
|
+
raise TypeError, '1st parameter must be WIN32OLE object' unless ole.is_a?(WIN32OLE)
|
|
15
|
+
|
|
16
|
+
@events = []
|
|
17
|
+
@handler = nil
|
|
18
|
+
@finalizer_state = nil
|
|
19
|
+
@sink_closures = nil
|
|
20
|
+
@event_typeinfo_ptr = nil
|
|
21
|
+
@ole = nil
|
|
22
|
+
|
|
23
|
+
advise(ole, itf)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.message_loop
|
|
27
|
+
W.pump_windows_messages
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_event(event = nil, &block)
|
|
31
|
+
register_event(event, block, false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_event_with_outargs(event = nil, &block)
|
|
35
|
+
register_event(event, block, true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def off_event(event = nil)
|
|
39
|
+
name = event.nil? ? nil : normalize_event_name(event)
|
|
40
|
+
@events.reject! { |e| e[:name] == name }
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def handler=(obj)
|
|
45
|
+
@handler = obj
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handler
|
|
49
|
+
@handler
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def unadvise
|
|
53
|
+
return nil if @finalizer_state.nil? || @finalizer_state[:cp_ptr].nil?
|
|
54
|
+
|
|
55
|
+
# ext/win32ole/win32ole_event.c:1164 (fev_unadvise): drain the message
|
|
56
|
+
# queue BEFORE Unadvise, so an already-queued event can't fire into a
|
|
57
|
+
# sink we are about to tear down. Deliberately NOT done in
|
|
58
|
+
# self.finalizer -- a finalizer runs at GC time with no well-defined
|
|
59
|
+
# thread or message-loop context.
|
|
60
|
+
W.pump_windows_messages
|
|
61
|
+
|
|
62
|
+
cp_ptr = @finalizer_state[:cp_ptr]
|
|
63
|
+
W.vtable_function(cp_ptr, 6, [W::VOIDP, W::DWORD], W::LONG).call(cp_ptr, @finalizer_state[:cookie])
|
|
64
|
+
W.vtable_function(cp_ptr, 2, [W::VOIDP], W::DWORD).call(cp_ptr)
|
|
65
|
+
release_ptr(@finalizer_state[:disp_ptr])
|
|
66
|
+
ti_ptr = @finalizer_state[:ti_ptr]
|
|
67
|
+
W.vtable_function(ti_ptr, 2, [W::VOIDP], W::DWORD).call(ti_ptr) if ti_ptr && !ti_ptr.zero?
|
|
68
|
+
Fiddle.free(Fiddle::Pointer.new(@finalizer_state[:sink_addr])) if @finalizer_state[:sink_addr]
|
|
69
|
+
Fiddle.free(Fiddle::Pointer.new(@finalizer_state[:vtable_addr])) if @finalizer_state[:vtable_addr]
|
|
70
|
+
|
|
71
|
+
@finalizer_state[:cp_ptr] = nil
|
|
72
|
+
@finalizer_state[:disp_ptr] = nil
|
|
73
|
+
@finalizer_state[:ti_ptr] = nil
|
|
74
|
+
@finalizer_state[:sink_addr] = nil
|
|
75
|
+
@finalizer_state[:vtable_addr] = nil
|
|
76
|
+
@finalizer_state[:iunknown_closures] = nil
|
|
77
|
+
@sink_closures = nil
|
|
78
|
+
@event_typeinfo_ptr = nil
|
|
79
|
+
ADVISED.delete(self)
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
TKIND_COCLASS = 5
|
|
86
|
+
private_constant :TKIND_COCLASS
|
|
87
|
+
|
|
88
|
+
def release_ptr(ptr)
|
|
89
|
+
return if ptr.nil? || ptr.zero?
|
|
90
|
+
|
|
91
|
+
W.vtable_function(ptr, 2, [W::VOIDP], W::DWORD).call(ptr)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def get_type_info0(idispatch_ptr)
|
|
95
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
96
|
+
hr = TI.get_type_info_fn(idispatch_ptr).call(idispatch_ptr, 0, W::LOCALE_SYSTEM_DEFAULT, out)
|
|
97
|
+
return nil if W.failed?(hr)
|
|
98
|
+
|
|
99
|
+
out.unpack1(W::PACK_PTR)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def containing_typelib(itypeinfo_ptr)
|
|
103
|
+
tlib_out = ("\x00" * W::PTR_SIZE).b
|
|
104
|
+
index_out = ("\x00" * 4).b
|
|
105
|
+
hr = TI.containing_typelib_fn(itypeinfo_ptr).call(itypeinfo_ptr, tlib_out, index_out)
|
|
106
|
+
return nil if W.failed?(hr)
|
|
107
|
+
|
|
108
|
+
tlib_out.unpack1(W::PACK_PTR)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def type_attr_ptr(itypeinfo_ptr)
|
|
112
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
113
|
+
hr = TI.type_attr_fn(itypeinfo_ptr).call(itypeinfo_ptr, out)
|
|
114
|
+
return nil if W.failed?(hr)
|
|
115
|
+
|
|
116
|
+
out.unpack1(W::PACK_PTR)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def type_name(itypeinfo_ptr)
|
|
120
|
+
name_out = ("\x00" * W::PTR_SIZE).b
|
|
121
|
+
TI.documentation_fn_for_typeinfo(itypeinfo_ptr).call(itypeinfo_ptr, -1, name_out, nil, nil, nil)
|
|
122
|
+
bstr = name_out.unpack1(W::PACK_PTR)
|
|
123
|
+
name = W.bstr_to_s(bstr)
|
|
124
|
+
W.sys_free_string.call(bstr) unless bstr.zero?
|
|
125
|
+
name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def type_guid(attr_ptr)
|
|
129
|
+
ta = TI::TYPEATTR.new(attr_ptr)
|
|
130
|
+
[ta.guid_Data1, ta.guid_Data2, ta.guid_Data3].pack('LSS') + ta.guid_Data4.pack('C8')
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def impl_type_ref_typeinfo(itypeinfo_ptr, index)
|
|
134
|
+
href_out = ("\x00" * 4).b
|
|
135
|
+
hr = TI.ref_type_of_impl_type_fn(itypeinfo_ptr).call(itypeinfo_ptr, index, href_out)
|
|
136
|
+
return nil if W.failed?(hr)
|
|
137
|
+
|
|
138
|
+
ref_out = ("\x00" * W::PTR_SIZE).b
|
|
139
|
+
hr = TI.ref_type_info_fn(itypeinfo_ptr).call(itypeinfo_ptr, href_out.unpack1('L'), ref_out)
|
|
140
|
+
return nil if W.failed?(hr)
|
|
141
|
+
|
|
142
|
+
ref_out.unpack1(W::PACK_PTR)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# ext/win32ole/win32ole_event.c:481-590 (find_iid, pitf given): scans
|
|
146
|
+
# every COCLASS in ole's containing typelib for an implemented type
|
|
147
|
+
# named itf_name; that impl type's own GUID becomes the event source IID.
|
|
148
|
+
def find_iid_by_name(ole, itf_name)
|
|
149
|
+
itypeinfo_ptr = get_type_info0(ole.dispatch_ptr)
|
|
150
|
+
raise ::RuntimeError, 'interface not found' if itypeinfo_ptr.nil?
|
|
151
|
+
|
|
152
|
+
tlib_ptr = containing_typelib(itypeinfo_ptr)
|
|
153
|
+
release_ptr(itypeinfo_ptr)
|
|
154
|
+
raise ::RuntimeError, 'interface not found' if tlib_ptr.nil?
|
|
155
|
+
|
|
156
|
+
found_iid = nil
|
|
157
|
+
found_ti_ptr = nil
|
|
158
|
+
count = TI.type_info_count_fn(tlib_ptr).call(tlib_ptr)
|
|
159
|
+
count.times do |index|
|
|
160
|
+
break if found_iid
|
|
161
|
+
|
|
162
|
+
ti_out = ("\x00" * W::PTR_SIZE).b
|
|
163
|
+
next if W.failed?(TI.type_info_fn(tlib_ptr).call(tlib_ptr, index, ti_out))
|
|
164
|
+
|
|
165
|
+
ti_ptr = ti_out.unpack1(W::PACK_PTR)
|
|
166
|
+
attr_ptr = type_attr_ptr(ti_ptr)
|
|
167
|
+
if attr_ptr
|
|
168
|
+
if TI::TYPEATTR.new(attr_ptr).typekind == TKIND_COCLASS
|
|
169
|
+
impl_count = TI::TYPEATTR.new(attr_ptr).cImplTypes
|
|
170
|
+
impl_count.times do |t|
|
|
171
|
+
impl_ti_ptr = impl_type_ref_typeinfo(ti_ptr, t)
|
|
172
|
+
next if impl_ti_ptr.nil?
|
|
173
|
+
|
|
174
|
+
if type_name(impl_ti_ptr) == itf_name
|
|
175
|
+
impl_attr_ptr = type_attr_ptr(impl_ti_ptr)
|
|
176
|
+
if impl_attr_ptr
|
|
177
|
+
found_iid = type_guid(impl_attr_ptr)
|
|
178
|
+
TI.release_type_attr_fn(impl_ti_ptr).call(impl_ti_ptr, impl_attr_ptr)
|
|
179
|
+
found_ti_ptr = impl_ti_ptr
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
release_ptr(impl_ti_ptr) unless impl_ti_ptr == found_ti_ptr
|
|
183
|
+
break if found_iid
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
TI.release_type_attr_fn(ti_ptr).call(ti_ptr, attr_ptr)
|
|
187
|
+
end
|
|
188
|
+
release_ptr(ti_ptr)
|
|
189
|
+
end
|
|
190
|
+
release_ptr(tlib_ptr)
|
|
191
|
+
raise ::RuntimeError, 'interface not found' if found_iid.nil?
|
|
192
|
+
|
|
193
|
+
[found_iid, found_ti_ptr]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# ext/win32ole/win32ole_event.c:518-524 (find_iid, pitf NULL): a direct
|
|
197
|
+
# GetTypeInfoOfGuid lookup, used when the caller already knows the IID
|
|
198
|
+
# (the IProvideClassInfo2::GetGUID path in find_default_source).
|
|
199
|
+
def find_iid_by_guid(ole, iid_bytes)
|
|
200
|
+
itypeinfo_ptr = get_type_info0(ole.dispatch_ptr)
|
|
201
|
+
raise ::RuntimeError, 'interface not found' if itypeinfo_ptr.nil?
|
|
202
|
+
|
|
203
|
+
tlib_ptr = containing_typelib(itypeinfo_ptr)
|
|
204
|
+
release_ptr(itypeinfo_ptr)
|
|
205
|
+
raise ::RuntimeError, 'interface not found' if tlib_ptr.nil?
|
|
206
|
+
|
|
207
|
+
ti_out = ("\x00" * W::PTR_SIZE).b
|
|
208
|
+
hr = TI.type_info_of_guid_fn(tlib_ptr).call(tlib_ptr, iid_bytes, ti_out)
|
|
209
|
+
release_ptr(tlib_ptr)
|
|
210
|
+
raise ::RuntimeError, 'interface not found' if W.failed?(hr)
|
|
211
|
+
|
|
212
|
+
ti_out.unpack1(W::PACK_PTR)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
IMPLTYPEFLAG_FDEFAULT = 0x1
|
|
216
|
+
IMPLTYPEFLAG_FSOURCE = 0x2
|
|
217
|
+
GUIDKIND_DEFAULT_SOURCE_DISP_IID = 1
|
|
218
|
+
private_constant :IMPLTYPEFLAG_FDEFAULT, :IMPLTYPEFLAG_FSOURCE, :GUIDKIND_DEFAULT_SOURCE_DISP_IID
|
|
219
|
+
|
|
220
|
+
def impl_type_flags(itypeinfo_ptr, index)
|
|
221
|
+
flags_out = ("\x00" * 4).b
|
|
222
|
+
hr = TI.impl_type_flags_fn(itypeinfo_ptr).call(itypeinfo_ptr, index, flags_out)
|
|
223
|
+
return nil if W.failed?(hr)
|
|
224
|
+
|
|
225
|
+
flags_out.unpack1('l')
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# ext/win32ole/win32ole_event.c:667-701
|
|
229
|
+
def find_default_source_from_typeinfo(ti_ptr, attr_ptr)
|
|
230
|
+
count = TI::TYPEATTR.new(attr_ptr).cImplTypes
|
|
231
|
+
count.times do |i|
|
|
232
|
+
flags = impl_type_flags(ti_ptr, i)
|
|
233
|
+
next if flags.nil?
|
|
234
|
+
next if (flags & IMPLTYPEFLAG_FDEFAULT).zero? || (flags & IMPLTYPEFLAG_FSOURCE).zero?
|
|
235
|
+
|
|
236
|
+
ref_ti_ptr = impl_type_ref_typeinfo(ti_ptr, i)
|
|
237
|
+
return ref_ti_ptr if ref_ti_ptr
|
|
238
|
+
end
|
|
239
|
+
nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# ext/win32ole/win32ole_event.c:592-665: find, in ti_ptr's containing
|
|
243
|
+
# typelib, the COCLASS whose default impl type is ti_ptr itself.
|
|
244
|
+
def find_coclass(ti_ptr)
|
|
245
|
+
tlib_ptr = containing_typelib(ti_ptr)
|
|
246
|
+
return [nil, nil] if tlib_ptr.nil?
|
|
247
|
+
|
|
248
|
+
target_attr_ptr = type_attr_ptr(ti_ptr)
|
|
249
|
+
target_guid = target_attr_ptr && type_guid(target_attr_ptr)
|
|
250
|
+
TI.release_type_attr_fn(ti_ptr).call(ti_ptr, target_attr_ptr) if target_attr_ptr
|
|
251
|
+
if target_guid.nil?
|
|
252
|
+
release_ptr(tlib_ptr)
|
|
253
|
+
return [nil, nil]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
found_ti_ptr = nil
|
|
257
|
+
found_attr_ptr = nil
|
|
258
|
+
count = TI.type_info_count_fn(tlib_ptr).call(tlib_ptr)
|
|
259
|
+
count.times do |i|
|
|
260
|
+
break if found_ti_ptr
|
|
261
|
+
|
|
262
|
+
ti2_out = ("\x00" * W::PTR_SIZE).b
|
|
263
|
+
next if W.failed?(TI.type_info_fn(tlib_ptr).call(tlib_ptr, i, ti2_out))
|
|
264
|
+
|
|
265
|
+
ti2_ptr = ti2_out.unpack1(W::PACK_PTR)
|
|
266
|
+
attr2_ptr = type_attr_ptr(ti2_ptr)
|
|
267
|
+
if attr2_ptr.nil?
|
|
268
|
+
release_ptr(ti2_ptr)
|
|
269
|
+
next
|
|
270
|
+
end
|
|
271
|
+
if TI::TYPEATTR.new(attr2_ptr).typekind != TKIND_COCLASS
|
|
272
|
+
TI.release_type_attr_fn(ti2_ptr).call(ti2_ptr, attr2_ptr)
|
|
273
|
+
release_ptr(ti2_ptr)
|
|
274
|
+
next
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
matched = TI::TYPEATTR.new(attr2_ptr).cImplTypes.times.any? do |j|
|
|
278
|
+
flags = impl_type_flags(ti2_ptr, j)
|
|
279
|
+
next false if flags.nil? || (flags & IMPLTYPEFLAG_FDEFAULT).zero?
|
|
280
|
+
|
|
281
|
+
ref_ti_ptr = impl_type_ref_typeinfo(ti2_ptr, j)
|
|
282
|
+
next false if ref_ti_ptr.nil?
|
|
283
|
+
|
|
284
|
+
ref_attr_ptr = type_attr_ptr(ref_ti_ptr)
|
|
285
|
+
ref_guid = ref_attr_ptr && type_guid(ref_attr_ptr)
|
|
286
|
+
TI.release_type_attr_fn(ref_ti_ptr).call(ref_ti_ptr, ref_attr_ptr) if ref_attr_ptr
|
|
287
|
+
release_ptr(ref_ti_ptr)
|
|
288
|
+
# A candidate whose own TYPEATTR fetch failed has no GUID to
|
|
289
|
+
# compare -- treat it as no match, never as equal to a
|
|
290
|
+
# likewise-missing target GUID (nil == nil would be a false hit).
|
|
291
|
+
!ref_guid.nil? && ref_guid == target_guid
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if matched
|
|
295
|
+
found_ti_ptr = ti2_ptr
|
|
296
|
+
found_attr_ptr = attr2_ptr
|
|
297
|
+
else
|
|
298
|
+
TI.release_type_attr_fn(ti2_ptr).call(ti2_ptr, attr2_ptr)
|
|
299
|
+
release_ptr(ti2_ptr)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
release_ptr(tlib_ptr)
|
|
303
|
+
[found_ti_ptr, found_attr_ptr]
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def provide_class_info2_iid(idispatch_ptr)
|
|
307
|
+
pci2_ptr = W.query_interface(idispatch_ptr, W::IID_IPROVIDECLASSINFO2)
|
|
308
|
+
return nil if pci2_ptr.nil?
|
|
309
|
+
|
|
310
|
+
iid_out = ("\x00" * 16).b
|
|
311
|
+
hr = W.vtable_function(pci2_ptr, 4, [W::VOIDP, W::DWORD, W::VOIDP], W::LONG).call(
|
|
312
|
+
pci2_ptr, GUIDKIND_DEFAULT_SOURCE_DISP_IID, iid_out
|
|
313
|
+
)
|
|
314
|
+
release_ptr(pci2_ptr)
|
|
315
|
+
return nil if W.failed?(hr)
|
|
316
|
+
|
|
317
|
+
iid_out
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def provide_class_info_typeinfo(idispatch_ptr)
|
|
321
|
+
pci_ptr = W.query_interface(idispatch_ptr, W::IID_IPROVIDECLASSINFO)
|
|
322
|
+
return nil if pci_ptr.nil?
|
|
323
|
+
|
|
324
|
+
ti_out = ("\x00" * W::PTR_SIZE).b
|
|
325
|
+
hr = W.vtable_function(pci_ptr, 3, [W::VOIDP, W::VOIDP], W::LONG).call(pci_ptr, ti_out)
|
|
326
|
+
release_ptr(pci_ptr)
|
|
327
|
+
return nil if W.failed?(hr)
|
|
328
|
+
|
|
329
|
+
ti_out.unpack1(W::PACK_PTR)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# ext/win32ole/win32ole_event.c:703-786, minus the GetGUID/find_iid
|
|
333
|
+
# fast path (handled by resolve_event_source below, since it needs an
|
|
334
|
+
# `iid_bytes` result rather than an `itypeinfo_ptr` result).
|
|
335
|
+
def find_default_source(ole)
|
|
336
|
+
itypeinfo_ptr = provide_class_info_typeinfo(ole.dispatch_ptr) || get_type_info0(ole.dispatch_ptr)
|
|
337
|
+
raise ::RuntimeError, 'interface not found' if itypeinfo_ptr.nil?
|
|
338
|
+
|
|
339
|
+
attr_ptr = type_attr_ptr(itypeinfo_ptr)
|
|
340
|
+
if attr_ptr.nil?
|
|
341
|
+
release_ptr(itypeinfo_ptr)
|
|
342
|
+
raise ::RuntimeError, 'interface not found'
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
result_ti_ptr = find_default_source_from_typeinfo(itypeinfo_ptr, attr_ptr)
|
|
346
|
+
if result_ti_ptr.nil?
|
|
347
|
+
co_ti_ptr, co_attr_ptr = find_coclass(itypeinfo_ptr)
|
|
348
|
+
if co_ti_ptr
|
|
349
|
+
result_ti_ptr = find_default_source_from_typeinfo(co_ti_ptr, co_attr_ptr)
|
|
350
|
+
TI.release_type_attr_fn(co_ti_ptr).call(co_ti_ptr, co_attr_ptr)
|
|
351
|
+
release_ptr(co_ti_ptr)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
TI.release_type_attr_fn(itypeinfo_ptr).call(itypeinfo_ptr, attr_ptr)
|
|
355
|
+
release_ptr(itypeinfo_ptr)
|
|
356
|
+
raise ::RuntimeError, 'interface not found' if result_ti_ptr.nil?
|
|
357
|
+
|
|
358
|
+
result_attr_ptr = type_attr_ptr(result_ti_ptr)
|
|
359
|
+
if result_attr_ptr.nil?
|
|
360
|
+
release_ptr(result_ti_ptr)
|
|
361
|
+
raise ::RuntimeError, 'interface not found'
|
|
362
|
+
end
|
|
363
|
+
guid = type_guid(result_attr_ptr)
|
|
364
|
+
TI.release_type_attr_fn(result_ti_ptr).call(result_ti_ptr, result_attr_ptr)
|
|
365
|
+
[guid, result_ti_ptr]
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# The single entry point #advise calls.
|
|
369
|
+
def resolve_event_source(ole, itf)
|
|
370
|
+
return find_iid_by_name(ole, itf) if itf
|
|
371
|
+
|
|
372
|
+
iid_bytes = provide_class_info2_iid(ole.dispatch_ptr)
|
|
373
|
+
if iid_bytes
|
|
374
|
+
begin
|
|
375
|
+
return [iid_bytes, find_iid_by_guid(ole, iid_bytes)]
|
|
376
|
+
rescue ::RuntimeError
|
|
377
|
+
# IProvideClassInfo2 succeeded but the IID it named isn't
|
|
378
|
+
# resolvable in this typelib -- fall through to the
|
|
379
|
+
# IProvideClassInfo/ImplType-traversal path below, matching
|
|
380
|
+
# win32ole_event.c:730-736's own fallthrough.
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
find_default_source(ole)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# ext/win32ole/win32ole_event.c:900-973 (ev_advise)
|
|
387
|
+
def advise(ole, itf)
|
|
388
|
+
iid_bytes, event_typeinfo_ptr = resolve_event_source(ole, itf)
|
|
389
|
+
|
|
390
|
+
idispatch_ptr = ole.dispatch_ptr
|
|
391
|
+
container_ptr = W.query_interface(idispatch_ptr, W::IID_ICONNECTIONPOINTCONTAINER)
|
|
392
|
+
if container_ptr.nil?
|
|
393
|
+
release_ptr(event_typeinfo_ptr)
|
|
394
|
+
raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('query IConnectionPointContainer', 'E_NOINTERFACE')
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
cp_out = ("\x00" * W::PTR_SIZE).b
|
|
398
|
+
hr = W.vtable_function(container_ptr, 4, [W::VOIDP, W::VOIDP, W::VOIDP], W::LONG).call(
|
|
399
|
+
container_ptr, iid_bytes, cp_out
|
|
400
|
+
)
|
|
401
|
+
release_ptr(container_ptr)
|
|
402
|
+
if W.failed?(hr)
|
|
403
|
+
release_ptr(event_typeinfo_ptr)
|
|
404
|
+
raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('query IConnectionPoint', W.hr_hex(hr))
|
|
405
|
+
end
|
|
406
|
+
connection_point_ptr = cp_out.unpack1(W::PACK_PTR)
|
|
407
|
+
|
|
408
|
+
sink_addr, vtable_addr, closures = build_sink(iid_bytes, event_typeinfo_ptr)
|
|
409
|
+
@sink_closures = closures # keep the trampolines alive; see build_sink's own comment
|
|
410
|
+
|
|
411
|
+
cookie_out = ("\x00" * 4).b
|
|
412
|
+
# cookie_out is a DWORD* OUT-parameter (a Ruby byte buffer), so its
|
|
413
|
+
# declared type is VOIDP -- the pointer -- not DWORD, the value it
|
|
414
|
+
# points at.
|
|
415
|
+
hr = W.vtable_function(connection_point_ptr, 5, [W::VOIDP, W::VOIDP, W::VOIDP], W::LONG).call(
|
|
416
|
+
connection_point_ptr, sink_addr, cookie_out
|
|
417
|
+
)
|
|
418
|
+
if W.failed?(hr)
|
|
419
|
+
release_ptr(connection_point_ptr)
|
|
420
|
+
release_ptr(event_typeinfo_ptr)
|
|
421
|
+
Fiddle.free(Fiddle::Pointer.new(sink_addr))
|
|
422
|
+
Fiddle.free(Fiddle::Pointer.new(vtable_addr))
|
|
423
|
+
@sink_closures = nil
|
|
424
|
+
raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('Advise', W.hr_hex(hr))
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
@event_typeinfo_ptr = event_typeinfo_ptr
|
|
428
|
+
@ole = ole
|
|
429
|
+
# ev_advise's OLE_ADDREF(pDispatch): a Ruby reference alone doesn't
|
|
430
|
+
# order finalizers, so the source's could otherwise run first and
|
|
431
|
+
# destroy the connection point before ours Unadvises it.
|
|
432
|
+
W.add_ref(idispatch_ptr)
|
|
433
|
+
@finalizer_state = {
|
|
434
|
+
cp_ptr: connection_point_ptr, cookie: cookie_out.unpack1('L'), disp_ptr: idispatch_ptr,
|
|
435
|
+
ti_ptr: event_typeinfo_ptr, sink_addr: sink_addr, vtable_addr: vtable_addr,
|
|
436
|
+
# Unadvise calls back into the sink's IUnknown methods. GC frees
|
|
437
|
+
# @sink_closures together with self, before the finalizer runs, so the
|
|
438
|
+
# finalizer must keep these alive itself; they capture no self (see
|
|
439
|
+
# build_sink).
|
|
440
|
+
iunknown_closures: closures.first(3)
|
|
441
|
+
}
|
|
442
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@finalizer_state))
|
|
443
|
+
# fev_initialize's evs_push: an advised Event stays reachable until
|
|
444
|
+
# #unadvise, so GC never Unadvises it at an arbitrary point (inside a
|
|
445
|
+
# sink callback, or on another thread).
|
|
446
|
+
ADVISED << self
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
ADVISED = []
|
|
450
|
+
private_constant :ADVISED
|
|
451
|
+
|
|
452
|
+
# Mirrors variant.rb:282-300's discipline exactly: the finalizer proc
|
|
453
|
+
# captures ONLY this data hash, never self and never the Fiddle::Closure
|
|
454
|
+
# objects that capture self (@sink_closures) -- capturing either would
|
|
455
|
+
# create a reference cycle that prevents GC from ever running the
|
|
456
|
+
# finalizer at all.
|
|
457
|
+
# Not deferred like the Release finalizers: as C's dfree does, this
|
|
458
|
+
# Unadvises on whatever thread GC picks (on JRuby, one without OLE).
|
|
459
|
+
def self.finalizer(state)
|
|
460
|
+
proc do
|
|
461
|
+
cp_ptr = state[:cp_ptr]
|
|
462
|
+
if cp_ptr && !cp_ptr.zero?
|
|
463
|
+
W.vtable_function(cp_ptr, 6, [W::VOIDP, W::DWORD], W::LONG).call(cp_ptr, state[:cookie])
|
|
464
|
+
W.vtable_function(cp_ptr, 2, [W::VOIDP], W::DWORD).call(cp_ptr)
|
|
465
|
+
end
|
|
466
|
+
disp_ptr = state[:disp_ptr]
|
|
467
|
+
W.vtable_function(disp_ptr, 2, [W::VOIDP], W::DWORD).call(disp_ptr) if disp_ptr && !disp_ptr.zero?
|
|
468
|
+
ti_ptr = state[:ti_ptr]
|
|
469
|
+
W.vtable_function(ti_ptr, 2, [W::VOIDP], W::DWORD).call(ti_ptr) if ti_ptr && !ti_ptr.zero?
|
|
470
|
+
Fiddle.free(Fiddle::Pointer.new(state[:sink_addr])) if state[:sink_addr]
|
|
471
|
+
Fiddle.free(Fiddle::Pointer.new(state[:vtable_addr])) if state[:vtable_addr]
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def register_event(event, block, with_outargs)
|
|
476
|
+
if @finalizer_state.nil? || @finalizer_state[:cp_ptr].nil?
|
|
477
|
+
raise WIN32OLE::RuntimeError, 'IConnectionPoint not found. You must call advise at first.'
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
name = event.nil? ? nil : normalize_event_name(event)
|
|
481
|
+
@events.reject! { |e| e[:name] == name }
|
|
482
|
+
@events << { name: name, proc: block, with_outargs: with_outargs }
|
|
483
|
+
nil
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def normalize_event_name(event)
|
|
487
|
+
unless event.is_a?(String) || event.is_a?(Symbol)
|
|
488
|
+
raise TypeError, 'wrong argument type (expected String or Symbol)'
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
event.to_s
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
SINK_VTBL_SLOTS = 7
|
|
495
|
+
private_constant :SINK_VTBL_SLOTS
|
|
496
|
+
|
|
497
|
+
def self.query_interface_closure(sink_addr, source_iid_bytes, refcount)
|
|
498
|
+
Fiddle::Closure::BlockCaller.new(W::LONG, [W::VOIDP, W::VOIDP, W::VOIDP], W::STDCALL) do |_this, riid_ptr, ppv_ptr|
|
|
499
|
+
riid = riid_ptr[0, 16]
|
|
500
|
+
if riid == W::IID_IUNKNOWN || riid == W::IID_IDISPATCH || riid == source_iid_bytes
|
|
501
|
+
ppv_ptr[0, W::PTR_SIZE] = [sink_addr].pack(W::PACK_PTR)
|
|
502
|
+
refcount[0] += 1
|
|
503
|
+
0
|
|
504
|
+
else
|
|
505
|
+
ppv_ptr[0, W::PTR_SIZE] = [0].pack(W::PACK_PTR)
|
|
506
|
+
E_NOINTERFACE
|
|
507
|
+
end
|
|
508
|
+
# `rescue Exception`, deliberately, at every closure boundary in this
|
|
509
|
+
# file: the rule is that NO Ruby exception may cross back into the OLE
|
|
510
|
+
# server's native stack frame, and SystemStackError, NoMemoryError,
|
|
511
|
+
# Interrupt and SignalException all descend from Exception rather than
|
|
512
|
+
# StandardError. Letting one of those unwind through a native frame is
|
|
513
|
+
# undefined behaviour, so this is one of the rare places where the
|
|
514
|
+
# broad rescue is the correct choice rather than an anti-pattern.
|
|
515
|
+
rescue Exception => e
|
|
516
|
+
warn_closure_exception('QueryInterface', e)
|
|
517
|
+
E_NOINTERFACE
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def self.add_ref_closure(refcount)
|
|
522
|
+
Fiddle::Closure::BlockCaller.new(W::DWORD, [W::VOIDP], W::STDCALL) do |_this|
|
|
523
|
+
refcount[0] += 1
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def self.release_closure(refcount)
|
|
528
|
+
Fiddle::Closure::BlockCaller.new(W::DWORD, [W::VOIDP], W::STDCALL) do |_this|
|
|
529
|
+
refcount[0] -= 1
|
|
530
|
+
refcount[0]
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def get_type_info_count_closure
|
|
535
|
+
Fiddle::Closure::BlockCaller.new(W::LONG, [W::VOIDP, W::VOIDP], W::STDCALL) do |_this, pct_ptr|
|
|
536
|
+
pct_ptr[0, 4] = [0].pack('L')
|
|
537
|
+
0
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def get_type_info_closure
|
|
542
|
+
Fiddle::Closure::BlockCaller.new(W::LONG, [W::VOIDP, W::DWORD, W::DWORD, W::VOIDP], W::STDCALL) do |_this, _idx, _lcid, ppti_ptr|
|
|
543
|
+
ppti_ptr[0, W::PTR_SIZE] = [0].pack(W::PACK_PTR)
|
|
544
|
+
DISP_E_BADINDEX
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def get_ids_of_names_closure(event_typeinfo_ptr)
|
|
549
|
+
Fiddle::Closure::BlockCaller.new(
|
|
550
|
+
W::LONG, [W::VOIDP, W::VOIDP, W::VOIDP, W::DWORD, W::DWORD, W::VOIDP], W::STDCALL
|
|
551
|
+
) do |_this, _riid, names_ptr, cnames, _lcid, dispids_ptr|
|
|
552
|
+
TI.get_ids_of_names_fn(event_typeinfo_ptr).call(event_typeinfo_ptr, names_ptr.to_i, cnames, dispids_ptr.to_i)
|
|
553
|
+
rescue Exception => e
|
|
554
|
+
warn_closure_exception('GetIDsOfNames', e)
|
|
555
|
+
DISP_E_UNKNOWNNAME
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
DISP_E_UNKNOWNNAME = -2147352570
|
|
560
|
+
E_NOINTERFACE = -2147467262
|
|
561
|
+
DISP_E_BADINDEX = -2147352565
|
|
562
|
+
private_constant :DISP_E_UNKNOWNNAME, :E_NOINTERFACE, :DISP_E_BADINDEX
|
|
563
|
+
|
|
564
|
+
def warn_closure_exception(where, error)
|
|
565
|
+
self.class.warn_closure_exception(where, error)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def self.warn_closure_exception(where, error)
|
|
569
|
+
backtrace = error.backtrace || []
|
|
570
|
+
warn "#{backtrace.first}: #{error.message} (#{error.class}) in WIN32OLE::Event sink's #{where}"
|
|
571
|
+
backtrace.drop(1).each { |line| warn "\tfrom #{line}" }
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# The event ITypeInfo* this sink resolves names against is read from
|
|
575
|
+
# @event_typeinfo_ptr (set once by #advise), not captured here -- the
|
|
576
|
+
# parameter is kept only so build_sink calls every closure builder the
|
|
577
|
+
# same way.
|
|
578
|
+
def invoke_closure(_event_typeinfo_ptr)
|
|
579
|
+
Fiddle::Closure::BlockCaller.new(
|
|
580
|
+
W::LONG, [W::VOIDP, W::LONG, W::VOIDP, W::DWORD, W::WORD, W::VOIDP, W::VOIDP, W::VOIDP, W::VOIDP], W::STDCALL
|
|
581
|
+
) do |_this, dispid, _riid, _lcid, _wflags, pdispparams_ptr, pvarresult_ptr, _pexcepinfo_ptr, _puargerr_ptr|
|
|
582
|
+
handle_invoke(dispid, pdispparams_ptr, pvarresult_ptr)
|
|
583
|
+
0 # NOERROR, always -- see Global Constraints: no exception may cross this boundary.
|
|
584
|
+
rescue Exception => e
|
|
585
|
+
warn_closure_exception('Invoke', e)
|
|
586
|
+
0
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def resolve_event_name(dispid)
|
|
591
|
+
bstr_out = ("\x00" * W::PTR_SIZE).b
|
|
592
|
+
count_out = ("\x00" * 4).b
|
|
593
|
+
hr = TI.get_names_fn(@event_typeinfo_ptr).call(@event_typeinfo_ptr, dispid, bstr_out, 1, count_out)
|
|
594
|
+
return nil if W.failed?(hr)
|
|
595
|
+
|
|
596
|
+
bstr = bstr_out.unpack1(W::PACK_PTR)
|
|
597
|
+
name = W.bstr_to_s(bstr)
|
|
598
|
+
W.sys_free_string.call(bstr) unless bstr.zero?
|
|
599
|
+
name
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# ext/win32ole/win32ole_event.c:813-836 (ole_search_event): a NAMED
|
|
603
|
+
# match wins immediately; otherwise fall back to the one catch-all
|
|
604
|
+
# (nil-name) entry, if any. Returns [entry_or_nil, is_default] --
|
|
605
|
+
# is_default is true only when a catch-all entry was actually found
|
|
606
|
+
# (matching the C source, which leaves *is_default FALSE when the
|
|
607
|
+
# array has no catch-all at all -- drives whether the event name gets
|
|
608
|
+
# prepended to the callback's args).
|
|
609
|
+
def find_event_entry(name)
|
|
610
|
+
fallback = nil
|
|
611
|
+
is_default = false
|
|
612
|
+
@events.each do |e|
|
|
613
|
+
return [e, false] if e[:name] == name
|
|
614
|
+
|
|
615
|
+
if e[:name].nil?
|
|
616
|
+
fallback = e
|
|
617
|
+
is_default = true
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
[fallback, is_default]
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def read_dispparams(pdispparams_ptr)
|
|
624
|
+
rgvarg_addr = pdispparams_ptr[0, W::PTR_SIZE].unpack1(W::PACK_PTR)
|
|
625
|
+
cargs = pdispparams_ptr[2 * W::PTR_SIZE, 4].unpack1('L')
|
|
626
|
+
[cargs, rgvarg_addr]
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# ext/win32ole/win32ole_event.c:132-234 (EVENTSINK_Invoke), including
|
|
630
|
+
# the Hash/Array out-argument write-back (#write_hash_result,
|
|
631
|
+
# #write_array_outargs).
|
|
632
|
+
def handle_invoke(dispid, pdispparams_ptr, pvarresult_ptr)
|
|
633
|
+
name = resolve_event_name(dispid)
|
|
634
|
+
return if name.nil?
|
|
635
|
+
|
|
636
|
+
entry, is_default = find_event_entry(name)
|
|
637
|
+
handler_obj = nil
|
|
638
|
+
mid = nil
|
|
639
|
+
with_outargs = false
|
|
640
|
+
if entry
|
|
641
|
+
handler_obj = entry[:proc]
|
|
642
|
+
mid = :call
|
|
643
|
+
with_outargs = entry[:with_outargs]
|
|
644
|
+
elsif @handler
|
|
645
|
+
on_name = "on#{name}"
|
|
646
|
+
if @handler.respond_to?(on_name)
|
|
647
|
+
handler_obj = @handler
|
|
648
|
+
mid = on_name
|
|
649
|
+
is_default = false
|
|
650
|
+
elsif @handler.respond_to?(:method_missing)
|
|
651
|
+
handler_obj = @handler
|
|
652
|
+
mid = :method_missing
|
|
653
|
+
is_default = true
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
return if handler_obj.nil? || mid.nil?
|
|
657
|
+
|
|
658
|
+
args = []
|
|
659
|
+
args << name if is_default
|
|
660
|
+
cargs, rgvarg_addr = read_dispparams(pdispparams_ptr)
|
|
661
|
+
cargs.times do |i|
|
|
662
|
+
var_ptr = Fiddle::Pointer.new(rgvarg_addr + (cargs - i - 1) * W::VARIANT_SIZE)
|
|
663
|
+
# rgvarg belongs to the event source; read it without consuming it
|
|
664
|
+
args << WIN32OLE.variant_bytes_to_ruby_value(var_ptr[0, W::VARIANT_SIZE], owned: false)
|
|
665
|
+
end
|
|
666
|
+
outargv = nil
|
|
667
|
+
if with_outargs
|
|
668
|
+
outargv = []
|
|
669
|
+
args << outargv
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
result = begin
|
|
673
|
+
handler_obj.send(mid, *args)
|
|
674
|
+
rescue Exception => e # see query_interface_closure: still inside the closure's native frame
|
|
675
|
+
warn_closure_exception('an event callback', e)
|
|
676
|
+
nil
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
if result.is_a?(Hash)
|
|
680
|
+
write_hash_result(result, dispid, cargs, rgvarg_addr)
|
|
681
|
+
# key? rather than `||`, so a legitimate {'return' => false} yields
|
|
682
|
+
# false instead of falling through to the symbol key.
|
|
683
|
+
result = result.key?('return') ? result['return'] : result[:return]
|
|
684
|
+
elsif with_outargs && outargv.is_a?(Array)
|
|
685
|
+
write_array_outargs(outargv, cargs, rgvarg_addr)
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
return if pvarresult_ptr.nil? || pvarresult_ptr.to_i.zero?
|
|
689
|
+
|
|
690
|
+
bytes = begin
|
|
691
|
+
WIN32OLE.ruby_value_to_variant_bytes(result, [])
|
|
692
|
+
rescue StandardError
|
|
693
|
+
W.pack_variant(W::VT_EMPTY, W.pack_empty)
|
|
694
|
+
end
|
|
695
|
+
pvarresult_ptr[0, W::VARIANT_SIZE] = bytes
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
# ext/win32ole/win32ole_event.c:333-399 (ole_val2ptr_variant), ported
|
|
699
|
+
# 1:1 including its "silently do nothing for an unhandled
|
|
700
|
+
# type/VARTYPE combination" fallthrough -- e.g. writing a String into a
|
|
701
|
+
# VT_I4|BYREF slot is a deliberate no-op in the original, not a bug we
|
|
702
|
+
# should "fix" by raising.
|
|
703
|
+
def write_byref_variant(var_ptr, value)
|
|
704
|
+
vt = var_ptr[0, 2].unpack1('S')
|
|
705
|
+
return if (vt & W::VT_BYREF).zero?
|
|
706
|
+
|
|
707
|
+
ref_addr = var_ptr[8, W::PTR_SIZE].unpack1(W::PACK_PTR)
|
|
708
|
+
return if ref_addr.zero?
|
|
709
|
+
|
|
710
|
+
ref_ptr = Fiddle::Pointer.new(ref_addr)
|
|
711
|
+
base_vt = vt & ~W::VT_BYREF
|
|
712
|
+
case value
|
|
713
|
+
when String
|
|
714
|
+
ref_ptr[0, W::PTR_SIZE] = [W.sys_alloc_string.call(W.wstr(value))].pack(W::PACK_PTR) if base_vt == W::VT_BSTR
|
|
715
|
+
when Integer
|
|
716
|
+
case base_vt
|
|
717
|
+
when W::VT_UI1 then ref_ptr[0, 1] = [value].pack('C')
|
|
718
|
+
when W::VT_I2 then ref_ptr[0, 2] = [value].pack('s')
|
|
719
|
+
when W::VT_I4 then ref_ptr[0, 4] = [value].pack('l')
|
|
720
|
+
when W::VT_R4 then ref_ptr[0, 4] = [value.to_f].pack('f')
|
|
721
|
+
when W::VT_R8 then ref_ptr[0, 8] = [value.to_f].pack('d')
|
|
722
|
+
end
|
|
723
|
+
when Float
|
|
724
|
+
case base_vt
|
|
725
|
+
when W::VT_I2 then ref_ptr[0, 2] = [value.to_i].pack('s')
|
|
726
|
+
when W::VT_I4 then ref_ptr[0, 4] = [value.to_i].pack('l')
|
|
727
|
+
when W::VT_R4 then ref_ptr[0, 4] = [value].pack('f')
|
|
728
|
+
when W::VT_R8 then ref_ptr[0, 8] = [value].pack('d')
|
|
729
|
+
end
|
|
730
|
+
when true, false
|
|
731
|
+
ref_ptr[0, 2] = [value ? -1 : 0].pack('s') if base_vt == W::VT_BOOL
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def write_array_outargs(ary, cargs, rgvarg_addr)
|
|
736
|
+
ary.each_with_index do |value, i|
|
|
737
|
+
break if i >= cargs
|
|
738
|
+
|
|
739
|
+
var_ptr = Fiddle::Pointer.new(rgvarg_addr + (cargs - i - 1) * W::VARIANT_SIZE)
|
|
740
|
+
write_byref_variant(var_ptr, value)
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# ext/win32ole/win32ole_event.c:401-428 (hash2ptr_dispparams)
|
|
745
|
+
def write_hash_result(hash, dispid, cargs, rgvarg_addr)
|
|
746
|
+
names_out = Fiddle::Pointer.malloc(W::PTR_SIZE * (cargs + 1))
|
|
747
|
+
count_out = ("\x00" * 4).b
|
|
748
|
+
hr = TI.get_names_fn(@event_typeinfo_ptr).call(@event_typeinfo_ptr, dispid, names_out, cargs + 1, count_out)
|
|
749
|
+
return if W.failed?(hr)
|
|
750
|
+
|
|
751
|
+
len = count_out.unpack1('L')
|
|
752
|
+
(len - 1).times do |i|
|
|
753
|
+
bstr = names_out[(i + 1) * W::PTR_SIZE, W::PTR_SIZE].unpack1(W::PACK_PTR)
|
|
754
|
+
key_name = W.bstr_to_s(bstr)
|
|
755
|
+
W.sys_free_string.call(bstr) unless bstr.zero?
|
|
756
|
+
|
|
757
|
+
value = hash[i]
|
|
758
|
+
value = hash[key_name] if value.nil?
|
|
759
|
+
value = hash[key_name.to_sym] if value.nil? && key_name
|
|
760
|
+
|
|
761
|
+
var_ptr = Fiddle::Pointer.new(rgvarg_addr + (cargs - i - 1) * W::VARIANT_SIZE)
|
|
762
|
+
write_byref_variant(var_ptr, value)
|
|
763
|
+
end
|
|
764
|
+
ensure
|
|
765
|
+
Fiddle.free(names_out) if names_out
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Builds a fresh 7-slot IDispatch-shaped vtable (QueryInterface, AddRef,
|
|
769
|
+
# Release, GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke -- same
|
|
770
|
+
# order/shape as ext/win32ole/win32ole_event.c's IEventSinkVtbl) backed
|
|
771
|
+
# by Fiddle::Closure::BlockCaller trampolines. Returns raw addresses (not
|
|
772
|
+
# Fiddle::Pointer wrappers) plus the closures themselves -- the CALLER
|
|
773
|
+
# must keep `closures` referenced for as long as the sink is advised
|
|
774
|
+
# (GC'ing a Closure frees its native trampoline), matching variant.rb's
|
|
775
|
+
# own finalizer-state discipline (see #advise).
|
|
776
|
+
def build_sink(source_iid_bytes, event_typeinfo_ptr)
|
|
777
|
+
sink_ptr = Fiddle::Pointer.malloc(W::PTR_SIZE)
|
|
778
|
+
sink_addr = sink_ptr.to_i
|
|
779
|
+
refcount = [0]
|
|
780
|
+
|
|
781
|
+
closures = [
|
|
782
|
+
# Class methods, so these blocks capture the class rather than self
|
|
783
|
+
# and #advise can hand them to the finalizer.
|
|
784
|
+
self.class.query_interface_closure(sink_addr, source_iid_bytes, refcount),
|
|
785
|
+
self.class.add_ref_closure(refcount),
|
|
786
|
+
self.class.release_closure(refcount),
|
|
787
|
+
get_type_info_count_closure,
|
|
788
|
+
get_type_info_closure,
|
|
789
|
+
get_ids_of_names_closure(event_typeinfo_ptr),
|
|
790
|
+
invoke_closure(event_typeinfo_ptr)
|
|
791
|
+
]
|
|
792
|
+
|
|
793
|
+
vtable_ptr = Fiddle::Pointer.malloc(W::PTR_SIZE * SINK_VTBL_SLOTS)
|
|
794
|
+
closures.each_with_index { |c, i| vtable_ptr[i * W::PTR_SIZE, W::PTR_SIZE] = [c.to_i].pack(W::PACK_PTR) }
|
|
795
|
+
sink_ptr[0, W::PTR_SIZE] = [vtable_ptr.to_i].pack(W::PACK_PTR)
|
|
796
|
+
|
|
797
|
+
[sink_addr, vtable_ptr.to_i, closures]
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
end
|