Skip to content
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 Windows Version Helper functions #1050

Merged
merged 3 commits into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ NOTE: as of JNA 4.0, JNA is now dual-licensed under LGPL and AL 2.0 (see LICENSE

NOTE: JNI native support is typically incompatible between minor versions, and almost always incompatible between major versions.

Next release (5.2.1)
Next release (5.3.0)
====================

Features
--------
* [#1050](https://github.com/java-native-access/jna/pull/1050): Add `c.s.j.p.win32.VersionHelpers` and supporting functions - [@dbwiddis](https://github.com/dbwiddis).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the version behind "Next release" to "5.3.0", as it will be a feature release with this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought so and somewhat expected this comment. :)


Bug Fixes
---------
Expand Down
67 changes: 67 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,73 @@ boolean CreateProcessW(String lpApplicationName, char[] lpCommandLine,
*/
boolean GetVersionEx(OSVERSIONINFOEX lpVersionInfo);

/**
* Compares a set of operating system version requirements to the
* corresponding values for the currently running version of the system.
* This function is subject to manifest-based behavior.
*
* @param lpVersionInformation
* A pointer to an {@link WinNT#OSVERSIONINFOEX} structure
* containing the operating system version requirements to
* compare. The {@code dwTypeMask} parameter indicates the
* members of this structure that contain information to compare.
* <p>
* You must set the {@code dwOSVersionInfoSize} member of this
* structure to {@code sizeof(OSVERSIONINFOEX)}. You must also
* specify valid data for the members indicated by
* {@code dwTypeMask}. The function ignores structure members for
* which the corresponding {@code dwTypeMask} bit is not set.
* @param dwTypeMask
* A mask that indicates the members of the
* {@link WinNT#OSVERSIONINFOEX} structure to be tested.
* @param dwlConditionMask
* The type of comparison to be used for each
* {@code lpVersionInfo} member being compared. To build this
* value, call the {@link #VerSetConditionMask} function once for
* each {@link WinNT#OSVERSIONINFOEX} member being compared.
* @return If the currently running operating system satisfies the specified
* requirements, the return value is a nonzero value.
* <p>
* If the current system does not satisfy the requirements, the
* return value is zero and {@link #GetLastError()} returns
* {@link WinError#ERROR_OLD_WIN_VERSION}.
* <p>
* If the function fails, the return value is zero and
* {@link #GetLastError()} returns an error code other than
* {@link WinError#ERROR_OLD_WIN_VERSION}.
*/
boolean VerifyVersionInfoW(OSVERSIONINFOEX lpVersionInformation, int dwTypeMask, long dwlConditionMask);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be VerifyVersionInfo -- but that would not work, because OSVERSIONINFOEX is only correctly bound for unicode?! Crap...

Copy link
Contributor Author

@dbwiddis dbwiddis Jan 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried VerifyVersionInfo but got UnsatisfiedLinkError: Error looking up function 'VerifyVersionInfo'. Both the W and A variants worked individually in this case because we're only using the numeric portions of OSVERSIONINFOEX. The inline functions in the header file use the W version, so that's the one I implemented. Should I add the A version as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er, now I see what you mean. This method should properly use OSVERSIONINFOEXW which is not defined (but the OSVERSIONINFOEX defined is the W version.)

I had to go look at git blame to make sure I wasn't at fault there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm thinking about the correct way to handle this. One thought was to copy the OSVERSIONINFOEX structure to OSVERSIONINFOEXW (have one extend the other) and mark the non-W version as deprecated. But there are many other functions which also call it, so we'd have to change all of them as well. Even though per the Windows documentation, all these old functions are also deprecated in favor of the Version Helper functions, which currently still refer to this structure... and around in circles we go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see a good option to fix this without breaking API, so at this point in time, I think binding it as VerifyVersionInfoW is a good solution.


/**
* Sets the bits of a 64-bit value to indicate the comparison operator to
* use for a specified operating system version attribute. This function is
* used to build the {@code dwlConditionMask} parameter of the
* {@link #VerifyVersionInfo} function.
*
* @param conditionMask
* A value to be passed as the {@code dwlConditionMask} parameter
* of the {@link #VerifyVersionInfo} function. The function
* stores the comparison information in the bits of this
* variable.
* <p>
* Before the first call to {@link #VerSetConditionMask},
* initialize this variable to zero. For subsequent calls, pass
* in the variable used in the previous call.
* @param typeMask
* A mask that indicates the member of the
* {@link WinNT#OSVERSIONINFOEX} structure whose comparison
* operator is being set. This value corresponds to one of the
* bits specified in the {@code dwTypeMask} parameter for the
* {@link #VerifyVersionInfo} function.
* @param condition
* The operator to be used for the comparison. The
* {@link #VerifyVersionInfo} function uses this operator to
* compare a specified attribute value to the corresponding value
* for the currently running system.
* @return The function returns the condition mask value.
*/
long VerSetConditionMask(long conditionMask, int typeMask, byte condition);

/**
* The GetSystemInfo function returns information about the current system.
*
Expand Down
202 changes: 202 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/VersionHelpers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/* Copyright (c) 2019 Daniel Widdis, All Rights Reserved
*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package com.sun.jna.platform.win32;

import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.WORD;
import com.sun.jna.platform.win32.WinNT.OSVERSIONINFOEX;

/**
* The following functions can be used to determine the current operating system
* version or identify whether it is a Windows or Windows Server release. These
* functions provide simple tests that use the VerifyVersionInfo function and
* the recommended greater than or equal to comparisons that are proven as a
* robust means to determine the operating system version.
*/
public class VersionHelpers {

/**
* This function is useful in confirming a version of Windows Server that
* doesn't share a version number with a client release. You should only use
* this function if the other provided version helper functions do not fit
* your scenario.
*
* @param wMajorVersion
* The major version to test
* @param wMinorVersion
* The minor version to test
* @param wServicePackMajor
* The service pack to test
* @return True if the current OS version matches, or is greater than, the
* provided version information.
*/
public static boolean IsWindowsVersionOrGreater(int wMajorVersion, int wMinorVersion, int wServicePackMajor) {
OSVERSIONINFOEX osvi = new OSVERSIONINFOEX();
osvi.dwOSVersionInfoSize = new DWORD(osvi.size());
osvi.dwMajorVersion = new DWORD(wMajorVersion);
osvi.dwMinorVersion = new DWORD(wMinorVersion);
osvi.wServicePackMajor = new WORD(wServicePackMajor);

long dwlConditionMask = 0;
dwlConditionMask = Kernel32.INSTANCE.VerSetConditionMask(dwlConditionMask, WinNT.VER_MAJORVERSION,
(byte) WinNT.VER_GREATER_EQUAL);
dwlConditionMask = Kernel32.INSTANCE.VerSetConditionMask(dwlConditionMask, WinNT.VER_MINORVERSION,
(byte) WinNT.VER_GREATER_EQUAL);
dwlConditionMask = Kernel32.INSTANCE.VerSetConditionMask(dwlConditionMask, WinNT.VER_SERVICEPACKMAJOR,
(byte) WinNT.VER_GREATER_EQUAL);

return Kernel32.INSTANCE.VerifyVersionInfoW(osvi,
WinNT.VER_MAJORVERSION | WinNT.VER_MINORVERSION | WinNT.VER_SERVICEPACKMAJOR, dwlConditionMask);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows XP version.
*/
public static boolean IsWindowsXPOrGreater() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether I like this, but I don't see a saner version, that doesn't look like repeats...

What might be worth evaluation is, if pulling HIBYTE(WinNT.WIN32_WINNT_WINXP) and LOBYTE(WinNT.WIN32_WINNT_WINXP) into private static final variables might be worth it.

Copy link
Contributor Author

@dbwiddis dbwiddis Jan 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to faithfully reproduce the existing inline header file, in the interest of readability.

I think adding variables would be worse than just explicitly doing the math ((byte) (word >>>8)) and (byte) word inline, which would be pre-calculated and optimized by the compiler. (I expect the JIT optimizer would also eventually optimize the method calls as well if they were called frequently enough.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry! I did not realize, that I had looked at an older SDK, that did not contain the header. And I just realized, that you linked the header. Your first version was better readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you want me to go back to the HIBYTE and LOBYTE methods (that would be JIT optimized) rather than the current compiler-optimized bit math? I kind of like the current version and think adding a comment would make it just as readable/understandable for future maintainers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was, that at this time I understand what you are doing there. But in 6-12 months I might have a WTF moment. I'm ok if you want to keep the bit shifts inline.

Independend of this - my original idea was this:

    private static final byte WIN32_WINNT_WINXP_MAJOR = HIBYTE(WinNT.WIN32_WINNT_WINXP);
    private static final byte WIN32_WINNT_WINXP_MINOR = LOBYTE(WinNT.WIN32_WINNT_WINXP);
    /**
     * @return true if the current OS version matches, or is greater than, the
     *         Windows XP version.
     */
    public static boolean IsWindowsXPOrGreater() {
        return IsWindowsVersionOrGreater(WIN32_WINNT_WINXP_MAJOR, WIN32_WINNT_WINXP_MINOR, 0);
    }

Pulling the calculation into a static final. But as said, if you like it this way, I won't stand in the way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a comment explaining what the bit shifts do, so you and I both can remember what's going on in 6-12 months.

return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WINXP >>> 8), (byte) Kernel32.WIN32_WINNT_WINXP,
0);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows XP with Service Pack 1 (SP1) version.
*/
public static boolean IsWindowsXPSP1OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WINXP >>> 8), (byte) Kernel32.WIN32_WINNT_WINXP,
1);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows XP with Service Pack 2 (SP2) version.
*/
public static boolean IsWindowsXPSP2OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WINXP >>> 8), (byte) Kernel32.WIN32_WINNT_WINXP,
2);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows XP with Service Pack 3 (SP3) version.
*/
public static boolean IsWindowsXPSP3OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WINXP >>> 8), (byte) Kernel32.WIN32_WINNT_WINXP,
3);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows Vista version.
*/
public static boolean IsWindowsVistaOrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_VISTA >>> 8), (byte) Kernel32.WIN32_WINNT_VISTA,
0);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows Vista with Service Pack 1 (SP1) version.
*/
public static boolean IsWindowsVistaSP1OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_VISTA >>> 8), (byte) Kernel32.WIN32_WINNT_VISTA,
1);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows Vista with Service Pack 2 (SP2) version.
*/
public static boolean IsWindowsVistaSP2OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_VISTA >>> 8), (byte) Kernel32.WIN32_WINNT_VISTA,
2);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows 7 version.
*/
public static boolean IsWindows7OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WIN7 >>> 8), (byte) Kernel32.WIN32_WINNT_WIN7, 0);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows 7 with Service Pack 1 (SP1) version.
*/
public static boolean IsWindows7SP1OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WIN7 >>> 8), (byte) Kernel32.WIN32_WINNT_WIN7, 1);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows 8 version.
*/
public static boolean IsWindows8OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WIN8 >>> 8), (byte) Kernel32.WIN32_WINNT_WIN8, 0);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows 8.1 version. For Windows 8.1 and/or Windows 10,
* {@link #IsWindows8Point1OrGreater} returns false unless the
* application contains a manifest that includes a compatibility
* section that contains the GUIDs that designate Windows 8.1 and/or
* Windows 10.
*/
public static boolean IsWindows8Point1OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WINBLUE >>> 8),
(byte) Kernel32.WIN32_WINNT_WINBLUE, 0);
}

