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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d256a4fde6e3b23e2eaa63e19f294a07ce0f50af3b8afc24dda7a9c0a67dad17
|
|
4
|
+
data.tar.gz: 250e8e558565e7857d04f906cdc24433e1747c092a24860a8ea0bbfe5dd48f96
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bedcff11b9a0a5a8fa0f31b90b94de0b683ef2b608f3859e3375845f2a0fab02096a0589caaf4c441b9c9d5acf73f54ef99ca7fb7d1c1785b5b2c76dad4b8ebf
|
|
7
|
+
data.tar.gz: ffea99b91f339f2d534c5abc24aca19a74a9bbb324cb0bdb4476c9cef7296d71c8264b2c73176383d306df7d5b3ec235afa5b2bdf8d2fa6ad653c220063e83c8
|
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
|