win32ole-ffi 0.1.0-java

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b7fb3f46aa0e372664dbdc9b634c6b6642270e22feef177cd17ae39d7806304
4
+ data.tar.gz: 03d21f965a0a42f0d2d8f586428f9cae2d9b61fa94248f2bf02354b251188fdd
5
+ SHA512:
6
+ metadata.gz: 9fce08276f23ee1619d16463c9986918b6bcbf42bbea37b842d0b060817699e27843d6b5b1db64a30d7f23d434671504d681b0108ffe156ac68a4c757db90040
7
+ data.tar.gz: efc8091bd2528e4530fa03d4e7d61c9ee4492e325fe78e8ae4659222a91f2393cae5e29288331abaf0a3a6b6394187e04bf21b32c41e64df1e4f77aada544bd7
data/BSDL ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release: WIN32OLE and WIN32OLE::Type, TypeLib, Method, Param,
6
+ Variable, Variant, Record and Event in pure Ruby with Fiddle, tested on
7
+ Windows with CRuby 2.6+ (mswin/mingw) and JRuby 9.4+.
8
+ - The deprecated toplevel names (WIN32OLE_TYPE, WIN32OLERuntimeError, ...)
9
+ as aliases, as in ruby/win32ole.
10
+ - RBS signatures under `sig/`.
data/COPYING ADDED
@@ -0,0 +1,56 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the
3
+ 2-clause BSDL (see the file BSDL), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a. place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b. use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c. give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d. make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a. distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b. accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c. give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d. make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # win32ole-ffi
2
+
3
+ WIN32OLE implemented in pure Ruby with Fiddle, without a C extension, so
4
+ that it also runs on JRuby. It works on Windows only, and is tested with
5
+ CRuby 2.6+ (mswin and mingw) and JRuby 9.4+.
6
+
7
+ It provides the same API as [ruby/win32ole](https://github.com/ruby/win32ole)
8
+ (`WIN32OLE`, `WIN32OLE::Type`, `WIN32OLE::Event`, ...) and runs that gem's
9
+ test suite. Use one or the other: both define the `WIN32OLE` class, so do
10
+ not load `win32ole` and `win32ole/ffi` in the same process.
11
+
12
+ ## Installation
13
+
14
+ ```ruby
15
+ gem 'win32ole-ffi'
16
+ ```
17
+
18
+ The gem depends on the `fiddle` gem on CRuby and on the `ffi` gem (which
19
+ JRuby's Fiddle runs on) on JRuby.
20
+
21
+ ## Usage
22
+
23
+ Require `win32ole/ffi` where you would require `win32ole`:
24
+
25
+ ```ruby
26
+ require 'win32ole/ffi'
27
+
28
+ dict = WIN32OLE.new('Scripting.Dictionary')
29
+ dict.add('ruby', 'Ruby')
30
+ dict.item('ruby') #=> "Ruby"
31
+ ```
32
+
33
+ `WIN32OLE::Property`, the helper that ruby/win32ole's `olegen.rb`-generated
34
+ code uses, is not included.
35
+
36
+ ## Development
37
+
38
+ $ git clone --recurse-submodules https://github.com/eitoball/win32ole-ffi
39
+ $ bundle install
40
+ $ bundle exec rake test
41
+
42
+ `rake test` also runs ruby/win32ole's tests from the `vendor/win32ole`
43
+ submodule. Before that it applies `patches/*.patch` to the submodule's
44
+ working tree; `rake test:unpatch` reverts them.
45
+
46
+ ## License
47
+
48
+ The same as ruby/win32ole: Ruby's license or BSD-2-Clause (see `COPYING`
49
+ and `BSDL`).
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiddle'
4
+ require 'win32ole/ffi/win32'
5
+
6
+ class WIN32OLE
7
+ module SafeArray
8
+ W = Win32
9
+ private_constant :W
10
+
11
+ module_function
12
+
13
+ def oleaut32
14
+ W.oleaut32
15
+ end
16
+
17
+ def safe_array_create
18
+ @safe_array_create ||= Fiddle::Function.new(
19
+ oleaut32['SafeArrayCreate'], [W::WORD, W::DWORD, W::VOIDP], W::VOIDP, W::STDCALL
20
+ )
21
+ end
22
+
23
+ def safe_array_create_vector
24
+ @safe_array_create_vector ||= Fiddle::Function.new(
25
+ oleaut32['SafeArrayCreateVector'], [W::WORD, W::LONG, W::DWORD], W::VOIDP, W::STDCALL
26
+ )
27
+ end
28
+
29
+ def safe_array_destroy
30
+ @safe_array_destroy ||= Fiddle::Function.new(oleaut32['SafeArrayDestroy'], [W::VOIDP], W::LONG, W::STDCALL)
31
+ end
32
+
33
+ def safe_array_lock
34
+ @safe_array_lock ||= Fiddle::Function.new(oleaut32['SafeArrayLock'], [W::VOIDP], W::LONG, W::STDCALL)
35
+ end
36
+
37
+ def safe_array_unlock
38
+ @safe_array_unlock ||= Fiddle::Function.new(oleaut32['SafeArrayUnlock'], [W::VOIDP], W::LONG, W::STDCALL)
39
+ end
40
+
41
+ def safe_array_get_dim
42
+ @safe_array_get_dim ||= Fiddle::Function.new(oleaut32['SafeArrayGetDim'], [W::VOIDP], W::DWORD, W::STDCALL)
43
+ end
44
+
45
+ def safe_array_get_lbound
46
+ @safe_array_get_lbound ||= Fiddle::Function.new(
47
+ oleaut32['SafeArrayGetLBound'], [W::VOIDP, W::DWORD, W::VOIDP], W::LONG, W::STDCALL
48
+ )
49
+ end
50
+
51
+ def safe_array_get_ubound
52
+ @safe_array_get_ubound ||= Fiddle::Function.new(
53
+ oleaut32['SafeArrayGetUBound'], [W::VOIDP, W::DWORD, W::VOIDP], W::LONG, W::STDCALL
54
+ )
55
+ end
56
+
57
+ def safe_array_ptr_of_index
58
+ @safe_array_ptr_of_index ||= Fiddle::Function.new(
59
+ oleaut32['SafeArrayPtrOfIndex'], [W::VOIDP, W::VOIDP, W::VOIDP], W::LONG, W::STDCALL
60
+ )
61
+ end
62
+
63
+ def safe_array_put_element
64
+ @safe_array_put_element ||= Fiddle::Function.new(
65
+ oleaut32['SafeArrayPutElement'], [W::VOIDP, W::VOIDP, W::VOIDP], W::LONG, W::STDCALL
66
+ )
67
+ end
68
+
69
+ def safe_array_access_data
70
+ @safe_array_access_data ||= Fiddle::Function.new(
71
+ oleaut32['SafeArrayAccessData'], [W::VOIDP, W::VOIDP], W::LONG, W::STDCALL
72
+ )
73
+ end
74
+
75
+ def safe_array_unaccess_data
76
+ @safe_array_unaccess_data ||= Fiddle::Function.new(
77
+ oleaut32['SafeArrayUnaccessData'], [W::VOIDP], W::LONG, W::STDCALL
78
+ )
79
+ end
80
+
81
+ # Port of ext/win32ole/win32ole.c's dimension() (line 1148) -- the
82
+ # nesting depth is the MAX depth found across every branch, not just
83
+ # the first element, so a ragged/mixed input still gets a well-defined
84
+ # depth.
85
+ def dimension_count(val)
86
+ return 0 unless val.is_a?(::Array)
87
+
88
+ val.reduce(0) { |max, v| [max, dimension_count(v)].max } + 1
89
+ end
90
+
91
+ # Port of ary_len_of_dim() (line 1167) -- the size at nesting level
92
+ # `dim` (0-indexed, 0 == outermost) is the MAX size found across every
93
+ # branch at that depth.
94
+ def dimension_size(val, dim)
95
+ return 0 unless val.is_a?(::Array)
96
+ return val.size if dim.zero?
97
+
98
+ val.reduce(0) { |max, v| [max, dimension_size(v, dim - 1)].max }
99
+ end
100
+
101
+ def dimension_sizes(val)
102
+ dims = dimension_count(val)
103
+ Array.new(dims) { |d| dimension_size(val, d) }
104
+ end
105
+
106
+ # Port of ole_ary_m_entry() (line 963) -- pid[0] indexes the outermost
107
+ # Array, pid[1] the next level in, etc. Returns nil past a shorter
108
+ # (ragged) branch, matching Ruby's own Array#[] out-of-range behavior.
109
+ def nested_entry(val, pid)
110
+ obj = val
111
+ pid.each { |i| obj = obj.is_a?(::Array) ? obj[i] : nil }
112
+ obj
113
+ end
114
+
115
+ # Port of ole_set_safe_array()'s pid increment loop (line 1116): the
116
+ # LAST index (the innermost Ruby nesting level) varies fastest,
117
+ # carrying left on overflow. This is the order ruby_array_to_safearray
118
+ # writes elements in.
119
+ def each_fill_index(sizes)
120
+ return enum_for(:each_fill_index, sizes) unless block_given?
121
+
122
+ return if sizes.any?(&:zero?)
123
+
124
+ pid = Array.new(sizes.size, 0)
125
+ loop do
126
+ yield pid.dup
127
+ i = sizes.size - 1
128
+ loop do
129
+ pid[i] += 1
130
+ break if pid[i] < sizes[i]
131
+
132
+ pid[i] = 0
133
+ i -= 1
134
+ return if i.negative?
135
+ end
136
+ end
137
+ end
138
+
139
+ # Port of ole_variant2val()'s pid increment loop (line 1476): the
140
+ # FIRST index (SAFEARRAY's own dimension 0) varies fastest --
141
+ # deliberately the opposite order of each_fill_index above (see this
142
+ # task's own note on why). lbounds/ubounds are inclusive, one entry
143
+ # per dimension, as SafeArrayGetLBound/GetUBound return them.
144
+ def each_read_index(lbounds, ubounds)
145
+ return enum_for(:each_read_index, lbounds, ubounds) unless block_given?
146
+
147
+ return if lbounds.zip(ubounds).any? { |lb, ub| ub < lb }
148
+
149
+ pid = lbounds.dup
150
+ loop do
151
+ yield pid.dup
152
+ i = 0
153
+ loop do
154
+ pid[i] += 1
155
+ break if pid[i] <= ubounds[i]
156
+
157
+ pid[i] = lbounds[i]
158
+ i += 1
159
+ return if i == pid.size
160
+ end
161
+ end
162
+ end
163
+
164
+ POINTER_ELEMENT_TYPES = [W::VT_BSTR, W::VT_DISPATCH, W::VT_UNKNOWN].freeze
165
+
166
+ # One element's value, read the way ole_variant2val reads a SAFEARRAY:
167
+ # through a VT_BYREF VARIANT pointing at it, without taking ownership
168
+ # of anything the array still holds.
169
+ def read_element(elem_addr, base_vt)
170
+ byref = W.pack_variant(base_vt | W::VT_BYREF, W.pack_pointer(elem_addr))
171
+ WIN32OLE.variant_bytes_to_ruby_value(byref, owned: false)
172
+ end
173
+
174
+ # ole_set_safe_array's val2variant_ptr + SafeArrayPutElement: the
175
+ # value's natural VARIANT, coerced to the element type; a VT_VARIANT
176
+ # element takes the VARIANT itself, a BSTR/IDispatch/IUnknown element
177
+ # the pointer (SafeArrayPutElement copies or AddRefs it), any other the
178
+ # address of the value.
179
+ def write_element(psa, index_buf, base_vt, val, to_free)
180
+ if base_vt == W::VT_VARIANT
181
+ var = WIN32OLE.ruby_value_to_variant_bytes(val, to_free)
182
+ return put_element(psa, index_buf, W.native_pointer_for(var))
183
+ end
184
+
185
+ var = val.nil? ? W.pack_variant(base_vt, W.pack_empty) : WIN32OLE.ruby_value_to_variant_bytes(val, to_free)
186
+ natural_vt, = W.unpack_variant(var)
187
+ if natural_vt != base_vt
188
+ var = W.change_variant_type(var, base_vt)
189
+ converted_bstr = base_vt == W::VT_BSTR
190
+ end
191
+ _vt, payload = W.unpack_variant(var)
192
+ if POINTER_ELEMENT_TYPES.include?(base_vt)
193
+ ptr = W.unpack_pointer(payload)
194
+ if ptr.zero? && base_vt != W::VT_BSTR
195
+ raise WIN32OLE::RuntimeError, 'element of array does not have IDispatch or IUnknown Interface'
196
+ end
197
+
198
+ put_element(psa, index_buf, ptr)
199
+ W.sys_free_string.call(ptr) if converted_bstr && !ptr.zero?
200
+ else
201
+ put_element(psa, index_buf, W.native_pointer_for(payload))
202
+ end
203
+ end
204
+
205
+ def put_element(psa, index_buf, pv)
206
+ hr = safe_array_put_element.call(psa, index_buf, pv)
207
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayPutElement: #{W.hr_hex(hr)}" if W.failed?(hr)
208
+ end
209
+
210
+ def ruby_array_to_safearray(ary, elem_vt, to_free = [])
211
+ base_vt = elem_vt & W::VT_TYPEMASK
212
+ base_vt = W::VT_VARIANT if base_vt.zero? # ole_val_ary2variant_ary: plain VT_ARRAY means VT_VARIANT elements
213
+ return ui1_safearray_from_bytes(ary) if base_vt == W::VT_UI1 && ary.is_a?(::String)
214
+ raise TypeError, "wrong argument type #{ary.class} (expected Array)" unless ary.is_a?(::Array)
215
+
216
+ sizes = dimension_sizes(ary)
217
+ dims = sizes.size
218
+ bounds = sizes.flat_map { |n| [n, 0] }.pack('L2' * dims)
219
+ psa = safe_array_create.call(base_vt, dims, bounds)
220
+ raise ::RuntimeError, 'memory allocation error' if psa.nil? || psa.to_i.zero?
221
+
222
+ hr = safe_array_lock.call(psa)
223
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayLock: #{W.hr_hex(hr)}" if W.failed?(hr)
224
+
225
+ begin
226
+ each_fill_index(sizes) do |pid|
227
+ write_element(psa, pid.pack('l' * dims), base_vt, nested_entry(ary, pid), to_free)
228
+ end
229
+ ensure
230
+ safe_array_unlock.call(psa)
231
+ end
232
+ psa
233
+ end
234
+
235
+ def safearray_to_ruby_array(psa, elem_vt)
236
+ base_vt = elem_vt & W::VT_TYPEMASK
237
+
238
+ dim = safe_array_get_dim.call(psa)
239
+ lbounds = Array.new(dim) { |d| out = ("\x00" * 4).b; safe_array_get_lbound.call(psa, d + 1, out); out.unpack1('l') }
240
+ ubounds = Array.new(dim) { |d| out = ("\x00" * 4).b; safe_array_get_ubound.call(psa, d + 1, out); out.unpack1('l') }
241
+
242
+ hr = safe_array_lock.call(psa)
243
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayLock: #{W.hr_hex(hr)}" if W.failed?(hr)
244
+
245
+ result = nested_array_skeleton(ubounds.zip(lbounds).map { |u, l| u - l + 1 })
246
+ begin
247
+ each_read_index(lbounds, ubounds) do |pid|
248
+ index_buf = pid.pack('l' * dim)
249
+ elem_ptr_out = ("\x00" * W::PTR_SIZE).b
250
+ hr = safe_array_ptr_of_index.call(psa, index_buf, elem_ptr_out)
251
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayPtrOfIndex: #{W.hr_hex(hr)}" if W.failed?(hr)
252
+
253
+ val = read_element(elem_ptr_out.unpack1(W::PACK_PTR), base_vt)
254
+ zero_based_pid = pid.each_with_index.map { |v, d| v - lbounds[d] }
255
+ store_nested(result, zero_based_pid, val)
256
+ end
257
+ ensure
258
+ safe_array_unlock.call(psa)
259
+ end
260
+ result
261
+ end
262
+
263
+ def nested_array_skeleton(sizes)
264
+ return Array.new(sizes.first) if sizes.size == 1
265
+
266
+ Array.new(sizes.first) { nested_array_skeleton(sizes[1..]) }
267
+ end
268
+
269
+ def store_nested(ary, pid, val)
270
+ obj = ary
271
+ pid[0..-2].each { |i| obj = obj[i] }
272
+ obj[pid.last] = val
273
+ end
274
+
275
+ # VT_UI1|VT_ARRAY <-> String fast path: bulk-copy via
276
+ # SafeArrayAccessData instead of the generic per-element path above --
277
+ # a real, separate MRI code path (ole_val2olevariantdata's first
278
+ # branch / folevariant_value's dim==1 reverse path), not an
279
+ # optimization detail this design can skip: without it, a binary blob
280
+ # argument would round-trip through an Array of small Integers instead
281
+ # of staying a String.
282
+ def ui1_safearray_from_bytes(bytes)
283
+ bytes = bytes.b
284
+ bounds = [bytes.bytesize, 0].pack('L2')
285
+ psa = safe_array_create.call(W::VT_UI1, 1, bounds)
286
+ raise ::RuntimeError, 'memory allocation error' if psa.nil? || psa.to_i.zero?
287
+
288
+ data_out = ("\x00" * W::PTR_SIZE).b
289
+ hr = safe_array_access_data.call(psa, data_out)
290
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayAccessData: #{W.hr_hex(hr)}" if W.failed?(hr)
291
+
292
+ Fiddle::Pointer.new(data_out.unpack1(W::PACK_PTR))[0, bytes.bytesize] = bytes
293
+ safe_array_unaccess_data.call(psa)
294
+ psa
295
+ end
296
+
297
+ def ui1_safearray_to_bytes(psa)
298
+ data_out = ("\x00" * W::PTR_SIZE).b
299
+ hr = safe_array_access_data.call(psa, data_out)
300
+ raise WIN32OLE::RuntimeError, "failed to SafeArrayAccessData: #{W.hr_hex(hr)}" if W.failed?(hr)
301
+
302
+ lb_out = ("\x00" * 4).b
303
+ ub_out = ("\x00" * 4).b
304
+ safe_array_get_lbound.call(psa, 1, lb_out)
305
+ safe_array_get_ubound.call(psa, 1, ub_out)
306
+ len = ub_out.unpack1('l') - lb_out.unpack1('l') + 1
307
+ bytes = Fiddle::Pointer.new(data_out.unpack1(W::PACK_PTR))[0, len].dup.b
308
+ safe_array_unaccess_data.call(psa)
309
+ bytes
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'win32ole/ffi/win32'
4
+
5
+ class WIN32OLE
6
+ module Dispatch
7
+ W = WIN32OLE::Win32
8
+
9
+ def dispid_for(name)
10
+ ids = dispids_for([name])
11
+ ids && ids.first
12
+ end
13
+
14
+ # One GetIDsOfNames call for several names. Given a method name followed
15
+ # by parameter names, the parameters are resolved in that method's
16
+ # scope (what named arguments need). nil if the call fails.
17
+ def dispids_for(names)
18
+ raise WIN32OLE::RuntimeError, 'this WIN32OLE object has already been released' if @ptr.nil? || @ptr.zero?
19
+
20
+ name_bufs = names.map { |name| W.wstr(name) }
21
+ name_ptrs = name_bufs.map { |buf| W.native_pointer_for(buf) }
22
+ names_blob = name_ptrs.map(&:to_i).pack("#{W::PACK_PTR}*")
23
+ dispids = ("\x00" * (4 * names.size)).b
24
+ hr = get_ids_of_names_fn.call(@ptr, W::IID_NULL, names_blob, names.size, 0, dispids)
25
+ return nil if W.failed?(hr)
26
+
27
+ dispids.unpack('l*')
28
+ end
29
+
30
+ # ole_invoke's treatment of a trailing Hash: its entries become named
31
+ # arguments, their DISPIDs looked up together with the method name.
32
+ # Returns [positional_args, named_args], named_args being
33
+ # [[dispid, value], ...] or nil when there is no trailing Hash.
34
+ def split_named_args(name, args)
35
+ hash = args.last
36
+ return [args, nil] unless hash.is_a?(::Hash)
37
+
38
+ keys = hash.keys.map do |key|
39
+ case key
40
+ when ::String then key
41
+ when ::Symbol then key.to_s
42
+ else raise TypeError, 'wrong argument type (expected String or Symbol)'
43
+ end
44
+ end
45
+ ids = dispids_for([name, *keys])
46
+ raise WIN32OLE::RuntimeError, "failed to get named argument info: `#{name}'" if ids.nil?
47
+
48
+ [args[0...-1], ids.drop(1).zip(hash.values)]
49
+ end
50
+
51
+ # Returns [hr, result_variant_bytes, excepinfo_bytes]. Never raises —
52
+ # callers build the user-facing error message (they know whether this
53
+ # was a method call or a property-put, which changes the message).
54
+ # named_args ([[dispid, value], ...]) occupy the front of rgvarg, in
55
+ # order, ahead of the reversed positional arguments -- the layout
56
+ # ole_invoke builds; named_put marks the value as DISPID_PROPERTYPUT.
57
+ def ole_invoke(dispid, arg_values, wflags, named_put: false, named_args: nil)
58
+ raise WIN32OLE::RuntimeError, 'this WIN32OLE object has already been released' if @ptr.nil? || @ptr.zero?
59
+
60
+ keep_alive = []
61
+ to_free = []
62
+
63
+ named_args ||= []
64
+ values = named_args.map(&:last) + arg_values.reverse
65
+ arg_variants = values.map { |v| WIN32OLE.ruby_value_to_variant_bytes(v, to_free) }
66
+ args_blob = arg_variants.join
67
+ args_ptr = args_blob.empty? ? nil : W.native_pointer_for(args_blob)
68
+ keep_alive << args_ptr if args_ptr
69
+
70
+ named_ids = named_put ? [W::DISPID_PROPERTYPUT] : named_args.map(&:first)
71
+ named_blob = named_ids.pack('l*')
72
+ named_ptr = named_blob.empty? ? nil : W.native_pointer_for(named_blob)
73
+ keep_alive << named_ptr if named_ptr
74
+
75
+ # DISPPARAMS is a real native struct (VARIANTARG *rgvarg; DISPID
76
+ # *rgdispidNamedArgs; UINT cArgs; UINT cNamedArgs;) read positionally
77
+ # by the callee, so — unlike a VARIANT's self-designed padding — the
78
+ # two pointer fields MUST be packed at native pointer width (PACK_PTR),
79
+ # not a hardcoded 8 bytes, or this silently misaligns cArgs/cNamedArgs
80
+ # on x86.
81
+ dispparams = [
82
+ args_ptr ? args_ptr.to_i : 0,
83
+ named_ptr ? named_ptr.to_i : 0,
84
+ arg_variants.size,
85
+ named_ids.size
86
+ ].pack("#{W::PACK_PTR}#{W::PACK_PTR}LL")
87
+ keep_alive << dispparams
88
+
89
+ excepinfo = ("\x00" * W::EXCEPINFO_SIZE).b
90
+ result = ("\x00" * W::VARIANT_SIZE).b
91
+
92
+ hr = invoke_fn.call(@ptr, dispid, W::IID_NULL, 0, wflags, dispparams, result, excepinfo, nil)
93
+ to_free.each(&:call)
94
+ [hr, result, excepinfo]
95
+ end
96
+
97
+ def release
98
+ return unless @ptr && !@ptr.zero?
99
+
100
+ release_fn.call(@ptr)
101
+ @ptr = nil
102
+ ObjectSpace.undefine_finalizer(self)
103
+ end
104
+
105
+ private
106
+
107
+ def get_ids_of_names_fn
108
+ @get_ids_of_names_fn ||= W.vtable_function(
109
+ @ptr, 5, [W::VOIDP, W::VOIDP, W::VOIDP, W::DWORD, W::DWORD, W::VOIDP], W::LONG
110
+ )
111
+ end
112
+
113
+ def invoke_fn
114
+ @invoke_fn ||= W.vtable_function(
115
+ @ptr, 6,
116
+ [W::VOIDP, W::LONG, W::VOIDP, W::DWORD, W::WORD, W::VOIDP, W::VOIDP, W::VOIDP, W::VOIDP],
117
+ W::LONG
118
+ )
119
+ end
120
+
121
+ def release_fn
122
+ @release_fn ||= W.vtable_function(@ptr, 2, [W::VOIDP], W::DWORD)
123
+ end
124
+ end
125
+ end