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 +4 -4
- data/README.md +3 -1
- data/RELNOTES.md +6 -0
- data/examples/DesktopGadget.rbw +3 -7
- data/examples/UIRibbon/Command.dll +0 -0
- data/examples/UIRibbon/Command.rb +30 -0
- data/examples/UIRibbon/Command.rbw +201 -0
- data/examples/UIRibbon/Command.xml +71 -0
- data/lib/windows_com.rb +1 -0
- data/lib/windows_com/common.rb +148 -27
- data/lib/windows_com/ole.rb +29 -0
- data/lib/windows_com/oleaut.rb +3 -0
- data/lib/windows_com/shlwapi.rb +11 -0
- data/screenshot.png +0 -0
- metadata +8 -3
- data/lib/windows_com/cruft.rb +0 -173
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fd412b3685ad9f5901bd3f89dbf2b6a477fa884
|
4
|
+
data.tar.gz: 29d1847667d2b8e406c09b9fab9afd5785e0f545
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dadc3e3d21b5f52341fee37cee3ed18024d305fc8e547d1adb959dc2d323f3c235a4402d5a115902ddb86c9e9a7fcc717a0e8ca86bdfeed6a3a3ca54519326e2
|
7
|
+
data.tar.gz: 3ddeeffb332b95f21147f2300edca335c5848025850289647b142e56ba3feb01426bda38d845ca0001e9d4d4234f567f5a08461d07fe0ea87364977392f78aee
|
data/README.md
CHANGED
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
|
data/examples/DesktopGadget.rbw
CHANGED
@@ -10,12 +10,8 @@ IDesktopGadget = COMInterface[IUnknown,
|
|
10
10
|
|
11
11
|
DesktopGadget = COMFactory[IDesktopGadget, '924ccc1b-6562-4c85-8657-d177925222b6']
|
12
12
|
|
13
|
-
|
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
|
-
|
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='&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='&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='&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
data/lib/windows_com/common.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
require 'ffi'
|
2
2
|
|
3
|
-
WINDOWS_COM_VERSION = '
|
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
|
-
|
71
|
-
|
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
|
-
|
124
|
+
guid = new
|
74
125
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
85
|
-
|
135
|
+
guid
|
136
|
+
end
|
86
137
|
|
87
|
-
|
88
|
-
|
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::
|
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::
|
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
|
data/lib/windows_com/ole.rb
CHANGED
@@ -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 \
|
data/lib/windows_com/oleaut.rb
CHANGED
@@ -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
|
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:
|
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-
|
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
|
data/lib/windows_com/cruft.rb
DELETED
@@ -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
|