windows_com 1.0.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '0194380637c6543160a0be00e072f4806e0cdd65'
4
- data.tar.gz: 935c162da6720ad0ba281f0bc7d8d6573f82fcca
3
+ metadata.gz: 8fd412b3685ad9f5901bd3f89dbf2b6a477fa884
4
+ data.tar.gz: 29d1847667d2b8e406c09b9fab9afd5785e0f545
5
5
  SHA512:
6
- metadata.gz: ed3ab8238d527d4fd4b6770a70b39d0cf6690c1036ba4702d162ef230954bf293051bab645200dd0e6e69c5f091a45aec62e903a0c8d699c32f96294e952e1bb
7
- data.tar.gz: 0e6994e3718acc2292accf0dca730484428dd2c22816580d07169c1d7926311108b209b192196b773af6de539647f629f6fdefc74789d790829e6b7e658ff6a2
6
+ metadata.gz: dadc3e3d21b5f52341fee37cee3ed18024d305fc8e547d1adb959dc2d323f3c235a4402d5a115902ddb86c9e9a7fcc717a0e8ca86bdfeed6a3a3ca54519326e2
7
+ data.tar.gz: 3ddeeffb332b95f21147f2300edca335c5848025850289647b142e56ba3feb01426bda38d845ca0001e9d4d4234f567f5a08461d07fe0ea87364977392f78aee
data/README.md CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  Ruby FFI (x86) bindings to essential COM related Windows APIs
4
4
 
5
+ ![Screenshot](./screenshot.png)
6
+
5
7
  ## Install
6
8
 
7
9
  gem install windows_com
8
10
 
9
11
  ## Use
10
12
 
11
- See examples folder
13
+ See examples folder (the UIRibbon example requires windows_gui gem)
data/RELNOTES.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Release Notes
2
2
 
3
+ ## 2.0.0
4
+
5
+ - Add COMCallback module for implementing COM objects on the Ruby/FFI side
6
+ - enhance some bound FFI structs with useful methods
7
+ - improve code
8
+
3
9
  ## 1.0.0
4
10
 
5
11
  Rename library to windows_com and ensure it works with recent ruby