/**
* @return true if the current OS version matches, or is greater than, the
* Windows 10 version. For Windows 10,
* {@link #IsWindows8Point1OrGreater} returns false unless the
* application contains a manifest that includes a compatibility
* section that contains the GUID that designates Windows 10.
*/
public static boolean IsWindows10OrGreater() {
return IsWindowsVersionOrGreater((byte) (Kernel32.WIN32_WINNT_WIN10 >>> 8), (byte) Kernel32.WIN32_WINNT_WIN10,
0);
}

/**
* Applications that need to distinguish between server and client versions
* of Windows should call this function.
*
* @return true if the current OS is a Windows Server release.
*/
public static boolean IsWindowsServer() {
OSVERSIONINFOEX osvi = new OSVERSIONINFOEX();
osvi.dwOSVersionInfoSize = new DWORD(osvi.size());
osvi.wProductType = WinNT.VER_NT_WORKSTATION;

long dwlConditionMask = Kernel32.INSTANCE.VerSetConditionMask(0, WinNT.VER_PRODUCT_TYPE,
(byte) WinNT.VER_EQUAL);

return !Kernel32.INSTANCE.VerifyVersionInfoW(osvi, WinNT.VER_PRODUCT_TYPE, dwlConditionMask);
}
}

17 changes: 17 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
Original file line number Diff line number Diff line change
Expand Up @@ -2016,6 +2016,23 @@ public byte getProductType() {
int VER_PLATFORM_WIN32_WINDOWS = 1;
int VER_PLATFORM_WIN32_NT = 2;

/*
* WIN32_WINNT version constants
*/
short WIN32_WINNT_NT4 = 0x0400; // Windows NT 4.0
short WIN32_WINNT_WIN2K = 0x0500; // Windows 2000
short WIN32_WINNT_WINXP = 0x0501; // Windows XP
short WIN32_WINNT_WS03 = 0x0502; // Windows Server 2003
short WIN32_WINNT_WIN6 = 0x0600; // Windows Vista
short WIN32_WINNT_VISTA = 0x0600; // Windows Vista
short WIN32_WINNT_WS08 = 0x0600; // Windows Server 2008
short WIN32_WINNT_LONGHORN = 0x0600; // Windows Vista
short WIN32_WINNT_WIN7 = 0x0601; // Windows 7
short WIN32_WINNT_WIN8 = 0x0602; // Windows 8
short WIN32_WINNT_WINBLUE = 0x0603; // Windows 8.1
short WIN32_WINNT_WINTHRESHOLD = 0x0A00; // Windows 10
short WIN32_WINNT_WIN10 = 0x0A00; // Windows 10

/**
* Read the records sequentially. If this is the first read operation, the
* EVENTLOG_FORWARDS_READ EVENTLOG_BACKWARDS_READ flags determines which
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.sun.jna.platform.win32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this in the first round - could you please also the ALv2+LGPL header here? I'd like to converge on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do! I just copied some other random test file that was missing a header so didn't know if that was standard.


import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class VersionHelpersTest {
@Test
public void testVersionHelpers() {
// All windows versions should be higher than version 0.0!
assertTrue(VersionHelpers.IsWindowsVersionOrGreater(0, 0, 0));
// All windows versions should be lower than version Short.MAX_VALUE!
assertFalse(VersionHelpers.IsWindowsVersionOrGreater(Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE));
// These tests in order should be true until false; once false never
// true again
boolean lastVersionTest = true;
boolean versionTest = VersionHelpers.IsWindowsXPOrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsXPSP1OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsXPSP2OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsXPSP3OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsVistaOrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsVistaSP1OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindowsVistaSP2OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindows7OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindows7SP1OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindows8OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindows8Point1OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);

lastVersionTest = versionTest;
versionTest = VersionHelpers.IsWindows10OrGreater();
assertTrue((lastVersionTest == versionTest) || !versionTest);
}
}