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