From 1b6a4f9b4600079e829d5884f768563ed7dea5e5 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 18 Oct 2020 22:38:21 -0500 Subject: Add CompareVersions engine method and expose verutil in Mba.Core. --- src/WixToolset.Mba.Core/Engine.cs | 6 + src/WixToolset.Mba.Core/IBootstrapperEngine.cs | 6 + src/WixToolset.Mba.Core/IEngine.cs | 3 + src/WixToolset.Mba.Core/VerUtil.cs | 126 +++++++++++++++++++++ src/WixToolset.Mba.Core/VerUtilVersion.cs | 63 +++++++++++ .../VerUtilVersionReleaseLabel.cs | 22 ++++ src/balutil/BalBootstrapperEngine.cpp | 26 +++++ src/balutil/inc/IBootstrapperApplication.h | 2 +- src/balutil/inc/IBootstrapperEngine.h | 6 + src/bextutil/BextBundleExtensionEngine.cpp | 26 +++++ src/bextutil/inc/IBundleExtensionEngine.h | 6 + src/mbanative/mbanative.def | 6 + src/mbanative/precomp.h | 1 + .../BaseBootstrapperApplicationFactoryFixture.cs | 2 +- src/test/WixToolsetTest.Mba.Core/VerUtilFixture.cs | 93 +++++++++++++++ 15 files changed, 392 insertions(+), 2 deletions(-) create mode 100644 src/WixToolset.Mba.Core/VerUtil.cs create mode 100644 src/WixToolset.Mba.Core/VerUtilVersion.cs create mode 100644 src/WixToolset.Mba.Core/VerUtilVersionReleaseLabel.cs create mode 100644 src/test/WixToolsetTest.Mba.Core/VerUtilFixture.cs diff --git a/src/WixToolset.Mba.Core/Engine.cs b/src/WixToolset.Mba.Core/Engine.cs index e3ad097a..c6570f9d 100644 --- a/src/WixToolset.Mba.Core/Engine.cs +++ b/src/WixToolset.Mba.Core/Engine.cs @@ -49,6 +49,12 @@ namespace WixToolset.Mba.Core this.engine.CloseSplashScreen(); } + public int CompareVersions(string version1, string version2) + { + this.engine.CompareVersions(version1, version2, out var result); + return result; + } + public bool ContainsVariable(string name) { int capacity = 0; diff --git a/src/WixToolset.Mba.Core/IBootstrapperEngine.cs b/src/WixToolset.Mba.Core/IBootstrapperEngine.cs index 584e0f6d..5cc42071 100644 --- a/src/WixToolset.Mba.Core/IBootstrapperEngine.cs +++ b/src/WixToolset.Mba.Core/IBootstrapperEngine.cs @@ -145,6 +145,12 @@ namespace WixToolset.Mba.Core [MarshalAs(UnmanagedType.LPWStr)] string wzArguments, [MarshalAs(UnmanagedType.U4)] int dwWaitForInputIdleTimeout ); + + void CompareVersions( + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion1, + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion2, + [MarshalAs(UnmanagedType.I4)] out int pnResult + ); } /// diff --git a/src/WixToolset.Mba.Core/IEngine.cs b/src/WixToolset.Mba.Core/IEngine.cs index b486e51d..8403352d 100644 --- a/src/WixToolset.Mba.Core/IEngine.cs +++ b/src/WixToolset.Mba.Core/IEngine.cs @@ -25,6 +25,9 @@ namespace WixToolset.Mba.Core /// void CloseSplashScreen(); + /// 0 if equal, 1 if version1 > version2, -1 if version1 < version2 + int CompareVersions(string version1, string version2); + /// /// Checks if a variable exists in the engine. /// diff --git a/src/WixToolset.Mba.Core/VerUtil.cs b/src/WixToolset.Mba.Core/VerUtil.cs new file mode 100644 index 00000000..dcc9dee2 --- /dev/null +++ b/src/WixToolset.Mba.Core/VerUtil.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System; + using System.Runtime.InteropServices; + using System.Text; + + public static class VerUtil + { + [DllImport("mbanative.dll", ExactSpelling = true, PreserveSig = false)] + internal static extern int VerCompareParsedVersions( + VersionHandle pVersion1, + VersionHandle pVersion2 + ); + + [DllImport("mbanative.dll", ExactSpelling = true, PreserveSig = false)] + internal static extern int VerCompareStringVersions( + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion1, + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion2, + [MarshalAs(UnmanagedType.Bool)] bool fStrict + ); + + [DllImport("mbanative.dll", ExactSpelling = true, PreserveSig = false)] + internal static extern VersionHandle VerCopyVersion( + VersionHandle pSource + ); + + [DllImport("mbanative.dll", ExactSpelling = true)] + internal static extern void VerFreeVersion( + IntPtr pVersion + ); + + [DllImport("mbanative.dll", ExactSpelling = true, PreserveSig = false)] + internal static extern VersionHandle VerParseVersion( + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion, + [MarshalAs(UnmanagedType.U4)] uint cchValue, + [MarshalAs(UnmanagedType.Bool)] bool fStrict + ); + + [DllImport("mbanative.dll", ExactSpelling = true, PreserveSig = false)] + internal static extern VersionHandle VerVersionFromQword( + [MarshalAs(UnmanagedType.I8)] long qwVersion + ); + + [StructLayout(LayoutKind.Sequential)] + internal struct VersionReleaseLabelStruct + { + public bool fNumeric; + public uint dwValue; + public IntPtr cchLabelOffset; + public int cchLabel; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct VersionStruct + { + public IntPtr sczVersion; + public uint dwMajor; + public uint dwMinor; + public uint dwPatch; + public uint dwRevision; + public int cReleaseLabels; + public IntPtr rgReleaseLabels; + public IntPtr cchMetadataOffset; + public bool fInvalid; + } + + internal static string VersionStringFromOffset(IntPtr wzVersion, IntPtr cchOffset, int? cchLength = null) + { + var offset = cchOffset.ToInt64() * UnicodeEncoding.CharSize; + var wz = new IntPtr(wzVersion.ToInt64() + offset); + if (cchLength.HasValue) + { + return Marshal.PtrToStringUni(wz, (int)cchLength); + } + else + { + return Marshal.PtrToStringUni(wz); + } + } + + internal sealed class VersionHandle : SafeHandle + { + public VersionHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid => false; + + protected override bool ReleaseHandle() + { + VerFreeVersion(this.handle); + return true; + } + } + + /// 0 if equal, 1 if version1 > version2, -1 if version1 < version2 + public static int CompareParsedVersions(VerUtilVersion version1, VerUtilVersion version2) + { + return VerCompareParsedVersions(version1.GetHandle(), version2.GetHandle()); + } + + /// 0 if equal, 1 if version1 > version2, -1 if version1 < version2 + public static int CompareStringVersions(string version1, string version2, bool strict) + { + return VerCompareStringVersions(version1, version2, strict); + } + + public static VerUtilVersion CopyVersion(VerUtilVersion version) + { + var handle = VerCopyVersion(version.GetHandle()); + return new VerUtilVersion(handle); + } + + public static VerUtilVersion ParseVersion(string version, bool strict) + { + var handle = VerParseVersion(version, 0, strict); + return new VerUtilVersion(handle); + } + + public static VerUtilVersion VersionFromQword(long version) + { + var handle = VerVersionFromQword(version); + return new VerUtilVersion(handle); + } + } +} diff --git a/src/WixToolset.Mba.Core/VerUtilVersion.cs b/src/WixToolset.Mba.Core/VerUtilVersion.cs new file mode 100644 index 00000000..2b509a21 --- /dev/null +++ b/src/WixToolset.Mba.Core/VerUtilVersion.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System; + using System.Runtime.InteropServices; + + public sealed class VerUtilVersion : IDisposable + { + internal VerUtilVersion(VerUtil.VersionHandle handle) + { + this.Handle = handle; + + var pVersion = handle.DangerousGetHandle(); + var version = (VerUtil.VersionStruct)Marshal.PtrToStructure(pVersion, typeof(VerUtil.VersionStruct)); + this.Version = Marshal.PtrToStringUni(version.sczVersion); + this.Major = version.dwMajor; + this.Minor = version.dwMinor; + this.Patch = version.dwPatch; + this.Revision = version.dwRevision; + this.ReleaseLabels = new VerUtilVersionReleaseLabel[version.cReleaseLabels]; + this.Metadata = VerUtil.VersionStringFromOffset(version.sczVersion, version.cchMetadataOffset); + this.IsInvalid = version.fInvalid; + + for (var i = 0; i < version.cReleaseLabels; ++i) + { + var offset = i * Marshal.SizeOf(typeof(VerUtil.VersionReleaseLabelStruct)); + var pReleaseLabel = new IntPtr(version.rgReleaseLabels.ToInt64() + offset); + this.ReleaseLabels[i] = new VerUtilVersionReleaseLabel(pReleaseLabel, version.sczVersion); + } + } + + public string Version { get; private set; } + public uint Major { get; private set; } + public uint Minor { get; private set; } + public uint Patch { get; private set; } + public uint Revision { get; private set; } + public VerUtilVersionReleaseLabel[] ReleaseLabels { get; private set; } + public string Metadata { get; private set; } + public bool IsInvalid { get; private set; } + + public void Dispose() + { + if (this.Handle != null) + { + this.Handle.Dispose(); + this.Handle = null; + } + } + + private VerUtil.VersionHandle Handle { get; set; } + + internal VerUtil.VersionHandle GetHandle() + { + if (this.Handle == null) + { + throw new ObjectDisposedException(this.Version); + } + + return this.Handle; + } + } +} diff --git a/src/WixToolset.Mba.Core/VerUtilVersionReleaseLabel.cs b/src/WixToolset.Mba.Core/VerUtilVersionReleaseLabel.cs new file mode 100644 index 00000000..84ff3b36 --- /dev/null +++ b/src/WixToolset.Mba.Core/VerUtilVersionReleaseLabel.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System; + using System.Runtime.InteropServices; + + public sealed class VerUtilVersionReleaseLabel + { + internal VerUtilVersionReleaseLabel(IntPtr pReleaseLabel, IntPtr wzVersion) + { + var releaseLabel = (VerUtil.VersionReleaseLabelStruct)Marshal.PtrToStructure(pReleaseLabel, typeof(VerUtil.VersionReleaseLabelStruct)); + this.IsNumeric = releaseLabel.fNumeric; + this.Value = releaseLabel.dwValue; + this.Label = VerUtil.VersionStringFromOffset(wzVersion, releaseLabel.cchLabelOffset, releaseLabel.cchLabel); + } + + public bool IsNumeric { get; private set; } + public uint Value { get; private set; } + public string Label { get; private set; } + } +} diff --git a/src/balutil/BalBootstrapperEngine.cpp b/src/balutil/BalBootstrapperEngine.cpp index b68ea7c2..8a78b127 100644 --- a/src/balutil/BalBootstrapperEngine.cpp +++ b/src/balutil/BalBootstrapperEngine.cpp @@ -535,6 +535,32 @@ public: // IBootstrapperEngine return m_pfnBAEngineProc(BOOTSTRAPPER_ENGINE_MESSAGE_LAUNCHAPPROVEDEXE, &args, &results, m_pvBAEngineProcContext); } + virtual STDMETHODIMP CompareVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ) + { + HRESULT hr = S_OK; + BAENGINE_COMPAREVERSIONS_ARGS args = { }; + BAENGINE_COMPAREVERSIONS_RESULTS results = { }; + + ExitOnNull(pnResult, hr, E_INVALIDARG, "pnResult is required"); + + args.cbSize = sizeof(args); + args.wzVersion1 = wzVersion1; + args.wzVersion2 = wzVersion2; + + results.cbSize = sizeof(results); + + hr = m_pfnBAEngineProc(BOOTSTRAPPER_ENGINE_MESSAGE_COMPAREVERSIONS, &args, &results, m_pvBAEngineProcContext); + + *pnResult = results.nResult; + + LExit: + return hr; + } + public: // IMarshal virtual STDMETHODIMP GetUnmarshalClass( __in REFIID /*riid*/, diff --git a/src/balutil/inc/IBootstrapperApplication.h b/src/balutil/inc/IBootstrapperApplication.h index 9cc19120..75ea7bc9 100644 --- a/src/balutil/inc/IBootstrapperApplication.h +++ b/src/balutil/inc/IBootstrapperApplication.h @@ -396,7 +396,7 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A __inout BOOL* pfCancel ) = 0; - // OnExecuteBegin - called when the engine begins executing a package. + // OnExecutePackageBegin - called when the engine begins executing a package. // STDMETHOD(OnExecutePackageBegin)( __in_z LPCWSTR wzPackageId, diff --git a/src/balutil/inc/IBootstrapperEngine.h b/src/balutil/inc/IBootstrapperEngine.h index cf055102..cd89a9f0 100644 --- a/src/balutil/inc/IBootstrapperEngine.h +++ b/src/balutil/inc/IBootstrapperEngine.h @@ -127,4 +127,10 @@ DECLARE_INTERFACE_IID_(IBootstrapperEngine, IUnknown, "6480D616-27A0-44D7-905B-8 __in_z_opt LPCWSTR wzArguments, __in DWORD dwWaitForInputIdleTimeout ) = 0; + + STDMETHOD(CompareVersions)( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ) = 0; }; diff --git a/src/bextutil/BextBundleExtensionEngine.cpp b/src/bextutil/BextBundleExtensionEngine.cpp index 983782a9..9906a7f5 100644 --- a/src/bextutil/BextBundleExtensionEngine.cpp +++ b/src/bextutil/BextBundleExtensionEngine.cpp @@ -280,6 +280,32 @@ public: // IBundleExtensionEngine return m_pfnBundleExtensionEngineProc(BUNDLE_EXTENSION_ENGINE_MESSAGE_SETVARIABLEVERSION, &args, &results, m_pvBundleExtensionEngineProcContext); } + virtual STDMETHODIMP CompareVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ) + { + HRESULT hr = S_OK; + BUNDLE_EXTENSION_ENGINE_COMPAREVERSIONS_ARGS args = { }; + BUNDLE_EXTENSION_ENGINE_COMPAREVERSIONS_RESULTS results = { }; + + ExitOnNull(pnResult, hr, E_INVALIDARG, "pnResult is required"); + + args.cbSize = sizeof(args); + args.wzVersion1 = wzVersion1; + args.wzVersion2 = wzVersion2; + + results.cbSize = sizeof(results); + + hr = m_pfnBundleExtensionEngineProc(BUNDLE_EXTENSION_ENGINE_MESSAGE_COMPAREVERSIONS, &args, &results, m_pvBundleExtensionEngineProcContext); + + *pnResult = results.nResult; + + LExit: + return hr; + } + public: CBextBundleExtensionEngine( __in PFN_BUNDLE_EXTENSION_ENGINE_PROC pfnBundleExtensionEngineProc, diff --git a/src/bextutil/inc/IBundleExtensionEngine.h b/src/bextutil/inc/IBundleExtensionEngine.h index 77ea2770..a96e3812 100644 --- a/src/bextutil/inc/IBundleExtensionEngine.h +++ b/src/bextutil/inc/IBundleExtensionEngine.h @@ -58,4 +58,10 @@ DECLARE_INTERFACE_IID_(IBundleExtensionEngine, IUnknown, "9D027A39-F6B6-42CC-973 __in_z LPCWSTR wzVariable, __in_z_opt LPCWSTR wzValue ) = 0; + + STDMETHOD(CompareVersions)( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ) = 0; }; diff --git a/src/mbanative/mbanative.def b/src/mbanative/mbanative.def index bd366ac7..28e923b6 100644 --- a/src/mbanative/mbanative.def +++ b/src/mbanative/mbanative.def @@ -4,3 +4,9 @@ EXPORTS InitializeFromCreateArgs StoreBAInCreateResults + VerCompareParsedVersions + VerCompareStringVersions + VerCopyVersion + VerFreeVersion + VerParseVersion + VerVersionFromQword diff --git a/src/mbanative/precomp.h b/src/mbanative/precomp.h index d19b4695..2e2f3ff8 100644 --- a/src/mbanative/precomp.h +++ b/src/mbanative/precomp.h @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/src/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs b/src/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs index 12483ddf..7fe0a405 100644 --- a/src/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs +++ b/src/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -namespace WixToolsetTest.Util +namespace WixToolsetTest.Mba.Core { using System; using System.Runtime.InteropServices; diff --git a/src/test/WixToolsetTest.Mba.Core/VerUtilFixture.cs b/src/test/WixToolsetTest.Mba.Core/VerUtilFixture.cs new file mode 100644 index 00000000..44142e3d --- /dev/null +++ b/src/test/WixToolsetTest.Mba.Core/VerUtilFixture.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolsetTest.Mba.Core +{ + using System; + using WixToolset.Mba.Core; + using Xunit; + + public class VerUtilFixture + { + [Fact] + public void CanCompareStringVersions() + { + var version1 = "1.2.3.4+abcd"; + var version2 = "1.2.3.4+zyxw"; + + Assert.Equal(0, VerUtil.CompareStringVersions(version1, version2, strict: false)); + } + + [Fact] + public void CanCopyVersion() + { + var version = "1.2.3.4-5.6.7.8.9.0"; + + VerUtilVersion copiedVersion = null; + try + { + using (var parsedVersion = VerUtil.ParseVersion(version, strict: true)) + { + copiedVersion = VerUtil.CopyVersion(parsedVersion); + } + + using (var secondVersion = VerUtil.ParseVersion(version, strict: true)) + { + Assert.Equal(0, VerUtil.CompareParsedVersions(copiedVersion, secondVersion)); + } + } + finally + { + copiedVersion?.Dispose(); + } + } + + [Fact] + public void CanCreateFromQword() + { + var version = new Version(100, 200, 300, 400); + var qwVersion = Engine.VersionToLong(version); + + using var parsedVersion = VerUtil.VersionFromQword(qwVersion); + Assert.Equal("100.200.300.400", parsedVersion.Version); + Assert.Equal(100u, parsedVersion.Major); + Assert.Equal(200u, parsedVersion.Minor); + Assert.Equal(300u, parsedVersion.Patch); + Assert.Equal(400u, parsedVersion.Revision); + Assert.Empty(parsedVersion.ReleaseLabels); + Assert.Equal("", parsedVersion.Metadata); + Assert.False(parsedVersion.IsInvalid); + } + + [Fact] + public void CanParseVersion() + { + var version = "1.2.3.4-a.b.c.d.5.+abc123"; + + using var parsedVersion = VerUtil.ParseVersion(version, strict: false); + Assert.Equal(version, parsedVersion.Version); + Assert.Equal(1u, parsedVersion.Major); + Assert.Equal(2u, parsedVersion.Minor); + Assert.Equal(3u, parsedVersion.Patch); + Assert.Equal(4u, parsedVersion.Revision); + Assert.Equal(5, parsedVersion.ReleaseLabels.Length); + Assert.Equal("+abc123", parsedVersion.Metadata); + Assert.True(parsedVersion.IsInvalid); + + Assert.Equal("a", parsedVersion.ReleaseLabels[0].Label); + Assert.False(parsedVersion.ReleaseLabels[0].IsNumeric); + + Assert.Equal("b", parsedVersion.ReleaseLabels[1].Label); + Assert.False(parsedVersion.ReleaseLabels[1].IsNumeric); + + Assert.Equal("c", parsedVersion.ReleaseLabels[2].Label); + Assert.False(parsedVersion.ReleaseLabels[2].IsNumeric); + + Assert.Equal("d", parsedVersion.ReleaseLabels[3].Label); + Assert.False(parsedVersion.ReleaseLabels[3].IsNumeric); + + Assert.Equal("5", parsedVersion.ReleaseLabels[4].Label); + Assert.True(parsedVersion.ReleaseLabels[4].IsNumeric); + Assert.Equal(5u, parsedVersion.ReleaseLabels[4].Value); + } + } +} -- cgit v1.2.3-55-g6feb