@@ -10,12 +10,8 @@ IDesktopGadget = COMInterface[IUnknown,
10
10
 
11
11
  DesktopGadget = COMFactory[IDesktopGadget, '924ccc1b-6562-4c85-8657-d177925222b6']
12
12
 
13
- dg = DesktopGadget.new
14
-
15
- begin
13
+ UsingCOMObjects(DesktopGadget.new) { |dg|
16
14
  dg.RunGadget(
17
- "#{ENV['ProgramFiles']}\\Windows Sidebar\\Gadgets\\Clock.Gadget\0".encode('utf-16le')
15
+ "#{ENV['ProgramFiles']}\\Windows Sidebar\\Gadgets\\Clock.Gadget\0".encode!('utf-16le')
18
16
  )
19
- ensure
20
- dg.Release
21
- end
17
+ }
Binary file
@@ -0,0 +1,30 @@
1
+ # Generated by the UIRibbon build, do NOT modify
2
+
3
+ CmdAppMenu = 2
4
+ CmdQAT = 3
5
+ CmdTab1 = 4
6
+ CmdTab1_LabelTitle_RESID = 60001
7
+ CmdTab1Group1 = 5
8
+ CmdTab1Group1_LabelTitle_RESID = 60002
9
+ CmdTab1Group1_LabelDescription_RESID = 60003
10
+ CmdTab1Group1_TooltipDescription_RESID = 60004
11
+ CmdTab1Group1_SmallImages_RESID = 60005
12
+ CmdTab1Group1_SmallImages_120__RESID = 60006
13
+ CmdTab1Group1_LargeImages_RESID = 60007
14
+ CmdTab1Group1_LargeImages_120__RESID = 60008
15
+ CmdItem1 = 6
16
+ CmdItem1_LabelTitle_RESID = 60009
17
+ CmdItem1_LabelDescription_RESID = 60010
18
+ CmdItem1_TooltipDescription_RESID = 60011
19
+ CmdItem1_SmallImages_RESID = 60012
20
+ CmdItem1_SmallImages_120__RESID = 60013
21
+ CmdItem1_LargeImages_RESID = 60014
22
+ CmdItem1_LargeImages_120__RESID = 60015
23
+ CmdButton1 = 7
24
+ CmdButton1_LabelTitle_RESID = 60016
25
+ CmdButton1_LabelDescription_RESID = 60017
26
+ CmdButton1_TooltipDescription_RESID = 60018
27
+ CmdButton1_SmallImages_RESID = 60019
28
+ CmdButton1_SmallImages_120__RESID = 60020
29
+ CmdButton1_LargeImages_RESID = 60021
30
+ CmdButton1_LargeImages_120__RESID = 60022
@@ -0,0 +1,201 @@
1
+ #WINDOWS_COM_TRACE_CALLBACK_REFCOUNT = true
2
+
3
+ require 'windows_gui'
4
+ require 'windows_gui/uiribbon' # requires windows_com gem
5
+
6
+ include WindowsGUI
7
+ include UIRibbon
8
+
9
+ class UIF < UIFramework
10
+ def initialize(hwnd)
11
+ @hwnd = hwnd
12
+
13
+ super()
14
+ end
15
+
16
+ attr_reader :hwnd
17
+ end
18
+
19
+ class UICH < IUICommandHandlerImpl
20
+ def initialize(uif)
21
+ @uif = uif
22
+
23
+ super()
24
+ end
25
+
26
+ attr_reader :uif
27
+
28
+ def OnItem1(*args)
29
+ MessageBox(uif.hwnd,
30
+ L("#{self}.#{__method__}"),
31
+ APPNAME,
32
+ MB_OK
33
+ )
34
+
35
+ uif.SetUICommandProperty(CmdButton1, UI_PKEY_Enabled, PROPVARIANT[VT_BOOL, :boolVal, -1])
36
+ uif.SetUICommandProperty(CmdItem1, UI_PKEY_Enabled, PROPVARIANT[VT_BOOL, :boolVal, 0])
37
+ end
38
+
39
+ def OnButton1(*args)
40
+ MessageBox(uif.hwnd,
41
+ L("#{self}.#{__method__}"),
42
+ APPNAME,
43
+ MB_OK
44
+ )
45
+
46
+ uif.SetUICommandProperty(CmdItem1, UI_PKEY_Enabled, PROPVARIANT[VT_BOOL, :boolVal, -1])
47
+ uif.SetUICommandProperty(CmdButton1, UI_PKEY_Enabled, PROPVARIANT[VT_BOOL, :boolVal, 0])
48
+ end
49
+
50
+ def Execute(*args)
51
+ case args[0]
52
+ when CmdItem1
53
+ OnItem1(*args)
54
+ when CmdButton1
55
+ OnButton1(*args)
56
+ end
57
+
58
+ S_OK
59
+ end
60
+ end
61
+
62
+ class UIA < IUIApplicationImpl
63
+ def initialize(uich)
64
+ @uich = uich
65
+
66
+ super()
67
+ end
68
+
69
+ attr_reader :uich
70
+
71
+ def OnCreateUICommand(*args)
72
+ uich.QueryInterface(uich.class::IID, args[-1])
73
+
74
+ S_OK
75
+ end
76
+ end
77
+
78
+ WndExtra = Struct.new(
79
+ :uif,
80
+ :uich,
81
+ :uia
82
+ )
83
+
84
+ def OnCreate(hwnd,
85
+ cs
86
+ )
87
+ xtra = Id2Ref[GetWindowLong(hwnd, GWL_USERDATA)]
88
+
89
+ xtra[:uif] = UIF.new(hwnd)
90
+ xtra[:uich] = UICH.new(xtra[:uif])
91
+ xtra[:uia] = UIA.new(xtra[:uich])
92
+
93
+ xtra[:uif].Initialize(hwnd, xtra[:uia].vptr)
94
+ xtra[:uif].LoadUI(LoadUIDll(), L('APPLICATION_RIBBON'))
95
+
96
+ 0
97
+ end
98
+
99
+ def OnDestroy(hwnd)
100
+ xtra = Id2Ref[GetWindowLong(hwnd, GWL_USERDATA)]
101
+
102
+ xtra[:uif].Destroy()
103
+ xtra[:uif].Release()
104
+ xtra[:uich].Release()
105
+ xtra[:uia].Release()
106
+
107
+ PostQuitMessage(0); 0
108
+ end
109
+
110
+ WindowProc = FFI::Function.new(:long,
111
+ [:pointer, :uint, :uint, :long],
112
+ convention: :stdcall
113
+ ) { |hwnd, uMsg, wParam, lParam|
114
+ begin
115
+ result = case uMsg
116
+ when WM_NCCREATE
117
+ DefWindowProc(hwnd, uMsg, wParam, lParam)
118
+
119
+ SetWindowLong(hwnd,
120
+ GWL_USERDATA,
121
+ CREATESTRUCT.new(FFI::Pointer.new(lParam))[:lpCreateParams].to_i
122
+ )
123
+
124
+ 1
125
+ when WM_CREATE
126
+ OnCreate(hwnd, CREATESTRUCT.new(FFI::Pointer.new(lParam)))
127
+ when WM_DESTROY
128
+ OnDestroy(hwnd)
129
+ end
130
+
131
+ result || DefWindowProc(hwnd, uMsg, wParam, lParam)
132
+ rescue SystemExit => ex
133
+ PostQuitMessage(ex.status)
134
+ rescue
135
+ case MessageBox(hwnd,
136
+ L(FormatException($!)),
137
+ APPNAME,
138
+ MB_ABORTRETRYIGNORE | MB_ICONERROR
139
+ )
140
+ when IDABORT
141
+ PostQuitMessage(2)
142
+ when IDRETRY
143
+ retry
144
+ end
145
+ end
146
+ }
147
+
148
+ def WinMain
149
+ Id2RefTrack(xtra = WndExtra.new)
150
+
151
+ UsingFFIStructs(WNDCLASSEX.new) { |wc|
152
+ wc[:cbSize] = wc.size
153
+ wc[:lpfnWndProc] = WindowProc
154
+ wc[:cbWndExtra] = FFI::Type::Builtin::POINTER.size
155
+ wc[:hInstance] = GetModuleHandle(nil)
156
+ wc[:hIcon] = LoadIcon(nil, IDI_APPLICATION)
157
+ wc[:hCursor] = LoadCursor(nil, IDC_ARROW)
158
+ wc[:hbrBackground] = FFI::Pointer.new(COLOR_WINDOW + 1)
159
+
160
+ UsingFFIMemoryPointers(PWSTR(APPNAME)) { |className|
161
+ wc[:lpszClassName] = className
162
+
163
+ DetonateLastError(0, :RegisterClassEx,
164
+ wc
165
+ )
166
+ }
167
+ }
168
+
169
+ hwnd = CreateWindowEx(
170
+ WS_EX_CLIENTEDGE, APPNAME, APPNAME, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
171
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
172
+ nil, nil, GetModuleHandle(nil), FFI::Pointer.new(xtra.object_id)
173
+ )
174
+
175
+ raise "CreateWindowEx failed (last error: #{GetLastError()})" if
176
+ hwnd.null? && GetLastError() != 0
177
+
178
+ exit(0) if hwnd.null?
179
+
180
+ ShowWindow(hwnd, SW_SHOWNORMAL)
181
+ UpdateWindow(hwnd)
182
+
183
+ UsingFFIStructs(MSG.new) { |msg|
184
+ until DetonateLastError(-1, :GetMessage,
185
+ msg, nil, 0, 0
186
+ ) == 0
187
+ TranslateMessage(msg)
188
+ DispatchMessage(msg)
189
+ end
190
+
191
+ exit(msg[:wParam])
192
+ }
193
+ rescue
194
+ MessageBox(hwnd,
195
+ L(FormatException($!)),
196
+ APPNAME,
197
+ MB_ICONERROR
198
+ ); exit(1)
199
+ end
200
+
201
+ WinMain()
@@ -0,0 +1,71 @@
1
+ <?xml version='1.0' encoding='utf-8' ?>
2
+ <Application xmlns='http://schemas.microsoft.com/windows/2009/Ribbon'>
3
+ <Application.Commands>
4
+ <Command Name='cmdAppMenu' />
5
+
6
+ <Command Name='cmdQAT' />
7
+
8
+ <Command Name='cmdTab1' LabelTitle='Tab1' />
9
+ <Command Name='cmdTab1Group1' LabelTitle='&amp;Group1' LabelDescription='LabelDescription...' TooltipDescription='TooltipDescription...'>
10
+ <Command.SmallImages>
11
+ <Image>../../res/go-next-small.bmp</Image>
12
+ <Image MinDPI='120'>../../res/go-next.bmp</Image>
13
+ </Command.SmallImages>
14
+ <Command.LargeImages>
15
+ <Image>../../res/go-next.bmp</Image>
16
+ <Image MinDPI='120'>../../res/go-next-big.bmp</Image>
17
+ </Command.LargeImages>
18
+ </Command>
19
+
20
+ <Command Name='cmdItem1' LabelTitle='&amp;Item1' LabelDescription='LabelDescription...' TooltipDescription='TooltipDescription...'>
21
+ <Command.SmallImages>
22
+ <Image>../../res/go-previous-small.bmp</Image>
23
+ <Image MinDPI='120'>../../res/go-previous.bmp</Image>
24
+ </Command.SmallImages>
25
+ <Command.LargeImages>
26
+ <Image>../../res/go-previous.bmp</Image>
27
+ <Image MinDPI='120'>../../res/go-previous-big.bmp</Image>
28
+ </Command.LargeImages>
29
+ </Command>
30
+
31
+ <Command Name='cmdButton1' LabelTitle='&amp;Button1' LabelDescription='LabelDescription...' TooltipDescription='TooltipDescription...'>
32
+ <Command.SmallImages>
33
+ <Image>../../res/go-next-small.bmp</Image>
34
+ <Image MinDPI='120'>../../res/go-next.bmp</Image>
35
+ </Command.SmallImages>
36
+ <Command.LargeImages>
37
+ <Image>../../res/go-next.bmp</Image>
38
+ <Image MinDPI='120'>../../res/go-next-big.bmp</Image>
39
+ </Command.LargeImages>
40
+ </Command>
41
+ </Application.Commands>
42
+
43
+ <Application.Views>
44
+ <Ribbon>
45
+ <Ribbon.ApplicationMenu>
46
+ <ApplicationMenu CommandName='cmdAppMenu'>
47
+ <MenuGroup Class='MajorItems'>
48
+ <Button CommandName='cmdItem1' />
49
+ </MenuGroup>
50
+ </ApplicationMenu>
51
+ </Ribbon.ApplicationMenu>
52
+
53
+ <Ribbon.QuickAccessToolbar>
54
+ <QuickAccessToolbar CommandName='cmdQAT'>
55
+ <QuickAccessToolbar.ApplicationDefaults>
56
+ <Button CommandName='cmdItem1' />
57
+ <Button CommandName='cmdButton1' />
58
+ </QuickAccessToolbar.ApplicationDefaults>
59
+ </QuickAccessToolbar>
60
+ </Ribbon.QuickAccessToolbar>
61
+
62
+ <Ribbon.Tabs>
63
+ <Tab CommandName='cmdTab1'>
64
+ <Group CommandName='cmdTab1Group1' SizeDefinition='OneButton'>
65
+ <Button CommandName='cmdButton1' />
66
+ </Group>
67
+ </Tab>
68
+ </Ribbon.Tabs>
69
+ </Ribbon>
70
+ </Application.Views>
71
+ </Application>
data/lib/windows_com.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative 'windows_com/common'
2
2
  require_relative 'windows_com/libc'
3
+ require_relative 'windows_com/shlwapi'
3
4
  require_relative 'windows_com/ole'
4
5
  require_relative 'windows_com/oleaut'
@@ -1,12 +1,21 @@
1
1
  require 'ffi'
2
2
 
3
- WINDOWS_COM_VERSION = '1.0.0'
3
+ WINDOWS_COM_VERSION = '2.0.0'
4
4
 
5
5
  WINDOWS_COM_OLE_INIT = true unless defined?(WINDOWS_COM_OLE_INIT)
6
+ WINDOWS_COM_TRACE_CALLBACK_REFCOUNT = false unless defined?(WINDOWS_COM_TRACE_CALLBACK_REFCOUNT)
6
7
 
7
8
  module WindowsCOM
8
9
  extend FFI::Library
9
10
 
11
+ def UsingCOMObjects(*objs)
12
+ yield(*objs)
13
+ ensure
14
+ objs.each { |obj|
15
+ obj.Release
16
+ }
17
+ end
18
+
10
19
  def DetonateHresult(name, *args)
11
20
  hresult = send(name, *args)
12
21
  failed = FAILED(hresult)
@@ -18,7 +27,48 @@ module WindowsCOM
18
27
  yield hresult if failed && block_given?
19
28
  end
20
29
 
30
+ module FFIStructMemoryEquality
31
+ def ==(other)
32
+ WindowsCOM.windows_com_memcmp(self, other, self.size) == 0
33
+ end
34
+ end
35
+
36
+ module FFIStructAnonymousAccess
37
+ def [](k)
38
+ if members.include?(k)
39
+ super
40
+ elsif self[:_].members.include?(k)
41
+ self[:_][k]
42
+ else
43
+ self[:_][:_][k]
44
+ end
45
+ end
46
+
47
+ def []=(k, v)
48
+ if members.include?(k)
49
+ super
50
+ elsif self[:_].members.include?(k)
51
+ self[:_][k] = v
52
+ else
53
+ self[:_][:_][k] = v
54
+ end
55
+ end
56
+ end
57
+
58
+ # use with extend
59
+ module VariantBasicCreation
60
+ def [](type, field, val)
61
+ var = new
62
+
63
+ var[:vt] = type
64
+ var[field] = val
65
+
66
+ var
67
+ end
68
+ end
69
+
21
70
  module_function \
71
+ :UsingCOMObjects,
22
72
  :DetonateHresult
23
73
 
24
74
  S_OK = 0
@@ -60,38 +110,39 @@ module WindowsCOM
60
110
  :HRESULT_FROM_WIN32
61
111
 
62
112
  class GUID < FFI::Struct
113
+ include FFIStructMemoryEquality
114
+
63
115
  layout \
64
116
  :Data1, :ulong,
65
117
  :Data2, :ushort,
66
118
  :Data3, :ushort,
67
119
  :Data4, [:uchar, 8]
68
- end
69
120
 
70
- def GUIDFromString(str)
71
- raise 'Bad GUID format' unless str =~ /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i
121
+ def self.[](str)
122
+ raise 'Bad GUID format' unless str =~ /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i
72
123
 
73
- guid = GUID.new
124
+ guid = new
74
125
 
75
- guid[:Data1] = str[0, 8].to_i(16)
76
- guid[:Data2] = str[9, 4].to_i(16)
77
- guid[:Data3] = str[14, 4].to_i(16)
78
- guid[:Data4][0] = str[19, 2].to_i(16)
79
- guid[:Data4][1] = str[21, 2].to_i(16)
80
- str[24, 12].split('').each_slice(2).with_index { |a, i|
81
- guid[:Data4][i + 2] = a.join('').to_i(16)
82
- }
126
+ guid[:Data1] = str[0, 8].to_i(16)
127
+ guid[:Data2] = str[9, 4].to_i(16)
128
+ guid[:Data3] = str[14, 4].to_i(16)
129
+ guid[:Data4][0] = str[19, 2].to_i(16)
130
+ guid[:Data4][1] = str[21, 2].to_i(16)
131
+ str[24, 12].split('').each_slice(2).with_index { |a, i|
132
+ guid[:Data4][i + 2] = a.join('').to_i(16)
133
+ }
83
134
 
84
- guid
85
- end
135
+ guid
136
+ end
86
137
 
87
- def GUIDEqual(guid1, guid2)
88
- windows_com_memcmp(guid1, guid2, GUID.size) == 0
138
+ def to_s
139
+ format '%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X',
140
+ self[:Data1], self[:Data2], self[:Data3],
141
+ self[:Data4][0], self[:Data4][1],
142
+ self[:Data4][2], self[:Data4][3], self[:Data4][4], self[:Data4][5], self[:Data4][6], self[:Data4][7]
143
+ end
89
144
  end
90
145
 
91
- module_function \
92
- :GUIDFromString,
93
- :GUIDEqual
94
-
95
146
  class COMVptr_ < FFI::Struct
96
147
  layout \
97
148
  :lpVtbl, :pointer
@@ -100,7 +151,7 @@ module WindowsCOM
100
151
  module COMVtbl_
101
152
  def self.[](parent_vtbl, spec)
102
153
  spec.each { |name, sig|
103
- sig[0].unshift(:pointer) # prepend *this* pointer
154
+ sig[0].unshift(:pointer) # prepend *this* pointer to FFI func signature
104
155
  }
105
156
 
106
157
  Class.new(FFI::Struct) {
@@ -124,10 +175,10 @@ module WindowsCOM
124
175
  def self.[](vtbl, siid)
125
176
  Class.new {
126
177
  const_set :Vtbl, vtbl
127
- const_set :IID, WindowsCOM::GUIDFromString(siid)
178
+ const_set :IID, WindowsCOM::GUID[siid]
128
179
 
129
180
  def initialize(pointer)
130
- @vptr = COMVptr_.new(pointer)
181
+ @vptr = WindowsCOM::COMVptr_.new(pointer)
131
182
  @vtbl = self.class::Vtbl.new(@vptr[:lpVtbl])
132
183
  end
133
184
 
@@ -158,11 +209,11 @@ module WindowsCOM
158
209
  module COMFactory
159
210
  def self.[](iface, sclsid)
160
211
  Class.new(iface) {
161
- const_set :CLSID, WindowsCOM::GUIDFromString(sclsid)
212
+ const_set :CLSID, WindowsCOM::GUID[sclsid]
162
213
 
163
- def initialize(clsctx = CLSCTX_INPROC)
214
+ def initialize(clsctx = WindowsCOM::CLSCTX_INPROC)
164
215
  FFI::MemoryPointer.new(:pointer) { |ppv|
165
- DetonateHresult(:CoCreateInstance,
216
+ WindowsCOM::DetonateHresult(:CoCreateInstance,
166
217
  self.class::CLSID, nil, clsctx, self.class::IID, ppv
167
218
  )
168
219
 
@@ -172,4 +223,74 @@ module WindowsCOM
172
223
  }
173
224
  end
174
225
  end
226
+
227
+ module COMCallback
228
+ def self.[](iface)
229
+ Class.new(iface) {
230
+ def initialize
231
+ @vtbl = self.class::Vtbl.new
232
+ @vtbl.members.each { |name|
233
+ @vtbl[name] = instance_variable_set("@__ffi_func__#{name}",
234
+ FFI::Function.new(*@vtbl.class::Spec[name].reverse, convention: :stdcall) { |*args|
235
+ __send__(name, *args[1..-1]) # remove *this* pointer from Ruby meth call
236
+ }
237
+ )
238
+ }
239
+
240
+ @vptr = WindowsCOM::COMVptr_.new
241
+ @vptr[:lpVtbl] = @vtbl
242
+
243
+ @refc = 0
244
+ AddRef()
245
+ end
246
+
247
+ attr_reader :refc
248
+
249
+ def QueryInterface(iid, ppv)
250
+ unless self.class::IID == iid || WindowsCOM::IUnknown::IID == iid
251
+ STDERR.puts "#{self}.#{__method__} called with unsupported interface id (IID: #{iid})" if $DEBUG
252
+
253
+ ppv.write_pointer(0)
254
+ return WindowsCOM::E_NOINTERFACE
255
+ end
256
+
257
+ ppv.write_pointer(@vptr)
258
+ AddRef()
259
+ WindowsCOM::S_OK
260
+ end
261
+
262
+ def AddRef
263
+ @refc += 1
264
+
265
+ STDERR.puts "#{self}.#{__method__} (@refc: #{@refc})" if
266
+ $DEBUG || WINDOWS_COM_TRACE_CALLBACK_REFCOUNT
267
+
268
+ @refc
269
+ end
270
+
271
+ def Release
272
+ @refc -= 1
273
+
274
+ STDERR.puts "#{self}.#{__method__} (@refc: #{@refc})" if
275
+ $DEBUG || WINDOWS_COM_TRACE_CALLBACK_REFCOUNT
276
+
277
+ if @refc == 0
278
+ @vtbl.pointer.free
279
+ @vptr.pointer.free
280
+
281
+ STDERR.puts "#{self} refcount is 0, #{@vtbl} and #{@vptr} freed" if
282
+ $DEBUG || WINDOWS_COM_TRACE_CALLBACK_REFCOUNT
283
+ end
284
+
285
+ @refc
286
+ end
287
+
288
+ (self::Vtbl.members - WindowsCOM::IUnknown::Vtbl.members).each { |name|
289
+ define_method(name) { |*args|
290
+ WindowsCOM::E_NOTIMPL
291
+ }
292
+ }
293
+ }
294
+ end
295
+ end
175
296
  end
@@ -1,6 +1,7 @@
1
1
  if __FILE__ == $0
2
2
  require_relative 'common'
3
3
  require_relative 'libc'
4
+ require_relative 'shlwapi'
4
5
  end
5
6
 
6
7
  module WindowsCOM
@@ -31,6 +32,8 @@ module WindowsCOM
31
32
  attach_function :CoTaskMemFree, [:pointer], :void
32
33
 
33
34
  class LARGE_INTEGER < FFI::Union
35
+ include FFIStructAnonymousAccess
36
+
34
37
  layout \
35
38
  :_, Class.new(FFI::Struct) {
36
39
  layout \
@@ -42,6 +45,8 @@ module WindowsCOM
42
45
  end
43
46
 
44
47
  class ULARGE_INTEGER < FFI::Union
48
+ include FFIStructAnonymousAccess
49
+
45
50
  layout \
46
51
  :_, Class.new(FFI::Struct) {
47
52
  layout \
@@ -134,6 +139,9 @@ module WindowsCOM
134
139
  VT_TYPEMASK = 0xff
135
140
 
136
141
  class VARIANT < FFI::Union
142
+ extend VariantBasicCreation
143
+ include FFIStructAnonymousAccess
144
+
137
145
  layout \
138
146
  :_, Class.new(FFI::Struct) {
139
147
  layout \
@@ -200,12 +208,33 @@ module WindowsCOM
200
208
  end
201
209
 
202
210
  class PROPERTYKEY < FFI::Struct
211
+ include FFIStructMemoryEquality
212
+
203
213
  layout \
204
214
  :fmtid, GUID,
205
215
  :pid, :ulong
216
+
217
+ def self.[](type, index)
218
+ propkey = new
219
+
220
+ propkey[:pid] = type
221
+
222
+ guid = propkey[:fmtid]
223
+ guid[:Data1] = 0x00000000 + index
224
+ guid[:Data2] = 0x7363
225
+ guid[:Data3] = 0x696e
226
+ [0x84, 0x41, 0x79, 0x8a, 0xcf, 0x5a, 0xeb, 0xb7].each_with_index { |part, i|
227
+ guid[:Data4][i] = part
228
+ }
229
+
230
+ propkey
231
+ end
206
232
  end
207
233
 
208
234
  class PROPVARIANT < FFI::Union
235
+ extend VariantBasicCreation
236
+ include FFIStructAnonymousAccess
237
+
209
238
  layout \
210
239
  :_, Class.new(FFI::Struct) {
211
240
  layout \
@@ -1,6 +1,7 @@
1
1
  if __FILE__ == $0
2
2
  require_relative 'common'
3
3
  require_relative 'libc'
4
+ require_relative 'shlwapi'
4
5
  require_relative 'ole'
5
6
  end
6
7
 
@@ -33,6 +34,8 @@ module WindowsCOM
33
34
  attach_function :SafeArrayAccessData, [:pointer, :pointer], :long
34
35
  attach_function :SafeArrayUnaccessData, [:pointer], :long
35
36
 
37
+ attach_function :VariantClear, [:pointer], :long
38
+
36
39
  OLEIVERB_PRIMARY = 0
37
40
  OLEIVERB_SHOW = -1
38
41
  OLEIVERB_OPEN = -2
@@ -0,0 +1,11 @@
1
+ if __FILE__ == $0
2
+ require_relative 'common'
3
+ require_relative 'libc'
4
+ end
5
+
6
+ module WindowsCOM
7
+ ffi_lib 'shlwapi'
8
+ ffi_convention :stdcall
9
+
10
+ attach_function :SHStrDup, :SHStrDupW, [:string, :buffer_out], :long
11
+ end
data/screenshot.png ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: windows_com
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radoslav Peev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-30 00:00:00.000000000 Z
11
+ date: 2017-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -35,12 +35,17 @@ files:
35
35
  - README.md
36
36
  - RELNOTES.md
37
37
  - examples/DesktopGadget.rbw
38
+ - examples/UIRibbon/Command.dll
39
+ - examples/UIRibbon/Command.rb
40
+ - examples/UIRibbon/Command.rbw
41
+ - examples/UIRibbon/Command.xml
38
42
  - lib/windows_com.rb
39
43
  - lib/windows_com/common.rb
40
- - lib/windows_com/cruft.rb
41
44
  - lib/windows_com/libc.rb
42
45
  - lib/windows_com/ole.rb
43
46
  - lib/windows_com/oleaut.rb
47
+ - lib/windows_com/shlwapi.rb
48
+ - screenshot.png
44
49
  homepage: https://github.com/rpeev/windows_com
45
50
  licenses:
46
51
  - MIT
@@ -1,173 +0,0 @@
1
- __END__
2
- module COMHelpers
3
- def QueryInstance(klass)
4
- instance = nil
5
-
6
- FFI::MemoryPointer.new(:pointer) { |ppv|
7
- QueryInterface(klass::IID, ppv)
8
-
9
- instance = klass.new(ppv.read_pointer)
10
- }
11
-
12
- begin
13
- yield instance; return self
14
- ensure
15
- instance.Release
16
- end if block_given?
17
-
18
- instance
19
- end
20
-
21
- def UseInstance(klass, name, *args)
22
- instance = nil
23
-
24
- FFI::MemoryPointer.new(:pointer) { |ppv|
25
- send(name, *args, klass::IID, ppv)
26
-
27
- yield instance = klass.new(ppv.read_pointer)
28
- }
29
-
30
- self
31
- ensure
32
- instance.Release if instance
33
- end
34
- end
35
-
36
- module COMCallback
37
- def self.[](iface)
38
- Class.new(FFI::Struct) {
39
- send(:include, COMHelpers)
40
-
41
- layout \
42
- :lpVtbl, :pointer
43
-
44
- def initialize(opts = {})
45
- @vtbl, @refc = iface::VTBL.new, 1
46
-
47
- @vtbl.members.each { |name|
48
- @vtbl[name] = instance_variable_set("@fn#{name}",
49
- FFI::Function.new(*@vtbl.class::SPEC[name].reverse, convention: :stdcall) { |*args|
50
- send(name, *args[1..-1])
51
- }
52
- )
53
- }
54
-
55
- self[:lpVtbl] = @vtbl
56
-
57
- begin
58
- yield self
59
- ensure
60
- Release()
61
- end if block_given?
62
- end
63
-
64
- attr_reader :vtbl, :refc
65
-
66
- def QueryInterface(riid, ppv)
67
- if [IUnknown::IID, iface::IID].any? { |iid| windows_com_memcmp(riid, iid, iid.size) == 0 }
68
- ppv.write_pointer(self)
69
- else
70
- ppv.write_pointer(0); return E_NOINTERFACE
71
- end
72
-
73
- AddRef(); S_OK
74
- end
75
-
76
- def AddRef
77
- @refc += 1
78
- end
79
-
80
- def Release
81
- @refc -= 1
82
- end
83
-
84
- (iface::VTBL.members - IUnknown::VTBL.members).each { |name|
85
- define_method(name) { |*args|
86
- E_NOTIMPL
87
- }
88
- }
89
- }
90
- end
91
- end
92
-
93
- module AnonymousFFIStructSupport
94
- def [](k)
95
- if members.include?(k)
96
- super
97
- elsif self[:_].members.include?(k)
98
- self[:_][k]
99
- else
100
- self[:_][:_][k]
101
- end
102
- end
103
-
104
- def []=(k, v)
105
- if members.include?(k)
106
- super
107
- elsif self[:_].members.include?(k)
108
- self[:_][k] = v
109
- else
110
- self[:_][:_][k] = v
111
- end
112
- end
113
- end
114
-
115
- # PROPERTYKEY
116
- def self.[](type, index)
117
- new.tap { |key|
118
- key[:fmtid].tap { |guid|
119
- guid[:Data1] = 0x00000000 + index
120
- guid[:Data2] = 0x7363
121
- guid[:Data3] = 0x696e
122
- [0x84, 0x41, 0x79, 0x8a, 0xcf, 0x5a, 0xeb, 0xb7].each_with_index { |part, i|
123
- guid[:Data4][i] = part
124
- }
125
- }
126
-
127
- key[:pid] = type
128
- }
129
- end
130
-
131
- # PROPVARIANT
132
- def ==(other) windows_com_memcmp(other, self, size) == 0 end
133
-
134
- def self.[](t, *v) new.tap { |var| var.send("#{t}=", *v) } end
135
-
136
- def bool; raise 'Wrong type tag.' unless self[:vt] == VT_BOOL; self[:boolVal] != 0 end
137
- def bool=(bool) self[:vt] = VT_BOOL; self[:boolVal] = (bool) ? -1 : 0 end
138
-
139
- def int; raise 'Wrong type tag.' unless self[:vt] == VT_I4; self[:intVal] end
140
- def int=(int) self[:vt] = VT_I4; self[:intVal] = int end
141
-
142
- def uint; raise 'Wrong type tag.' unless self[:vt] == VT_UI4; self[:uintVal] end
143
- def uint=(uint) self[:vt] = VT_UI4; self[:uintVal] = uint end
144
-
145
- def unknown
146
- raise 'Wrong type tag.' unless self[:vt] == VT_UNKNOWN
147
-
148
- yield Unknown.new(self[:punkVal])
149
- ensure
150
- Windows.PropVariantClear(self)
151
- end
152
-
153
- def unknown=(unknown) self[:vt] = VT_UNKNOWN; self[:punkVal] = unknown.pointer; unknown.AddRef end
154
-
155
- def wstring; raise 'Wrong type tag.' unless self[:vt] == VT_LPWSTR; Windows.WCSTOMBS(self[:pwszVal]) end
156
-
157
- def wstring=(string)
158
- self[:vt] = VT_LPWSTR
159
-
160
- FFI::MemoryPointer.new(:pointer) { |p|
161
- Windows.DetonateHresult(:SHStrDup, string, p)
162
-
163
- self[:pwszVal] = p.read_pointer
164
- }
165
- end
166
-
167
- def decimal
168
- raise 'Wrong type tag.' unless self[:vt] == VT_DECIMAL
169
-
170
- Rational(self[:decVal][:Lo64], 10 ** self[:decVal][:scale]) + self[:decVal][:Hi32]
171
- end
172
-
173
- def decimal=(decimal) self[:vt] = VT_DECIMAL; self[:decVal][:Lo64] = decimal end