From ece5bbbd0e4962f480df1e48118e775bf92a8e30 Mon Sep 17 00:00:00 2001 From: "sokoloff (Alexander)" Date: Thu, 16 Jun 2016 19:59:50 +0300 Subject: [PATCH 1/3] Use _NewEnum because SWbemObjectSet does not have ItemIndex on some systems (Win2003, WinXP). Closes issues #2, #10 and #12. --- wmi.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/wmi.go b/wmi.go index b931ca5..3214224 100644 --- a/wmi.go +++ b/wmi.go @@ -171,19 +171,33 @@ func (c *Client) Query(query string, dst interface{}, connectServerArgs ...inter return err } + enumProperty, err := result.GetProperty("_NewEnum") + if err != nil { + return err + } + defer enumProperty.Clear() + + enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) + if err != nil { + return err + } + if enum == nil { + return fmt.Errorf("can't get IEnumVARIANT, enum is nil") + } + // Initialize a slice with Count capacity dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count))) var errFieldMismatch error - for i := int64(0); i < count; i++ { + for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { + if err != nil { + return err + } + err := func() error { // item is a SWbemObject, but really a Win32_Process - itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) - if err != nil { - return err - } item := itemRaw.ToIDispatch() - defer itemRaw.Clear() + defer item.Release() ev := reflect.New(elemType) if err = c.loadEntity(ev.Interface(), item); err != nil { From 04850ec8cb7d7abe649e84596e564910521c22b4 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Thu, 26 May 2016 11:44:21 +0000 Subject: [PATCH 2/3] Correct threading model used by wmi calls WbemScripting is apartment threaded according to its registry entries so switch multi-threaded OLE to apartment threaded enabling the global lock to be removed. Correct missing CoUninitialize call when CoInitializeEx returns S_FALSE as documented here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms695279(v=vs.85).aspx Also: * Cleanup OleError checking in Query. * Handle nil return from CreateObject by returning ErrNilCreateObject before it causes a panic. This has been seen in rare cases in production when built under go v1.6. --- wmi.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/wmi.go b/wmi.go index 3214224..c7f4fad 100644 --- a/wmi.go +++ b/wmi.go @@ -34,7 +34,6 @@ import ( "runtime" "strconv" "strings" - "sync" "time" "github.com/go-ole/go-ole" @@ -45,9 +44,14 @@ var l = log.New(os.Stdout, "", log.LstdFlags) var ( ErrInvalidEntityType = errors.New("wmi: invalid entity type") - lock sync.Mutex + // ErrNilCreateObject is the error returned if CreateObject returns nil even + // if the error was nil. + ErrNilCreateObject = errors.New("wmi: create object returned nil") ) +// S_FALSE is returned by CoInitializeEx if it was already called on this thread. +const S_FALSE = 0x00000001 + // QueryNamespace invokes Query with the given namespace on the local machine. func QueryNamespace(query string, dst interface{}, namespace string) error { return Query(query, dst, nil, namespace) @@ -119,28 +123,23 @@ func (c *Client) Query(query string, dst interface{}, connectServerArgs ...inter return ErrInvalidEntityType } - lock.Lock() - defer lock.Unlock() runtime.LockOSThread() defer runtime.UnlockOSThread() - err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) + err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED) if err != nil { - oleerr := err.(*ole.OleError) - // S_FALSE = 0x00000001 // CoInitializeEx was already called on this thread - if oleerr.Code() != ole.S_OK && oleerr.Code() != 0x00000001 { + oleCode := err.(*ole.OleError).Code() + if oleCode != ole.S_OK && oleCode != S_FALSE { return err } - } else { - // Only invoke CoUninitialize if the thread was not initizlied before. - // This will allow other go packages based on go-ole play along - // with this library. - defer ole.CoUninitialize() } + defer ole.CoUninitialize() unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") if err != nil { return err + } else if unknown == nil { + return ErrNilCreateObject } defer unknown.Release() From d4ae5b448be53329ab38edcbc8aa1d196fc87f21 Mon Sep 17 00:00:00 2001 From: Greg Bray Date: Tue, 26 Jul 2016 18:57:16 -0600 Subject: [PATCH 3/3] Partial Revert "Correct threading model used by wmi calls" This reverts part of commit 04850ec8cb7d7abe649e84596e564910521c22b4. using COINIT_APARTMENTTHREADED caused a memory leak. See https://github.com/multiplay/wmi/pull/8 --- wmi.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wmi.go b/wmi.go index c7f4fad..fc62377 100644 --- a/wmi.go +++ b/wmi.go @@ -34,6 +34,7 @@ import ( "runtime" "strconv" "strings" + "sync" "time" "github.com/go-ole/go-ole" @@ -47,6 +48,7 @@ var ( // ErrNilCreateObject is the error returned if CreateObject returns nil even // if the error was nil. ErrNilCreateObject = errors.New("wmi: create object returned nil") + lock sync.Mutex ) // S_FALSE is returned by CoInitializeEx if it was already called on this thread. @@ -123,10 +125,12 @@ func (c *Client) Query(query string, dst interface{}, connectServerArgs ...inter return ErrInvalidEntityType } + lock.Lock() + defer lock.Unlock() runtime.LockOSThread() defer runtime.UnlockOSThread() - err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED) + err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) if err != nil { oleCode := err.(*ole.OleError).Code() if oleCode != ole.S_OK && oleCode != S_FALSE {