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 +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
|