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,827 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fiddle'
|
|
4
|
+
require 'win32ole/ffi/win32'
|
|
5
|
+
require 'win32ole/ffi/dispatch'
|
|
6
|
+
require 'win32ole/ffi/array'
|
|
7
|
+
require 'win32ole/ffi/record'
|
|
8
|
+
require 'win32ole/ffi/variant'
|
|
9
|
+
|
|
10
|
+
class WIN32OLE
|
|
11
|
+
class RuntimeError < ::RuntimeError; end
|
|
12
|
+
class QueryInterfaceError < RuntimeError; end
|
|
13
|
+
|
|
14
|
+
# ext/win32ole/win32ole_error.c keeps the old toplevel names as deprecated
|
|
15
|
+
# aliases, so existing scripts that use them still run here but get a
|
|
16
|
+
# warning to move to the WIN32OLE:: names.
|
|
17
|
+
::WIN32OLERuntimeError = RuntimeError
|
|
18
|
+
::Object.deprecate_constant(:WIN32OLERuntimeError)
|
|
19
|
+
|
|
20
|
+
::WIN32OLEQueryInterfaceError = QueryInterfaceError
|
|
21
|
+
::Object.deprecate_constant(:WIN32OLEQueryInterfaceError)
|
|
22
|
+
|
|
23
|
+
include Dispatch
|
|
24
|
+
|
|
25
|
+
W = Win32
|
|
26
|
+
private_constant :W
|
|
27
|
+
|
|
28
|
+
LOCALE_SYSTEM_DEFAULT = W::LOCALE_SYSTEM_DEFAULT
|
|
29
|
+
LOCALE_USER_DEFAULT = W::LOCALE_USER_DEFAULT
|
|
30
|
+
|
|
31
|
+
CP_ACP = W::CP_ACP
|
|
32
|
+
CP_OEMCP = W::CP_OEMCP
|
|
33
|
+
CP_MACCP = W::CP_MACCP
|
|
34
|
+
CP_THREAD_ACP = W::CP_THREAD_ACP
|
|
35
|
+
CP_SYMBOL = W::CP_SYMBOL
|
|
36
|
+
CP_UTF7 = W::CP_UTF7
|
|
37
|
+
CP_UTF8 = W::CP_UTF8
|
|
38
|
+
|
|
39
|
+
@lcid = LOCALE_SYSTEM_DEFAULT
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
# ext/win32ole/win32ole.c's fole_s_get_code_page / fole_s_set_code_page:
|
|
43
|
+
# the codepage strings coming back from COM are converted to (initially
|
|
44
|
+
# derived from Encoding.default_internal || default_external).
|
|
45
|
+
def codepage
|
|
46
|
+
W.codepage
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def codepage=(cp)
|
|
50
|
+
W.codepage = cp
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def locale
|
|
54
|
+
@lcid
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# ext/win32ole/win32ole.c's fole_s_set_locale: the two sentinel LCIDs
|
|
58
|
+
# are always accepted (they resolve dynamically at call time, so
|
|
59
|
+
# EnumSystemLocalesA can't confirm them up front); any other LCID must
|
|
60
|
+
# name an installed locale.
|
|
61
|
+
def locale=(lcid)
|
|
62
|
+
unless lcid == LOCALE_SYSTEM_DEFAULT || lcid == LOCALE_USER_DEFAULT || W.locale_installed?(lcid)
|
|
63
|
+
raise WIN32OLE::RuntimeError, "not installed locale: #{lcid}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@lcid = lcid
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ole_initialize (fole_s_ole_initialize): OleInitialize once per thread
|
|
71
|
+
# -- COM initialization is per OS thread, hence a thread variable, not
|
|
72
|
+
# a fiber-local Thread#[]. (C also installs an OLE message filter; this
|
|
73
|
+
# port doesn't.)
|
|
74
|
+
# Also runs the Releases finalizers left for a thread with OLE (on
|
|
75
|
+
# JRuby, all of them; see Win32.release_on_ole_thread).
|
|
76
|
+
def ole_initialize
|
|
77
|
+
unless Thread.current.thread_variable_get(:win32ole_ole_initialized)
|
|
78
|
+
hr = W.ole_initialize_fn.call(nil)
|
|
79
|
+
raise ::RuntimeError, "fail: OLE initialize\n#{hresult_detail(hr)}" if W.failed?(hr)
|
|
80
|
+
|
|
81
|
+
Thread.current.thread_variable_set(:win32ole_ole_initialized, true)
|
|
82
|
+
end
|
|
83
|
+
W.run_deferred_releases
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ole_uninitialize
|
|
88
|
+
return nil unless Thread.current.thread_variable_get(:win32ole_ole_initialized)
|
|
89
|
+
|
|
90
|
+
W.ole_uninitialize_fn.call
|
|
91
|
+
Thread.current.thread_variable_set(:win32ole_ole_initialized, nil)
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# fole_s_show_help: open +target+'s help -- a help file path, or a
|
|
96
|
+
# WIN32OLE::Type / WIN32OLE::Method's own help file at its context.
|
|
97
|
+
def ole_show_help(target, helpcontext = nil)
|
|
98
|
+
if target.is_a?(WIN32OLE::Type) || target.is_a?(WIN32OLE::Method)
|
|
99
|
+
helpfile = target.helpfile
|
|
100
|
+
raise ::RuntimeError, "no helpfile of `#{target.name}'" if helpfile.to_s.empty?
|
|
101
|
+
|
|
102
|
+
helpcontext = target.helpcontext
|
|
103
|
+
else
|
|
104
|
+
helpfile = target
|
|
105
|
+
end
|
|
106
|
+
raise TypeError, '1st parameter must be (String|WIN32OLE::Type|WIN32OLE::Method)' unless helpfile.is_a?(::String)
|
|
107
|
+
|
|
108
|
+
hwnd = W.show_html_help(helpfile, Integer(helpcontext))
|
|
109
|
+
raise ::RuntimeError, "failed to open help file `#{helpfile}'" if hwnd.zero?
|
|
110
|
+
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# fole_s_reference_count (a debugging aid): the IDispatch's reference
|
|
115
|
+
# count, read with an AddRef/Release pair.
|
|
116
|
+
def ole_reference_count(obj)
|
|
117
|
+
ptr = obj.dispatch_ptr
|
|
118
|
+
return 0 if ptr.nil? || ptr.zero?
|
|
119
|
+
|
|
120
|
+
W.add_ref(ptr)
|
|
121
|
+
release_com_pointer(ptr)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# fole_s_free (a debugging aid): one Release while a reference is
|
|
125
|
+
# left, returning the new count. Unlike WIN32OLE#ole_free, the object
|
|
126
|
+
# keeps its pointer.
|
|
127
|
+
def ole_free(obj)
|
|
128
|
+
ptr = obj.dispatch_ptr
|
|
129
|
+
return 0 if ptr.nil? || ptr.zero? || ole_reference_count(obj).zero?
|
|
130
|
+
|
|
131
|
+
release_com_pointer(ptr)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def wrap_dispatch_pointer(ptr)
|
|
135
|
+
obj = allocate
|
|
136
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
137
|
+
obj.send(:install_finalizer)
|
|
138
|
+
obj
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def connect(server, host = nil)
|
|
142
|
+
server = string_value(server)
|
|
143
|
+
raise NotImplementedError, 'remote OLE (host) is not supported yet' unless host.nil?
|
|
144
|
+
|
|
145
|
+
ole_initialize
|
|
146
|
+
|
|
147
|
+
# fole_s_connect: a name that is neither a ProgID nor a CLSID is
|
|
148
|
+
# bound as a moniker (e.g. "winmgmts:").
|
|
149
|
+
clsid = clsid_for(server)
|
|
150
|
+
return bind_moniker(server) if clsid.nil?
|
|
151
|
+
|
|
152
|
+
ppv = ("\x00" * W::PTR_SIZE).b
|
|
153
|
+
hr = W.get_active_object.call(clsid, nil, ppv)
|
|
154
|
+
if W.failed?(hr)
|
|
155
|
+
raise WIN32OLE::RuntimeError, "OLE server `#{server}' not running\n#{hresult_detail(hr)}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# GetActiveObject's out-parameter is IUnknown**, not IDispatch** --
|
|
159
|
+
# ext/win32ole/win32ole.c:1948-1957 QueryInterfaces for IID_IDispatch
|
|
160
|
+
# and releases the original IUnknown* before wrapping the result.
|
|
161
|
+
iunknown_ptr = ppv.unpack1(W::PACK_PTR)
|
|
162
|
+
idispatch_ptr = W.query_interface(iunknown_ptr, W::IID_IDISPATCH)
|
|
163
|
+
release_com_pointer(iunknown_ptr)
|
|
164
|
+
if idispatch_ptr.nil?
|
|
165
|
+
raise WIN32OLE::RuntimeError, W.unknown_server_error_message(server)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
wrap_dispatch_pointer(idispatch_ptr)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def release_com_pointer(ptr)
|
|
172
|
+
return if ptr.nil? || ptr.zero?
|
|
173
|
+
|
|
174
|
+
W.vtable_function(ptr, 2, [W::VOIDP], W::DWORD).call(ptr)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# +to_free+ collects a proc per allocation behind the VARIANT (C's
|
|
178
|
+
# VariantClear of it); call them once the VARIANT is no longer needed.
|
|
179
|
+
def ruby_value_to_variant_bytes(value, to_free)
|
|
180
|
+
case value
|
|
181
|
+
when ::Array
|
|
182
|
+
psa = SafeArray.ruby_array_to_safearray(value, W::VT_VARIANT, to_free)
|
|
183
|
+
to_free << -> { SafeArray.safe_array_destroy.call(psa) }
|
|
184
|
+
return W.pack_variant(W::VT_VARIANT | W::VT_ARRAY, W.pack_pointer(psa.to_i))
|
|
185
|
+
when WIN32OLE::Record
|
|
186
|
+
return value.to_variant_bytes
|
|
187
|
+
when WIN32OLE::Variant
|
|
188
|
+
# ole_variant2variant
|
|
189
|
+
copy = W.variant_copy(value.instance_variable_get(:@var))
|
|
190
|
+
to_free << -> { W.variant_clear(copy) }
|
|
191
|
+
return copy
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
type = W.ruby_to_variant_type(value)
|
|
195
|
+
payload =
|
|
196
|
+
case type
|
|
197
|
+
when :i4 then W.pack_i4(value)
|
|
198
|
+
when :i8 then W.pack_i8(value)
|
|
199
|
+
when :r8 then W.pack_r8(value)
|
|
200
|
+
when :bool then W.pack_bool(value)
|
|
201
|
+
when :empty then W.pack_empty
|
|
202
|
+
when :bstr
|
|
203
|
+
bstr = W.sys_alloc_string.call(W.wstr(value))
|
|
204
|
+
to_free << -> { W.sys_free_string.call(bstr) }
|
|
205
|
+
W.pack_pointer(bstr)
|
|
206
|
+
when :dispatch
|
|
207
|
+
ptr = value.instance_variable_get(:@ptr)
|
|
208
|
+
if ptr && !ptr.zero?
|
|
209
|
+
W.add_ref(ptr)
|
|
210
|
+
to_free << -> { release_com_pointer(ptr) }
|
|
211
|
+
end
|
|
212
|
+
W.pack_pointer(ptr)
|
|
213
|
+
when :date
|
|
214
|
+
W.pack_r8(W.time_to_vtdate(value))
|
|
215
|
+
end
|
|
216
|
+
W.pack_variant(W::VT_FOR_TYPE.fetch(type), payload)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# ole_variant2val. owned: true (a call's own result) hands its BSTR /
|
|
220
|
+
# IDispatch reference over to the Ruby value; owned: false (a
|
|
221
|
+
# Variant's own value, a SAFEARRAY element, anything behind VT_BYREF)
|
|
222
|
+
# leaves the VARIANT intact -- the BSTR is not freed and the IDispatch
|
|
223
|
+
# is AddRef'd before being wrapped.
|
|
224
|
+
def variant_bytes_to_ruby_value(bytes, owned: true)
|
|
225
|
+
vt, = W.unpack_variant(bytes)
|
|
226
|
+
if (vt & W::VT_BYREF) != 0
|
|
227
|
+
owned = false
|
|
228
|
+
bytes = W.deref_variant(bytes)
|
|
229
|
+
vt, = W.unpack_variant(bytes)
|
|
230
|
+
end
|
|
231
|
+
base_vt = vt & W::VT_TYPEMASK
|
|
232
|
+
|
|
233
|
+
if (vt & W::VT_ARRAY) != 0
|
|
234
|
+
_vt, payload = W.unpack_variant(bytes)
|
|
235
|
+
psa = W.unpack_pointer(payload)
|
|
236
|
+
return nil if psa.zero?
|
|
237
|
+
|
|
238
|
+
return SafeArray.safearray_to_ruby_array(psa, base_vt)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if base_vt == W::VT_RECORD
|
|
242
|
+
_vt, body = W.unpack_variant(bytes, body_size: WIN32OLE::Record::VT_RECORD_BODY_SIZE)
|
|
243
|
+
buffer_ptr, pri = body.unpack("#{W::PACK_PTR}2")
|
|
244
|
+
return WIN32OLE::Record.from_irecordinfo_and_buffer(pri, buffer_ptr)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
type = W.variant_ruby_type(vt)
|
|
248
|
+
_vt2, payload = W.unpack_variant(bytes)
|
|
249
|
+
case type
|
|
250
|
+
when :empty then nil
|
|
251
|
+
when :i4 then W.unpack_i4(payload)
|
|
252
|
+
when :i8 then W.unpack_i8(payload)
|
|
253
|
+
when :r8 then W.unpack_r8(payload)
|
|
254
|
+
when :bool then W.unpack_bool(payload)
|
|
255
|
+
when :i1 then W.unpack_i1(payload)
|
|
256
|
+
when :ui1 then W.unpack_ui1(payload)
|
|
257
|
+
when :i2 then W.unpack_i2(payload)
|
|
258
|
+
when :ui2 then W.unpack_ui2(payload)
|
|
259
|
+
when :ui4 then W.unpack_ui4(payload)
|
|
260
|
+
when :ui8 then W.unpack_ui8(payload)
|
|
261
|
+
when :int then W.unpack_int(payload)
|
|
262
|
+
when :uint then W.unpack_uint(payload)
|
|
263
|
+
when :r4 then W.unpack_r4(payload)
|
|
264
|
+
when :error then W.unpack_error(payload)
|
|
265
|
+
when :date then W.vtdate_to_time(W.unpack_r8(payload))
|
|
266
|
+
when :bstr
|
|
267
|
+
addr = W.unpack_pointer(payload)
|
|
268
|
+
str = W.bstr_to_s(addr) || '' # a NULL BSTR is "", as in ole_variant2val
|
|
269
|
+
W.sys_free_string.call(addr) if owned && !addr.zero?
|
|
270
|
+
str
|
|
271
|
+
when :dispatch
|
|
272
|
+
ptr = W.unpack_pointer(payload)
|
|
273
|
+
unless ptr.zero?
|
|
274
|
+
W.add_ref(ptr) unless owned
|
|
275
|
+
wrap_dispatch_pointer(ptr)
|
|
276
|
+
end
|
|
277
|
+
when :other then variant_to_bstr_value(bytes)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# ole_variant2val's default branch (VT_CY, VT_DECIMAL, ...): the
|
|
282
|
+
# VariantChangeTypeEx(VT_BSTR) rendering, or nil if it can't convert.
|
|
283
|
+
def variant_to_bstr_value(bytes)
|
|
284
|
+
dest = ("\x00" * W::VARIANT_SIZE).b
|
|
285
|
+
hr = W.variant_change_type.call(dest, bytes, locale, 0, W::VT_BSTR)
|
|
286
|
+
return nil if W.failed?(hr)
|
|
287
|
+
|
|
288
|
+
addr = W.unpack_pointer(W.unpack_variant(dest)[1])
|
|
289
|
+
str = W.bstr_to_s(addr) || ''
|
|
290
|
+
W.sys_free_string.call(addr) unless addr.zero?
|
|
291
|
+
str
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def resolve_clsid(server)
|
|
295
|
+
clsid, hr = clsid_and_hr(server)
|
|
296
|
+
return clsid if clsid
|
|
297
|
+
|
|
298
|
+
raise WIN32OLE::RuntimeError, "#{W.unknown_server_error_message(server)}\n#{hresult_detail(hr)}"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# The CLSID for a ProgID or "{...}" CLSID string, or nil.
|
|
302
|
+
def clsid_for(server)
|
|
303
|
+
clsid_and_hr(server).first
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def clsid_and_hr(server)
|
|
307
|
+
wide = W.wstr(server)
|
|
308
|
+
clsid = ("\x00" * 16).b
|
|
309
|
+
hr = W.clsid_from_progid.call(wide, clsid)
|
|
310
|
+
# ole_create_dcom tries the remote host's registry in between.
|
|
311
|
+
if W.failed?(hr) && block_given? && (remote = yield)
|
|
312
|
+
return [remote, 0]
|
|
313
|
+
end
|
|
314
|
+
hr = W.clsid_from_string.call(wide, clsid) if W.failed?(hr)
|
|
315
|
+
[W.failed?(hr) ? nil : clsid, hr]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
IMONIKER_BIND_TO_OBJECT = 8 # IUnknown(3) + IPersist(1) + IPersistStream(4)
|
|
319
|
+
|
|
320
|
+
# ole_bind_obj: CreateBindCtx + MkParseDisplayName + IMoniker::BindToObject
|
|
321
|
+
# for IDispatch.
|
|
322
|
+
def bind_moniker(moniker)
|
|
323
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
324
|
+
hr = W.create_bind_ctx.call(0, out)
|
|
325
|
+
raise WIN32OLE::RuntimeError, "failed to create bind context\n#{hresult_detail(hr)}" if W.failed?(hr)
|
|
326
|
+
|
|
327
|
+
bindctx = out.unpack1(W::PACK_PTR)
|
|
328
|
+
begin
|
|
329
|
+
eaten = ("\x00" * 4).b
|
|
330
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
331
|
+
hr = W.mk_parse_display_name.call(bindctx, W.wstr(moniker), eaten, out)
|
|
332
|
+
if W.failed?(hr)
|
|
333
|
+
raise WIN32OLE::RuntimeError, "failed to parse display name of moniker `#{moniker}'\n#{hresult_detail(hr)}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
pmoniker = out.unpack1(W::PACK_PTR)
|
|
337
|
+
begin
|
|
338
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
339
|
+
hr = W.vtable_function(pmoniker, IMONIKER_BIND_TO_OBJECT, [W::VOIDP] * 5, W::LONG)
|
|
340
|
+
.call(pmoniker, bindctx, nil, W::IID_IDISPATCH, out)
|
|
341
|
+
raise WIN32OLE::RuntimeError, "failed to bind moniker `#{moniker}'\n#{hresult_detail(hr)}" if W.failed?(hr)
|
|
342
|
+
ensure
|
|
343
|
+
release_com_pointer(pmoniker)
|
|
344
|
+
end
|
|
345
|
+
ensure
|
|
346
|
+
release_com_pointer(bindctx)
|
|
347
|
+
end
|
|
348
|
+
wrap_dispatch_pointer(out.unpack1(W::PACK_PTR))
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def hresult_detail(hr)
|
|
352
|
+
" HRESULT error code:#{W.hr_hex(hr)}\n #{W.hresult_system_message(hr)}"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def create_guid
|
|
356
|
+
buf = ("\x00" * 16).b
|
|
357
|
+
hr = W.co_create_guid.call(buf)
|
|
358
|
+
raise WIN32OLE::RuntimeError, "failed to create GUID\n#{hresult_detail(hr)}" if W.failed?(hr)
|
|
359
|
+
|
|
360
|
+
d1, d2, d3 = buf.unpack('LSS')
|
|
361
|
+
d4 = buf[8, 8].unpack('C8')
|
|
362
|
+
format('{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}', d1, d2, d3, *d4)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# ext/win32ole/win32ole.c's fole_s_const_load / ole_const_load. +ole+ is
|
|
366
|
+
# a WIN32OLE (its containing typelib) or a String naming a typelib the
|
|
367
|
+
# way typelib_file resolves it; +mod+ defaults to WIN32OLE itself. Like
|
|
368
|
+
# C, each visible constant's first letter is ASCII-upcased
|
|
369
|
+
# (ssfWINDOWS -> SsfWINDOWS); names that still aren't valid constant
|
|
370
|
+
# names go into a CONSTANTS hash on +mod+ instead.
|
|
371
|
+
def const_load(ole, mod = nil)
|
|
372
|
+
raise TypeError, '2nd parameter must be Class or Module' unless mod.nil? || mod.is_a?(::Module)
|
|
373
|
+
|
|
374
|
+
mod ||= self
|
|
375
|
+
tlib =
|
|
376
|
+
case ole
|
|
377
|
+
when WIN32OLE then ole.ole_type.ole_typelib
|
|
378
|
+
when ::String
|
|
379
|
+
ptr = WIN32OLE::TypeLib.load_typelib(WIN32OLE::TypeLib.file_for(ole) || ole)
|
|
380
|
+
raise WIN32OLE::RuntimeError, 'failed to LoadTypeLibEx' if ptr.nil?
|
|
381
|
+
|
|
382
|
+
WIN32OLE::TypeLib.from_itypelib_ptr(ptr)
|
|
383
|
+
else
|
|
384
|
+
raise TypeError, '1st parameter must be WIN32OLE instance'
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
constants = {}
|
|
388
|
+
tlib.ole_types.each do |type|
|
|
389
|
+
type.variables.each do |var|
|
|
390
|
+
next unless var.variable_kind == 'CONSTANT' && var.visible?
|
|
391
|
+
|
|
392
|
+
name = var.name.dup
|
|
393
|
+
name[0] = name[0].upcase(:ascii)
|
|
394
|
+
if name.match?(/\A[A-Z]\w*\z/)
|
|
395
|
+
mod.const_set(name, var.value) unless mod.const_defined?(name, false)
|
|
396
|
+
else
|
|
397
|
+
constants[name] = var.value
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
mod.const_set(:CONSTANTS, constants)
|
|
402
|
+
nil
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Ruby-level StringValue: a String, or anything with an implicit
|
|
406
|
+
# #to_str, else TypeError -- which is what ext/win32ole raises for a
|
|
407
|
+
# non-string server/host/method name.
|
|
408
|
+
def string_value(val)
|
|
409
|
+
String.try_convert(val) || raise(TypeError, "no implicit conversion of #{val.class} into String")
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
private :resolve_clsid, :clsid_for, :clsid_and_hr, :bind_moniker, :hresult_detail, :release_com_pointer,
|
|
413
|
+
:string_value
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def initialize(server, host = nil)
|
|
417
|
+
server = self.class.send(:string_value, server)
|
|
418
|
+
self.class.ole_initialize
|
|
419
|
+
return create_dcom(server, self.class.send(:string_value, host)) unless host.nil?
|
|
420
|
+
|
|
421
|
+
clsid = self.class.send(:resolve_clsid, server)
|
|
422
|
+
ppv = ("\x00" * W::PTR_SIZE).b
|
|
423
|
+
hr = W.co_create_instance.call(
|
|
424
|
+
clsid, nil, W::CLSCTX_INPROC_SERVER | W::CLSCTX_LOCAL_SERVER, W::IID_IDISPATCH, ppv
|
|
425
|
+
)
|
|
426
|
+
if W.failed?(hr)
|
|
427
|
+
raise WIN32OLE::RuntimeError, "#{W.unknown_server_error_message(server)}\n#{self.class.send(:hresult_detail, hr)}"
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
@ptr = ppv.unpack1(W::PTR_SIZE == 8 ? 'Q' : 'L')
|
|
431
|
+
install_finalizer
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
private
|
|
435
|
+
|
|
436
|
+
# ole_create_dcom: the CLSID from the ProgID here, then from the host's
|
|
437
|
+
# registry, then as a CLSID string; the object from CoCreateInstanceEx
|
|
438
|
+
# on +host+.
|
|
439
|
+
def create_dcom(server, host)
|
|
440
|
+
clsid, hr = self.class.send(:clsid_and_hr, server) { W.clsid_from_remote(host, server) }
|
|
441
|
+
raise WIN32OLE::RuntimeError, "#{W.unknown_server_error_message(server)}\n#{self.class.send(:hresult_detail, hr)}" unless clsid
|
|
442
|
+
|
|
443
|
+
# The pointers, not just their addresses, are held until the call: on
|
|
444
|
+
# JRuby to_ptr copies a String into native memory that is freed once its
|
|
445
|
+
# Fiddle::Pointer is collected.
|
|
446
|
+
host_name = W.wstr(host)
|
|
447
|
+
host_name_ptr = W.native_pointer_for(host_name)
|
|
448
|
+
# COSERVERINFO { DWORD dwReserved1; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; }
|
|
449
|
+
info = [0, host_name_ptr.to_i, 0, 0].pack("L#{'x4' if W::PTR_SIZE == 8}#{W::PACK_PTR}#{W::PACK_PTR}L#{'x4' if W::PTR_SIZE == 8}")
|
|
450
|
+
iid = W::IID_IDISPATCH.dup
|
|
451
|
+
iid_ptr = W.native_pointer_for(iid)
|
|
452
|
+
# MULTI_QI { const IID *pIID; IUnknown *pItf; HRESULT hr; }
|
|
453
|
+
mqi = [iid_ptr.to_i, 0, 0].pack("#{W::PACK_PTR}#{W::PACK_PTR}l#{'x4' if W::PTR_SIZE == 8}")
|
|
454
|
+
hr = W.co_create_instance_ex.call(clsid, nil, W::CLSCTX_REMOTE_SERVER, info, 1, mqi)
|
|
455
|
+
if W.failed?(hr)
|
|
456
|
+
raise WIN32OLE::RuntimeError,
|
|
457
|
+
"failed to create DCOM server `#{server}' in `#{host}'\n#{self.class.send(:hresult_detail, hr)}"
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
@ptr = mqi[W::PTR_SIZE, W::PTR_SIZE].unpack1(W::PACK_PTR)
|
|
461
|
+
install_finalizer
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def install_finalizer
|
|
465
|
+
ptr = @ptr
|
|
466
|
+
release_fn = W.vtable_function(ptr, 2, [W::VOIDP], W::DWORD)
|
|
467
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(ptr, release_fn))
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# OLE_FREE: Release only on a thread that has initialized OLE. GC can run
|
|
471
|
+
# this on any thread, e.g. the main thread at exit after the apartment the
|
|
472
|
+
# object lived in is gone; then a later thread with OLE does it. On JRuby
|
|
473
|
+
# that is every time: it runs all finalizers on a thread of its own, which
|
|
474
|
+
# never has OLE.
|
|
475
|
+
def self.finalizer(ptr, release_fn)
|
|
476
|
+
proc { W.release_on_ole_thread { release_fn.call(ptr) } unless ptr.zero? }
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
DISP_E_EXCEPTION = -2147352567 # 0x80020009
|
|
480
|
+
|
|
481
|
+
# A Symbol (the normal path) or a String is accepted, as fole_missing's
|
|
482
|
+
# rb_check_symbol + StringValueCStr does; anything else is a TypeError.
|
|
483
|
+
def method_missing(name, *args)
|
|
484
|
+
mname = name.is_a?(::Symbol) ? name.to_s : self.class.send(:string_value, name)
|
|
485
|
+
plan = W.dispatch_plan(mname, args)
|
|
486
|
+
dispid = dispid_for(plan[:name])
|
|
487
|
+
if dispid.nil?
|
|
488
|
+
if plan[:named_put]
|
|
489
|
+
raise WIN32OLE::RuntimeError, W.property_put_error_message(plan[:name], "\nunknown property or method: `#{plan[:name]}'")
|
|
490
|
+
end
|
|
491
|
+
return super(name, *args)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
args, named_args = split_named_args(plan[:name], args) unless plan[:named_put]
|
|
495
|
+
hr, result_bytes, excepinfo_bytes =
|
|
496
|
+
ole_invoke(dispid, args, plan[:wflags], named_put: plan[:named_put], named_args: named_args)
|
|
497
|
+
|
|
498
|
+
if W.failed?(hr)
|
|
499
|
+
detail = error_detail(hr, excepinfo_bytes)
|
|
500
|
+
message = plan[:named_put] ? W.property_put_error_message(plan[:name], detail)
|
|
501
|
+
: W.method_error_message(plan[:name], detail)
|
|
502
|
+
raise WIN32OLE::RuntimeError, message
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
return nil if plan[:named_put]
|
|
506
|
+
|
|
507
|
+
self.class.variant_bytes_to_ruby_value(result_bytes)
|
|
508
|
+
end
|
|
509
|
+
# Public, as rb_define_method makes fole_missing.
|
|
510
|
+
public :method_missing
|
|
511
|
+
|
|
512
|
+
def respond_to_missing?(name, include_private = false)
|
|
513
|
+
!dispid_for(name.to_s.sub(/=\z/, '')).nil? || super
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
public
|
|
517
|
+
|
|
518
|
+
# ext/win32ole/win32ole.c's fole_invoke: the explicit-call form of
|
|
519
|
+
# method_missing's regular (non property-put) path -- lets a caller
|
|
520
|
+
# invoke a method whose name collides with a Ruby method (Object#send
|
|
521
|
+
# works too, but this matches MRI's documented API).
|
|
522
|
+
def invoke(name, *args)
|
|
523
|
+
dispid = dispid_for(name.to_s)
|
|
524
|
+
return super if dispid.nil?
|
|
525
|
+
|
|
526
|
+
args, named_args = split_named_args(name.to_s, args)
|
|
527
|
+
hr, result_bytes, excepinfo_bytes =
|
|
528
|
+
ole_invoke(dispid, args, W::DISPATCH_METHOD | W::DISPATCH_PROPERTYGET, named_args: named_args)
|
|
529
|
+
|
|
530
|
+
if W.failed?(hr)
|
|
531
|
+
raise WIN32OLE::RuntimeError, W.method_error_message(name, error_detail(hr, excepinfo_bytes))
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
self.class.variant_bytes_to_ruby_value(result_bytes)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# ext/win32ole/win32ole.c's fole_invoke2/fole_getproperty2/fole_setproperty2:
|
|
538
|
+
# the "early binding" siblings of #invoke/#setproperty -- dispid and
|
|
539
|
+
# per-argument VARTYPE are given explicitly instead of resolved by name,
|
|
540
|
+
# so no GetIDsOfNames round-trip happens.
|
|
541
|
+
def _invoke(dispid, args, types)
|
|
542
|
+
ole_invoke2(dispid, args, types, W::DISPATCH_METHOD)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def _getproperty(dispid, args, types)
|
|
546
|
+
ole_invoke2(dispid, args, types, W::DISPATCH_PROPERTYGET)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def _setproperty(dispid, args, types)
|
|
550
|
+
ole_invoke2(dispid, args, types, W::DISPATCH_PROPERTYPUT)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# ext/win32ole/win32ole.c's fole_setproperty: like the `name=(val)`
|
|
554
|
+
# method_missing path, but for properties that also take index
|
|
555
|
+
# arguments (e.g. sheet.setproperty('Cells', 1, 2, 10)) -- the last
|
|
556
|
+
# argument is the value, everything before it is an index arg, and (per
|
|
557
|
+
# ole_invoke) they're all invoked as one DISPATCH_PROPERTYPUT call with
|
|
558
|
+
# the value carried as the DISPID_PROPERTYPUT named argument.
|
|
559
|
+
def setproperty(name, *args)
|
|
560
|
+
if args.empty?
|
|
561
|
+
raise WIN32OLE::RuntimeError, W.property_put_error_message(name, "\nargument error")
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
dispid = dispid_for(name.to_s)
|
|
565
|
+
return super if dispid.nil?
|
|
566
|
+
|
|
567
|
+
hr, _result_bytes, excepinfo_bytes = ole_invoke(dispid, args, W::DISPATCH_PROPERTYPUT, named_put: true)
|
|
568
|
+
|
|
569
|
+
if W.failed?(hr)
|
|
570
|
+
raise WIN32OLE::RuntimeError, W.property_put_error_message(name, error_detail(hr, excepinfo_bytes))
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
nil
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# ext/win32ole/win32ole.c's fole_each: gets the collection's enumerator
|
|
577
|
+
# through DISPID_NEWENUM, QueryInterfaces it for IEnumVARIANT, and yields
|
|
578
|
+
# each item IEnumVARIANT::Next returns until it stops returning S_OK.
|
|
579
|
+
def each
|
|
580
|
+
return to_enum(:each) unless block_given?
|
|
581
|
+
|
|
582
|
+
hr, result_bytes, = ole_invoke(W::DISPID_NEWENUM, [], W::DISPATCH_METHOD | W::DISPATCH_PROPERTYGET)
|
|
583
|
+
if W.failed?(hr)
|
|
584
|
+
raise WIN32OLE::QueryInterfaceError, "failed to get IEnum Interface\n#{self.class.send(:hresult_detail, hr)}"
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
vt, payload = W.unpack_variant(result_bytes)
|
|
588
|
+
unknown = [W::VT_UNKNOWN, W::VT_DISPATCH].include?(vt) ? W.unpack_pointer(payload) : 0
|
|
589
|
+
raise WIN32OLE::QueryInterfaceError, 'failed to get IEnum Interface' if unknown.zero?
|
|
590
|
+
|
|
591
|
+
enum = W.query_interface(unknown, W::IID_IENUMVARIANT)
|
|
592
|
+
self.class.send(:release_com_pointer, unknown)
|
|
593
|
+
raise WIN32OLE::QueryInterfaceError, 'failed to get IEnum Interface' if enum.nil?
|
|
594
|
+
|
|
595
|
+
begin
|
|
596
|
+
next_fn = W.vtable_function(enum, 3, [W::VOIDP, W::DWORD, W::VOIDP, W::VOIDP], W::LONG)
|
|
597
|
+
loop do
|
|
598
|
+
var = ("\x00" * W::VARIANT_SIZE).b
|
|
599
|
+
break unless next_fn.call(enum, 1, var, nil).zero? # S_OK; S_FALSE (1) = no more
|
|
600
|
+
|
|
601
|
+
yield self.class.variant_bytes_to_ruby_value(var)
|
|
602
|
+
end
|
|
603
|
+
ensure
|
|
604
|
+
self.class.send(:release_com_pointer, enum)
|
|
605
|
+
end
|
|
606
|
+
nil
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# ext/win32ole/win32ole.c's fole_getproperty_with_bracket: invokes the
|
|
610
|
+
# object's default member (DISPID_VALUE) with the given arguments --
|
|
611
|
+
# e.g. dict['ruby'] on a Scripting.Dictionary, whose default property
|
|
612
|
+
# is Item. No name lookup happens here (unlike method_missing/#invoke):
|
|
613
|
+
# all arguments are passed straight through to DISPID_VALUE.
|
|
614
|
+
def [](*args)
|
|
615
|
+
hr, result_bytes, excepinfo_bytes = ole_invoke(W::DISPID_VALUE, args, W::DISPATCH_PROPERTYGET)
|
|
616
|
+
if W.failed?(hr)
|
|
617
|
+
raise WIN32OLE::RuntimeError, W.method_error_message(args.first, error_detail(hr, excepinfo_bytes))
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
self.class.variant_bytes_to_ruby_value(result_bytes)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# ext/win32ole/win32ole.c's fole_setproperty_with_bracket: same
|
|
624
|
+
# DISPID_VALUE target as #[], but DISPATCH_PROPERTYPUT -- the last
|
|
625
|
+
# argument is the value, everything before it is an index argument
|
|
626
|
+
# (e.g. dict[2] = 'TWO').
|
|
627
|
+
def []=(*args)
|
|
628
|
+
hr, _result_bytes, excepinfo_bytes = ole_invoke(W::DISPID_VALUE, args, W::DISPATCH_PROPERTYPUT, named_put: true)
|
|
629
|
+
if W.failed?(hr)
|
|
630
|
+
raise WIN32OLE::RuntimeError, W.property_put_error_message(args.first, error_detail(hr, excepinfo_bytes))
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
args.last
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
IPERSISTMEMORY_INIT_NEW = 8 # IUnknown(3) + IPersist(1) + IsDirty/Load/Save/GetSizeMax
|
|
637
|
+
|
|
638
|
+
# fole_activex_initialize: initialize an ActiveX control through its
|
|
639
|
+
# IPersistMemory::InitNew.
|
|
640
|
+
def ole_activex_initialize
|
|
641
|
+
persist = W.query_interface(@ptr, W::IID_IPERSISTMEMORY)
|
|
642
|
+
hr = -2147467262 # E_NOINTERFACE
|
|
643
|
+
if persist
|
|
644
|
+
begin
|
|
645
|
+
hr = W.vtable_function(persist, IPERSISTMEMORY_INIT_NEW, [W::VOIDP], W::LONG).call(persist)
|
|
646
|
+
ensure
|
|
647
|
+
self.class.send(:release_com_pointer, persist)
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
if W.failed?(hr)
|
|
651
|
+
raise WIN32OLE::RuntimeError, "fail to initialize ActiveX control\n#{self.class.send(:hresult_detail, hr)}"
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
nil
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# fole_free: Release the IDispatch now instead of at GC time.
|
|
658
|
+
def ole_free
|
|
659
|
+
release
|
|
660
|
+
nil
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
# WIN32OLE::Event needs this raw IDispatch* to build its own COM
|
|
664
|
+
# connections (QueryInterface for IConnectionPointContainer,
|
|
665
|
+
# IProvideClassInfo2, etc.) -- #ole_query_interface wraps whatever it
|
|
666
|
+
# gets back as a WIN32OLE, which is not what Event needs, so this is
|
|
667
|
+
# Event's one deliberate, documented crack in encapsulation.
|
|
668
|
+
def dispatch_ptr
|
|
669
|
+
@ptr
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def ole_type
|
|
673
|
+
type_info_ptr = get_type_info_ptr
|
|
674
|
+
raise WIN32OLE::QueryInterfaceError, 'failed to GetTypeInfo' if type_info_ptr.nil?
|
|
675
|
+
|
|
676
|
+
Type.from_typeinfo_ptr(type_info_ptr)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def ole_typelib
|
|
680
|
+
ole_type.ole_typelib
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def ole_methods
|
|
684
|
+
ole_methods_by_invkind(nil)
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def ole_get_methods
|
|
688
|
+
ole_methods_by_invkind(TypeInfo::INVOKE_PROPERTYGET)
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def ole_put_methods
|
|
692
|
+
ole_methods_by_invkind(TypeInfo::INVOKE_PROPERTYPUT | TypeInfo::INVOKE_PROPERTYPUTREF)
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def ole_func_methods
|
|
696
|
+
ole_methods_by_invkind(TypeInfo::INVOKE_FUNC)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
def ole_respond_to?(name)
|
|
700
|
+
!dispid_for(name.to_s).nil?
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# fole_method_help: the WIN32OLE::Method named +name+ on this object's
|
|
704
|
+
# type. (ole_method_help is an alias of it, and ole_obj_help one of
|
|
705
|
+
# ole_type -- neither launches a help file; only WIN32OLE.ole_show_help
|
|
706
|
+
# does.)
|
|
707
|
+
def ole_method(name)
|
|
708
|
+
WIN32OLE::Method.new(type_via_containing_typelib, self.class.send(:string_value, name))
|
|
709
|
+
end
|
|
710
|
+
alias ole_method_help ole_method
|
|
711
|
+
alias ole_obj_help ole_type
|
|
712
|
+
|
|
713
|
+
# ext/win32ole/win32ole.c's fole_query_interface: like C, the returned
|
|
714
|
+
# interface pointer is wrapped as a WIN32OLE (i.e. treated as IDispatch*).
|
|
715
|
+
def ole_query_interface(iid)
|
|
716
|
+
iid = self.class.send(:string_value, iid)
|
|
717
|
+
raise WIN32OLE::RuntimeError, 'this WIN32OLE object has already been released' if @ptr.nil? || @ptr.zero?
|
|
718
|
+
|
|
719
|
+
iid_bytes = ("\x00" * 16).b
|
|
720
|
+
hr = W.clsid_from_string.call(W.wstr(iid), iid_bytes)
|
|
721
|
+
if W.failed?(hr)
|
|
722
|
+
raise WIN32OLE::RuntimeError, "invalid iid: `#{iid}'\n#{self.class.send(:hresult_detail, hr)}"
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
ppv = ("\x00" * W::PTR_SIZE).b
|
|
726
|
+
hr = W.vtable_function(@ptr, 0, [W::VOIDP, W::VOIDP, W::VOIDP], W::LONG).call(@ptr, iid_bytes, ppv)
|
|
727
|
+
if W.failed?(hr)
|
|
728
|
+
raise WIN32OLE::QueryInterfaceError, "failed to get interface `#{iid}'\n#{self.class.send(:hresult_detail, hr)}"
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
self.class.wrap_dispatch_pointer(ppv.unpack1(W::PACK_PTR))
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
private
|
|
735
|
+
|
|
736
|
+
# ext/win32ole/win32ole.c's ole_invoke2: builds one DISPPARAMS entry per
|
|
737
|
+
# (value, VARTYPE) pair -- VT_VARIANT means "pass as its own natural
|
|
738
|
+
# type" (no coercion), matching C's skip of VariantChangeTypeEx for that
|
|
739
|
+
# case; anything else is packed via WIN32OLE::Variant.new(val, vt),
|
|
740
|
+
# which already implements the explicit-VARTYPE coercion (including
|
|
741
|
+
# VT_ARRAY/VT_BYREF) that this needs. The Variant objects are kept alive
|
|
742
|
+
# in keep_alive through the Invoke call, since VT_BYREF's backing buffer
|
|
743
|
+
# is otherwise only referenced from their own instance state.
|
|
744
|
+
def ole_invoke2(dispid, args, types, dispkind)
|
|
745
|
+
raise WIN32OLE::RuntimeError, 'this WIN32OLE object has already been released' if @ptr.nil? || @ptr.zero?
|
|
746
|
+
raise TypeError, 'wrong argument type (expected Array)' unless args.is_a?(::Array) && types.is_a?(::Array)
|
|
747
|
+
|
|
748
|
+
to_free = []
|
|
749
|
+
keep_alive = []
|
|
750
|
+
arg_variants = args.zip(types).map do |val, vt|
|
|
751
|
+
if vt == W::VT_VARIANT
|
|
752
|
+
WIN32OLE.ruby_value_to_variant_bytes(val, to_free)
|
|
753
|
+
else
|
|
754
|
+
variant = WIN32OLE::Variant.new(val, vt)
|
|
755
|
+
keep_alive << variant
|
|
756
|
+
variant.instance_variable_get(:@var)
|
|
757
|
+
end
|
|
758
|
+
end.reverse
|
|
759
|
+
args_blob = arg_variants.join
|
|
760
|
+
args_ptr = args_blob.empty? ? nil : W.native_pointer_for(args_blob)
|
|
761
|
+
|
|
762
|
+
named_put = (dispkind & W::DISPATCH_PROPERTYPUT) != 0
|
|
763
|
+
named_blob = named_put ? [W::DISPID_PROPERTYPUT].pack('l') : ''
|
|
764
|
+
named_ptr = named_blob.empty? ? nil : W.native_pointer_for(named_blob)
|
|
765
|
+
|
|
766
|
+
dispparams = [
|
|
767
|
+
args_ptr ? args_ptr.to_i : 0,
|
|
768
|
+
named_ptr ? named_ptr.to_i : 0,
|
|
769
|
+
arg_variants.size,
|
|
770
|
+
named_put ? 1 : 0
|
|
771
|
+
].pack("#{W::PACK_PTR}#{W::PACK_PTR}LL")
|
|
772
|
+
|
|
773
|
+
excepinfo = ("\x00" * W::EXCEPINFO_SIZE).b
|
|
774
|
+
result = ("\x00" * W::VARIANT_SIZE).b
|
|
775
|
+
|
|
776
|
+
hr = invoke_fn.call(@ptr, dispid, W::IID_NULL, 0, dispkind, dispparams, result, excepinfo, nil)
|
|
777
|
+
to_free.each(&:call)
|
|
778
|
+
|
|
779
|
+
if W.failed?(hr)
|
|
780
|
+
raise WIN32OLE::RuntimeError, W.method_error_message("<dispatch id:#{dispid}>", error_detail(hr, excepinfo))
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
self.class.variant_bytes_to_ruby_value(result)
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
def error_detail(hr, excepinfo_bytes)
|
|
787
|
+
if hr == DISP_E_EXCEPTION
|
|
788
|
+
info = W.parse_excepinfo(excepinfo_bytes)
|
|
789
|
+
source = W.bstr_to_s(info[:bstr_source_ptr]) || '<Unknown>'
|
|
790
|
+
description = W.bstr_to_s(info[:bstr_description_ptr]) || '<No Description>'
|
|
791
|
+
code = info[:w_code].zero? ? (info[:scode] & 0xFFFFFFFF).to_s(16).upcase : info[:w_code].to_s
|
|
792
|
+
"\n OLE error code:#{code} in #{source}\n #{description}\n#{self.class.send(:hresult_detail, hr)}"
|
|
793
|
+
else
|
|
794
|
+
"\n#{self.class.send(:hresult_detail, hr)}"
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
def get_type_info_ptr
|
|
799
|
+
out = ("\x00" * W::PTR_SIZE).b
|
|
800
|
+
hr = TypeInfo.get_type_info_fn(@ptr).call(@ptr, 0, W::LOCALE_SYSTEM_DEFAULT, out)
|
|
801
|
+
return nil if W.failed?(hr)
|
|
802
|
+
|
|
803
|
+
out.unpack1(W::PACK_PTR)
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
def ole_methods_by_invkind(mask)
|
|
807
|
+
type_via_containing_typelib.ole_methods.select do |m|
|
|
808
|
+
mask.nil? || (m.invkind & mask) != 0
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
# Ports ext/win32ole/win32ole.c's typeinfo_from_ole: GetTypeInfo →
|
|
813
|
+
# GetDocumentation (this type's own name) → GetContainingTypeLib →
|
|
814
|
+
# scan the typelib for the entry with a matching name → GetTypeInfo(i)
|
|
815
|
+
# again. Why MRI does this round-trip instead of just reusing the first
|
|
816
|
+
# ITypeInfo* the way #ole_type does is not confirmed (most likely
|
|
817
|
+
# GetTypeInfo(0, ...) can return a coclass's type info rather than the
|
|
818
|
+
# dispinterface's) — ported as-is rather than "simplified" without
|
|
819
|
+
# understanding it.
|
|
820
|
+
def type_via_containing_typelib
|
|
821
|
+
first_type = ole_type
|
|
822
|
+
target_name = first_type.name
|
|
823
|
+
tlib = first_type.ole_typelib
|
|
824
|
+
match = tlib.ole_types.find { |t| t.name == target_name }
|
|
825
|
+
match || first_type
|
|
826
|
+
end
|
|
827
|
+
end
|