-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Wbemcli classes needed to query WMI #994
Conversation
355b21e
to
3afe6ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a first pass - could you please have a look at the comments?
HRESULT CoSetProxyBlanket(Pointer pProxy, // | ||
int dwAuthnSvc, // | ||
int dwAuthzSvc, // | ||
BSTR pServerPrincName, // OLECHAR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beware! OLECHAR is not a BSTR. The parameter pServerPrincName
is a OLECHAR
array, so I'd bind this as a LPOLESTR
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MS is evil! I saw typedef OLECHAR * BSTR
and thought I was good. I only ever call that function with null
so this never reared its ugly head for me... will fix.
* | ||
* E_OUT_OF_MEMORY Out of memory. | ||
*/ | ||
HRESULT CoInitializeSecurity(Pointer pSecDesc, NativeLong cAuthSvc, Pointer asAuthSvc, Pointer pReserved1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pSecDec
is a PSECURITY_DESCRIPTOR
, so could WinNT.SECURITY_DESCRIPTOR
be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Another case where I always used null
and was lazy.
* | ||
* E_OUT_OF_MEMORY Out of memory. | ||
*/ | ||
HRESULT CoInitializeSecurity(Pointer pSecDesc, NativeLong cAuthSvc, Pointer asAuthSvc, Pointer pReserved1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cAuthSvc
is a LONG
-- for the windows data types, microsoft says
LONG A 32-bit signed integer. The range is 2147483648 through 2147483647 decimal.
So int should be a save binding.
* | ||
* E_INVALIDARG One or more arguments is invalid. | ||
*/ | ||
HRESULT CoSetProxyBlanket(Pointer pProxy, // |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should pProxy be Pointer
or Unknown
or IUnknown
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the pointed-to value of the last argument of IwbemLocator::ConnectServer which is a pointer to an IWbemServices object. In the code I use this Pointer
in the IWbemServices
initializer. Not sure what other mapping it should be.
} | ||
|
||
public HRESULT Get(BSTR wszName, NativeLong lFlags, VARIANT.ByReference pVal, Pointer pvtType, | ||
LongByReference plFlavor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the documents from microsoft this is the definition:
HRESULT Get(
LPCWSTR wszName,
long lFlags,
VARIANT *pVal,
CIMTYPE *pType,
long *plFlavor
);
So I would use String fro wszName
and wrap it into a WString
for the call. Microsoft defines the sizes of the base types in:
https://msdn.microsoft.com/de-de/library/cc953fe1.aspx
So lFlags
can be bound as int and plFlavor
is a IntByReference
. The LongByReference
is to big. pType
(it is not pvtType) is defined as:
typedef long CIMTYPE;
So it is not a pointer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't CIMTYPE *pType
mean that pType
is a pointer to CIMTYPE
so a Pointer is valid here (IntByReference
would be better as you note for long *plFlavor
). I have read the value from this call using Pointer.getInt()
and it has been the CIMTYPE.
super(pvInstance); | ||
} | ||
|
||
public HRESULT Next(NativeLong lTimeOut, NativeLong uCount, PointerByReference ppObjects, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as in IWbemClassObject
applies - is it expected, that this method is called on a non windows platform? if not, NativeLong
can be substituted with int
.
// Connect to WMI through the IWbemLocator::ConnectServer method | ||
// Connect to the namespace with the current user and obtain pointer | ||
// pSvc to make IWbemServices calls. | ||
HRESULT hres = loc.ConnectServer(new BSTR(namespace), null, null, null, null, null, null, pSvc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use the BSTR
constructor. BSTR
need to be allocated via OleAuto.INSTANCE.SysAllocString
and they need to be freed with OleAuto.INSTANCE.SysFreeString
.
} | ||
|
||
public HRESULT ConnectServer(BSTR strNetworkResource, BSTR strUser, BSTR strPassword, BSTR strLocale, | ||
NativeLong lSecurityFlags, BSTR strAuthority, Pointer pCtx, PointerByReference ppNamespace) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use String instead of BSTR and allocate + release the BSTRs inside the function (see below for allocation of BSTR).
For pCtx
should this really be a plain Pointer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For pCtx
it would probably be an IWbemContext object. But in all the sample code I have seen on MS sites for WMI connections (both local and remote) it's been null
. Reading the docs, it looks like IUnknown
(or is there a ByReference version of it) may be correct?
/** | ||
* Initializes COM library and sets security to impersonate the local user | ||
*/ | ||
public static void initCOM() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks dangerous if you combine it with other COM calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it looks dangerous but I'm not sure how else to handle it. I can ignore "already initialized" errors but then if I uninit later, it will kill the external program's COM code. If I don't uninit, it's a resource leak.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the user responsible for initializing COM. Else you need to guess how the user will use it. The COM Utilities have offer both options. COMThread handles the creation of a thread for dispatching COM Threads with correctly initialized COM. Factory
uses that, while ObjectFactory
allows creation of call proxies without a different thread.
I found it better to handle COM initialization myself, so I prefer to use the ObjectFactory
.
There is a problem with your code - when calls to the utility methods are done from different threads, COM will only be initialized once. This violates the requirement, that CoInitializeEx
must be called on each thread making COM calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved the COM initialization outside the Util class.
I think I've addressed all the comments except the type mapping for the last argument of |
* Exception encountered in this class | ||
*/ | ||
@SuppressWarnings("serial") | ||
class WbemcliException extends RuntimeException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used in the WbemcliUtil
class, so I would move it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it's used if CoCreateInstance()
fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - thank you for the pointer, I really overlooked that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be fair, there's an argument for just returning HRESULT and throwing the exception from the caller (in the Util method). Let me know if you'd prefer that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
COMBindingBaseObject
uses COMUtils#checkRC
which raises a COMException
on error - that would align Wbemcli
with other COM bindings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good timing, I was actually just about to update this. Based on the above, I'll
- return null from this method if it fails
- handle null in the util class with a
COMException
along with other uses of this exception.
HRESULT hres = Ole32.INSTANCE.CoCreateInstance(CLSID_WbemLocator, null, WTypes.CLSCTX_INPROC_SERVER, | ||
IID_IWbemLocator, pbr); | ||
if (COMUtils.FAILED(hres)) { | ||
Ole32.INSTANCE.CoUninitialize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an artifact from the "handle COM initialization in the calls" approach. There is no reason to uninitialize COM here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I had already removed that, somehow I committed it to a different branch. Same with the removed variables.
public Integer getInteger(T property, int index) { | ||
Object o = this.propertyMap.get(property).get(index); | ||
if (o == null) { | ||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit irritated here - why return a primitive 0 instead of the java null
? I would understand if you return int, but with a nullable type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very good question, and I went back and forth on this implementation several times. My reasoning:
- Requiring the user to null-check every return takes away some of the value of a utility class that provides sensible defaults (zero or empty string). The user still has the ability to test null (and even determine types if not-null) directly using
getValue()
. All the other getters are convenience methods, and I wanted them to be convenient. - The return type should be tied directly to the CIM type.
getInteger()
should only be used foruint16
,sint32
, anduint32
CIM types, all of which suggest a numeric value. The only time we run into an issue is if WMI has no value for that column, and we don't check the type. - I've also considered having different return types based on the CIM Type rather than the value type encoded in the Variant. For example, both
String
anduint64
return as a Variant BSTR, but the latter requires further parsing (to eitherlong
or possibly aBigInteger
orWinNT.LARGE_INTEGER
. All of which returns to the question of how to treat parse exceptions / nulls. I eventually settled on what you see as a happy medium. - If I had my druthers I'd probably use Java 8's
Optional<Integer>
here. But that's not an option (pun intended). - All these numeric values began their life as primitives, from
VARIANT
's various.*value()
methods. They only got boxed when being added to the Lists. When I wrote this, I unboxed and returned primitives, and that's why I initially wrote the defaults. - In many cases in my application I would immediately re-box them to put into another list or map. So since the value was already boxed in the list, I felt returning it as it was (with a simple typecast) was the most direct option and changed it. So the code you see is a relic of the original return type but I still thought the defaults were more useful than null (since the user still has access to test null).
From the perspective of a user, if they want the default 0 it's just
result.getInteger(WmiProperty.WMIFIELD, 0)
If they want a different default:
result.getValue(WmiProperty.WMIFIELD, 0) == null ? -1 : result.getInteger(WmiProperty.WMIFIELD, 0)
If I were to return null instead of a guaranteed numeric it would require the user to always do:
result.getInteger(WmiProperty.WMIFIELD, 0) == null ? 0 : result.getInteger(WmiProperty.WMIFIELD, 0)
Runtime.getRuntime().addShutdownHook(new Thread() { | ||
@Override | ||
public void run() { | ||
OleAuto.INSTANCE.SysFreeString(WQL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not preallocated WQL
and do the allocation in the query. At least I would measure if it really is a significant part of the call cost. This construction looks fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The allocation and release take an average of 33 microseconds for both calls. Assuming (generously) I could kill one of them that'd save 16 microseconds per invocation. So, yeah, not too significant. :)
|
||
// Track initialization of COM and Security | ||
private static boolean comInitialized = false; | ||
private static boolean securityInitialized = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These variables are not used anymore.
import com.sun.jna.ptr.PointerByReference; | ||
|
||
/** | ||
* This header is used by Remote Desktop Services. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?! Not sure what the header wants to tell me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I stole it from here. I guess I can make it more useful.
/** | ||
* Increment the result count by one. | ||
*/ | ||
public void incrementResultCount() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add
is private, this should also be private
OleAuto.INSTANCE.SysFreeString(queryStr); | ||
OleAuto.INSTANCE.SysFreeString(wql); | ||
if (COMUtils.FAILED(hres)) { | ||
svc.Release(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is svc
released here? The value is passed in, so I would keep away from handling it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was released there in the sample code that I ported, although since I've moved out other resource releasing, may as well do that here, as well.
* A copy is also included in the downloadable source code package | ||
* containing JNA, in file "AL2.0". | ||
*/ | ||
package com.sun.jna.platform.win32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are COM bindings could you please move this and the Util class to the com.sun.jna.platform.win32.COM
package?
c5a6ff3
to
8b5c3a3
Compare
I would be willing to merge - shall I? |
Woohoo! Only one other change I was considering: The Thanks as always for the educational code review and your patience. |
OK, ready to merge! If you like my latest commit, include it. If not, back out the last commit and merge! |
Hi Daniel, I had another look at this (sorry for the delay). Looking at the Wbem classes, they look a lot like JDBC and could be wrapped similarly, I did not pursue that further, as that might be overkill. However, I added a few changes, that you might want to consider: https://github.com/matthiasblaesing/jna/commits/dbwiddis-wmi Please have a look at the last three commits of the branch referenced above (If all went well, you should be able to pull directly from it). The first commit adds a bit binding sugar, the second moves WmiQuery more into a object oriented direction and the third fixes the unittests. |
Looks great! I've added your commits to my PR. I submitted one final commit which removes an unused variable and unneeded cast, some unneeded javadoc params , and adds to the Wbemcli class javadocs to better match the MSDN documentation. (And my formatter spaces slightly different than yours.) Tests fine on my system, good to go here! |
Thank you for your work - its appreciated. I added a slight update: I squashed the work into one feature commit and merged that to master. |
No description provided.