1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
require 'ffi'
module Puppet::Util::Windows::COM
extend FFI::Library
ffi_convention :stdcall
S_OK = 0
S_FALSE = 1
def SUCCEEDED(hr) hr >= 0 end
def FAILED(hr) hr < 0 end
module_function :SUCCEEDED, :FAILED
def raise_if_hresult_failed(name, *args)
failed = FAILED(result = send(name, *args)) and raise _("%{name} failed (hresult %{result}).") % { name: name, result: format('%#08x', result) }
result
ensure
yield failed if block_given?
end
module_function :raise_if_hresult_failed
CLSCTX_INPROC_SERVER = 0x1
CLSCTX_INPROC_HANDLER = 0x2
CLSCTX_LOCAL_SERVER = 0x4
CLSCTX_INPROC_SERVER16 = 0x8
CLSCTX_REMOTE_SERVER = 0x10
CLSCTX_INPROC_HANDLER16 = 0x20
CLSCTX_RESERVED1 = 0x40
CLSCTX_RESERVED2 = 0x80
CLSCTX_RESERVED3 = 0x100
CLSCTX_RESERVED4 = 0x200
CLSCTX_NO_CODE_DOWNLOAD = 0x400
CLSCTX_RESERVED5 = 0x800
CLSCTX_NO_CUSTOM_MARSHAL = 0x1000
CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000
CLSCTX_NO_FAILURE_LOG = 0x4000
CLSCTX_DISABLE_AAA = 0x8000
CLSCTX_ENABLE_AAA = 0x10000
CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000
CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000
CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000
CLSCTX_ENABLE_CLOAKING = 0x100000
CLSCTX_PS_DLL = -0x80000000
CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER
CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx
# HRESULT CoCreateInstance(
# _In_ REFCLSID rclsid,
# _In_ LPUNKNOWN pUnkOuter,
# _In_ DWORD dwClsContext,
# _In_ REFIID riid,
# _Out_ LPVOID *ppv
# );
ffi_lib :ole32
attach_function_private :CoCreateInstance,
[:pointer, :lpunknown, :dword, :pointer, :lpvoid], :hresult
# code modified from Unknownr project https://github.com/rpeev/Unknownr
# licensed under MIT
module Interface
def self.[](*args)
spec, iid, *ifaces = args.reverse
spec.each { |name, signature| signature[0].unshift(:pointer) }
Class.new(FFI::Struct) do
const_set(:IID, iid)
vtable = Class.new(FFI::Struct) do
vtable_hash = Hash[(ifaces.map { |iface| iface::VTBL::SPEC.to_a } << spec.to_a).flatten(1)]
const_set(:SPEC, vtable_hash)
layout(
*self::SPEC.map { |name, signature| [name, callback(*signature)] }.flatten
)
end
const_set(:VTBL, vtable)
layout \
:lpVtbl, :pointer
end
end
end
module Helpers
def QueryInstance(klass)
instance = nil
FFI::MemoryPointer.new(:pointer) do |ppv|
QueryInterface(klass::IID, ppv)
instance = klass.new(ppv.read_pointer)
end
begin
yield instance
return self
ensure
instance.Release
end if block_given?
instance
end
def UseInstance(klass, name, *args)
instance = nil
FFI::MemoryPointer.new(:pointer) do |ppv|
send(name, *args, ppv)
yield instance = klass.new(ppv.read_pointer)
end
self
ensure
instance.Release if instance && ! instance.null?
end
end
module Instance
def self.[](iface)
Class.new(iface) do
send(:include, Helpers)
def initialize(pointer)
self.pointer = pointer
@vtbl = self.class::VTBL.new(self[:lpVtbl])
end
attr_reader :vtbl
self::VTBL.members.each do |name|
define_method(name) do |*args|
if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args))
raise Puppet::Util::Windows::Error.new(_("Failed to call %{klass}::%{name} with HRESULT: %{result}.") % { klass: self, name: name, result: result }, result)
end
result
end
end
layout \
:lpVtbl, :pointer
end
end
end
module Factory
def self.[](iface, clsid)
Class.new(iface) do
send(:include, Helpers)
const_set(:CLSID, clsid)
def initialize(opts = {})
@opts = opts
@opts[:clsctx] ||= CLSCTX_INPROC_SERVER
FFI::MemoryPointer.new(:pointer) do |ppv|
hr = Puppet::Util::Windows::COM.CoCreateInstance(self.class::CLSID, FFI::Pointer::NULL, @opts[:clsctx], self.class::IID, ppv)
if Puppet::Util::Windows::COM.FAILED(hr)
raise _("CoCreateInstance failed (%{klass}).") % { klass: self.class }
end
self.pointer = ppv.read_pointer
end
@vtbl = self.class::VTBL.new(self[:lpVtbl])
end
attr_reader :vtbl
self::VTBL.members.each do |name|
define_method(name) do |*args|
if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args))
raise Puppet::Util::Windows::Error.new(_("Failed to call %{klass}::%{name} with HRESULT: %{result}.") % { klass: self, name: name, result: result }, result)
end
result
end
end
layout \
:lpVtbl, :pointer
end
end
end
IUnknown = Interface[
FFI::WIN32::GUID['00000000-0000-0000-C000-000000000046'],
QueryInterface: [[:pointer, :pointer], :hresult],
AddRef: [[], :win32_ulong],
Release: [[], :win32_ulong]
]
Unknown = Instance[IUnknown]
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms678543(v=vs.85).aspx
# HRESULT CoInitialize(
# _In_opt_ LPVOID pvReserved
# );
ffi_lib :ole32
attach_function_private :CoInitialize, [:lpvoid], :hresult
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms688715(v=vs.85).aspx
# void CoUninitialize(void);
ffi_lib :ole32
attach_function_private :CoUninitialize, [], :void
def InitializeCom
raise_if_hresult_failed(:CoInitialize, FFI::Pointer::NULL)
at_exit { CoUninitialize() }
end
module_function :InitializeCom
end
|