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.
@@ -0,0 +1,337 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'win32ole/ffi/win32'
4
+ require 'win32ole/ffi/typeinfo'
5
+ require 'win32ole/ffi/method'
6
+ require 'win32ole/ffi/variable'
7
+
8
+ class WIN32OLE
9
+ class Type
10
+ W = Win32
11
+ TI = TypeInfo
12
+ private_constant :W, :TI
13
+
14
+ TYPEFLAG_FHIDDEN = 0x10
15
+ TYPEFLAG_FRESTRICTED = 0x200
16
+ TKIND_ALIAS = 6
17
+
18
+ # Mirrors ext/win32ole/win32ole_type.c's foletype_initialize:
19
+ # WIN32OLE::Type.new(typelib, ole_class) resolves +typelib+ (a
20
+ # registered typelib name or a CLSID string) to a file via
21
+ # TypeLib.file_for, LoadTypeLibEx's it, then finds the ole_class
22
+ # member by name.
23
+ def self.new(typelib, ole_class)
24
+ typelib = check_string!(typelib)
25
+ ole_class = check_string!(ole_class)
26
+
27
+ itypelib_ptr = load_typelib_for(typelib)
28
+ begin
29
+ found = find_type_by_name(itypelib_ptr, ole_class)
30
+ raise WIN32OLE::RuntimeError, "not found `#{ole_class}` in `#{typelib}`" if found.nil?
31
+
32
+ found
33
+ ensure
34
+ W.vtable_function(itypelib_ptr, 2, [W::VOIDP], W::DWORD).call(itypelib_ptr)
35
+ end
36
+ end
37
+
38
+ def self.check_string!(val)
39
+ return val if val.is_a?(::String)
40
+ return val.to_str if val.respond_to?(:to_str)
41
+
42
+ raise TypeError, "no implicit conversion of #{val.class} into String"
43
+ end
44
+ private_class_method :check_string!
45
+
46
+ def self.load_typelib_for(typelib)
47
+ file = WIN32OLE::TypeLib.file_for(typelib) || typelib
48
+ out = ("\x00" * W::PTR_SIZE).b
49
+ hr = TI.load_type_lib_ex.call(W.wstr(file), TI::REGKIND_NONE, out)
50
+ raise WIN32OLE::RuntimeError, 'failed to LoadTypeLibEx' if W.failed?(hr)
51
+
52
+ out.unpack1(W::PACK_PTR)
53
+ end
54
+ private_class_method :load_typelib_for
55
+
56
+ # Mirrors oleclass_from_typelib: scan the typelib's members by
57
+ # GetDocumentation name first (cheap), and only GetTypeInfo (which
58
+ # AddRefs the result) once a name actually matches.
59
+ def self.find_type_by_name(itypelib_ptr, ole_class)
60
+ count = TI.type_info_count_fn(itypelib_ptr).call(itypelib_ptr)
61
+ count.times do |i|
62
+ name_out = ("\x00" * W::PTR_SIZE).b
63
+ hr = TI.documentation_fn_for_typelib(itypelib_ptr).call(itypelib_ptr, i, name_out, nil, nil, nil)
64
+ next if W.failed?(hr)
65
+
66
+ name_bstr = name_out.unpack1(W::PACK_PTR)
67
+ name = W.bstr_to_s(name_bstr)
68
+ W.sys_free_string.call(name_bstr) unless name_bstr.zero?
69
+ next unless name == ole_class
70
+
71
+ ti_out = ("\x00" * W::PTR_SIZE).b
72
+ hr = TI.type_info_fn(itypelib_ptr).call(itypelib_ptr, i, ti_out)
73
+ return nil if W.failed?(hr)
74
+
75
+ return from_typeinfo_ptr(ti_out.unpack1(W::PACK_PTR))
76
+ end
77
+ nil
78
+ end
79
+ private_class_method :find_type_by_name
80
+
81
+ def self.from_typeinfo_ptr(itypeinfo_ptr)
82
+ allocate.tap { |type| type.send(:initialize, itypeinfo_ptr) }
83
+ end
84
+
85
+ def initialize(itypeinfo_ptr)
86
+ # As the C allocator does, so the finalizer's ole_initialized? check
87
+ # holds on the thread that made this object.
88
+ WIN32OLE.ole_initialize
89
+ @ptr = itypeinfo_ptr
90
+ # Install the finalizer before any call that could raise: the caller
91
+ # has already AddRef'd this pointer, so if GetTypeAttr/read_documentation
92
+ # (or the vtable lookups they perform) raise, this Release must still
93
+ # happen -- otherwise the reference leaks permanently.
94
+ install_finalizer
95
+
96
+ attr_out = ("\x00" * W::PTR_SIZE).b
97
+ hr = TI.type_attr_fn(@ptr).call(@ptr, attr_out)
98
+ if W.failed?(hr)
99
+ raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('GetTypeAttr', W.hr_hex(hr))
100
+ end
101
+ attr_ptr = attr_out.unpack1(W::PACK_PTR)
102
+ typeattr = TI::TYPEATTR.new(attr_ptr)
103
+
104
+ @guid_bytes = [typeattr.guid_Data1, typeattr.guid_Data2, typeattr.guid_Data3].pack('LSS') +
105
+ typeattr.guid_Data4.pack('C8')
106
+ @typekind = typeattr.typekind
107
+ @major = typeattr.wMajorVerNum
108
+ @minor = typeattr.wMinorVerNum
109
+ @type_flags = typeattr.wTypeFlags
110
+ # ole_type_src_type: the aliased type, resolved now (while TYPEATTR is
111
+ # alive) through the raw TYPEDESC -- reading its union through the
112
+ # struct's `void *` accessor overflows on the garbage upper bytes it
113
+ # holds for anything but a real pointer.
114
+ @src_type =
115
+ if @typekind == TKIND_ALIAS
116
+ TI.typedesc_details(@ptr, attr_ptr + TI.offsetof(TI::TYPEATTR, 'tdescAlias_union_ptr')).last
117
+ end
118
+ TI.release_type_attr_fn(@ptr).call(@ptr, attr_ptr)
119
+
120
+ @name, @helpstring, @help_context, @helpfile = read_documentation(@ptr, -1)
121
+ end
122
+
123
+ # WIN32OLE::Method.new(oletype, name) needs this raw ITypeInfo* to
124
+ # search oletype's own funcs and its implemented interfaces -- same
125
+ # deliberate, documented crack in encapsulation as WIN32OLE#dispatch_ptr.
126
+ def itypeinfo_ptr
127
+ @ptr
128
+ end
129
+
130
+ def name
131
+ @name
132
+ end
133
+ alias to_s name
134
+
135
+ def ole_type
136
+ TI::TYPEKIND_NAMES[@typekind]
137
+ end
138
+
139
+ def guid
140
+ d1, d2, d3 = @guid_bytes.unpack('LSS')
141
+ d4 = @guid_bytes[8, 8].unpack('C8')
142
+ format('{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}', d1, d2, d3, *d4)
143
+ end
144
+
145
+ def progid
146
+ W.prog_id_from_clsid(@guid_bytes)
147
+ end
148
+
149
+ def visible?
150
+ (@type_flags & (TYPEFLAG_FHIDDEN | TYPEFLAG_FRESTRICTED)) == 0
151
+ end
152
+
153
+ def major_version
154
+ @major
155
+ end
156
+
157
+ def minor_version
158
+ @minor
159
+ end
160
+
161
+ def typekind
162
+ @typekind
163
+ end
164
+
165
+ def helpstring
166
+ @helpstring
167
+ end
168
+
169
+ def helpfile
170
+ @helpfile
171
+ end
172
+
173
+ def helpcontext
174
+ @help_context
175
+ end
176
+
177
+ def src_type
178
+ @src_type
179
+ end
180
+
181
+ def variables
182
+ count = type_attr_var_count
183
+ Array.new(count) { |i| WIN32OLE::Variable.from_typeinfo_ptr(@ptr, i) }
184
+ end
185
+
186
+ # ole_methods_from_typeinfo: the type's own functions, then (one level)
187
+ # those of every type it implements -- a coclass such as "Shell" has
188
+ # none of its own.
189
+ def ole_methods
190
+ methods = Array.new(type_attr_func_count) { |i| WIN32OLE::Method.from_typeinfo_ptr(@ptr, i) }
191
+ TI.each_impl_type(@ptr) do |_flags, ref_ptr|
192
+ begin
193
+ (TI.type_attr_value(ref_ptr, :cFuncs) || 0).times do |i|
194
+ methods << WIN32OLE::Method.from_typeinfo_ptr(ref_ptr, i, @ptr)
195
+ end
196
+ ensure
197
+ TI.release(ref_ptr)
198
+ end
199
+ end
200
+ methods
201
+ end
202
+
203
+ def ole_typelib
204
+ tlib_out = ("\x00" * W::PTR_SIZE).b
205
+ index_out = ("\x00" * 4).b
206
+ hr = TI.containing_typelib_fn(@ptr).call(@ptr, tlib_out, index_out)
207
+ if W.failed?(hr)
208
+ raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('GetContainingTypeLib', W.hr_hex(hr))
209
+ end
210
+ WIN32OLE::TypeLib.from_itypelib_ptr(tlib_out.unpack1(W::PACK_PTR))
211
+ end
212
+
213
+ def inspect
214
+ "#<WIN32OLE::Type:#{name}>"
215
+ end
216
+
217
+ def implemented_ole_types
218
+ impl_ole_types(0)
219
+ end
220
+
221
+ def source_ole_types
222
+ impl_ole_types(TI::IMPLTYPEFLAG_FSOURCE)
223
+ end
224
+
225
+ def default_event_sources
226
+ impl_ole_types(TI::IMPLTYPEFLAG_FSOURCE | TI::IMPLTYPEFLAG_FDEFAULT)
227
+ end
228
+
229
+ def default_ole_types
230
+ impl_ole_types(TI::IMPLTYPEFLAG_FDEFAULT)
231
+ end
232
+
233
+ # foletype_s_ole_classes (obsolete in C too)
234
+ def self.ole_classes(typelib)
235
+ WIN32OLE::TypeLib.new(typelib).ole_types
236
+ end
237
+
238
+ # foletype_s_typelibs (obsolete in C too)
239
+ def self.typelibs
240
+ WIN32OLE::TypeLib.typelibs.map(&:name)
241
+ end
242
+
243
+ # foletype_s_progids: the ProgID and VersionIndependentProgID of every
244
+ # HKCR\CLSID entry that has them.
245
+ def self.progids
246
+ tl = WIN32OLE::TypeLib
247
+ progids = []
248
+ tl.with_reg_subkey(TI::HKEY_CLASSES_ROOT, 'CLSID') do |hclsids|
249
+ tl.each_reg_subkey_name(hclsids) do |clsid|
250
+ tl.with_reg_subkey(hclsids, clsid) do |hclsid|
251
+ %w[ProgID VersionIndependentProgID].each do |key|
252
+ progid = tl.with_reg_subkey(hclsid, key) { |hkey| tl.reg_default_string_value(hkey) }
253
+ progids << progid if progid
254
+ end
255
+ end
256
+ end
257
+ end
258
+ progids
259
+ end
260
+
261
+ private
262
+
263
+ # ole_type_impl_ole_types: implemented types whose IMPLTYPEFLAGs include
264
+ # all of +implflags+. A matching ref ITypeInfo* is handed to the new
265
+ # Type (whose finalizer releases it); the rest are released here.
266
+ def impl_ole_types(implflags)
267
+ types = []
268
+ TI.each_impl_type(@ptr) do |flags, ref_ptr|
269
+ if (flags & implflags) == implflags
270
+ begin
271
+ types << Type.from_typeinfo_ptr(ref_ptr)
272
+ rescue WIN32OLE::QueryInterfaceError
273
+ # like ole_type_from_itypeinfo returning nil: skip it (the
274
+ # Type's finalizer, installed first, still releases ref_ptr)
275
+ end
276
+ else
277
+ TI.release(ref_ptr)
278
+ end
279
+ end
280
+ types
281
+ end
282
+
283
+ def type_attr_func_count
284
+ attr_out = ("\x00" * W::PTR_SIZE).b
285
+ hr = TI.type_attr_fn(@ptr).call(@ptr, attr_out)
286
+ if W.failed?(hr)
287
+ raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('GetTypeAttr', W.hr_hex(hr))
288
+ end
289
+ attr_ptr = attr_out.unpack1(W::PACK_PTR)
290
+ count = TI::TYPEATTR.new(attr_ptr).cFuncs
291
+ TI.release_type_attr_fn(@ptr).call(@ptr, attr_ptr)
292
+ count
293
+ end
294
+
295
+ def type_attr_var_count
296
+ attr_out = ("\x00" * W::PTR_SIZE).b
297
+ hr = TI.type_attr_fn(@ptr).call(@ptr, attr_out)
298
+ if W.failed?(hr)
299
+ raise WIN32OLE::QueryInterfaceError, W.query_interface_error_message('GetTypeAttr', W.hr_hex(hr))
300
+ end
301
+ attr_ptr = attr_out.unpack1(W::PACK_PTR)
302
+ count = TI::TYPEATTR.new(attr_ptr).cVars
303
+ TI.release_type_attr_fn(@ptr).call(@ptr, attr_ptr)
304
+ count
305
+ end
306
+
307
+ def read_documentation(itypeinfo_ptr, memid)
308
+ name_out = ("\x00" * W::PTR_SIZE).b
309
+ docstring_out = ("\x00" * W::PTR_SIZE).b
310
+ helpcontext_out = ("\x00" * 4).b
311
+ helpfile_out = ("\x00" * W::PTR_SIZE).b
312
+ TI.documentation_fn_for_typeinfo(itypeinfo_ptr).call(
313
+ itypeinfo_ptr, memid, name_out, docstring_out, helpcontext_out, helpfile_out
314
+ )
315
+ name_bstr = name_out.unpack1(W::PACK_PTR)
316
+ docstring_bstr = docstring_out.unpack1(W::PACK_PTR)
317
+ helpfile_bstr = helpfile_out.unpack1(W::PACK_PTR)
318
+ name = W.bstr_to_s(name_bstr)
319
+ helpstring = W.bstr_to_s(docstring_bstr) || '' # C's WC2VSTR(NULL) is '', not nil
320
+ helpfile = W.bstr_to_s(helpfile_bstr) || ''
321
+ [name_bstr, docstring_bstr, helpfile_bstr].each { |b| W.sys_free_string.call(b) unless b.zero? }
322
+ [name, helpstring, helpcontext_out.unpack1('L'), helpfile]
323
+ end
324
+
325
+ def install_finalizer
326
+ ptr = @ptr
327
+ release_fn = W.vtable_function(ptr, 2, [W::VOIDP], W::DWORD)
328
+ ObjectSpace.define_finalizer(self, self.class.finalizer(ptr, release_fn))
329
+ end
330
+
331
+ # OLE_FREE, deferred on a thread without OLE (as on JRuby); see
332
+ # WIN32OLE.finalizer.
333
+ def self.finalizer(ptr, release_fn)
334
+ proc { W.release_on_ole_thread { release_fn.call(ptr) } unless ptr.zero? }
335
+ end
336
+ end
337
+ end