windows_com 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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