From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- .../PatchAPI/PatchInterop.cs | 989 +++++++++++++++++++++ 1 file changed, 989 insertions(+) create mode 100644 src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs (limited to 'src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs') diff --git a/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs b/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs new file mode 100644 index 00000000..fcd749d2 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs @@ -0,0 +1,989 @@ +// 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.PatchAPI +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Runtime.InteropServices; + using WixToolset.Core; + + /// + /// Interop class for the mspatchc.dll. + /// + internal static class PatchInterop + { + // From WinError.h in the Platform SDK + internal const ushort FACILITY_WIN32 = 7; + + /// + /// Parse a number from text in either hex or decimal. + /// + /// Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise. + /// Numeric value that source represents. + static internal UInt32 ParseHexOrDecimal(string source) + { + string value = source.Trim(); + if (String.Equals(value.Substring(0,2), "0x", StringComparison.OrdinalIgnoreCase)) + { + return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); + } + else + { + return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat); + } + } + + /// + /// Create a binary delta file. + /// + /// Name of the delta file to create. + /// Name of updated file. + /// Optional paths to updated file's symbols. + /// Optional offsets to the delta retain sections in the updated file. + /// Optional array of target files. + /// Optional array of target files' symbol paths (must match basisFiles array). + /// Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries). + /// Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries). + /// Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries). + /// Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries). + /// ApiPatchingSymbolFlags value. + /// OptimizePatchSizeForLargeFiles value. + /// Flag to indicate retain ranges were ignored due to mismatch. + /// true if delta file was created, false if whole file should be used instead. + static public bool CreateDelta( + string deltaFile, + string targetFile, + string targetSymbolPath, + string targetRetainOffsets, + string[] basisFiles, + string[] basisSymbolPaths, + string[] basisIgnoreLengths, + string[] basisIgnoreOffsets, + string[] basisRetainLengths, + string[] basisRetainOffsets, + PatchSymbolFlagsType apiPatchingSymbolFlags, + bool optimizePatchSizeForLargeFiles, + out bool retainRangesIgnored + ) + { + retainRangesIgnored = false; + if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP | PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES | PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO))) + { + throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags"); + } + + if (null == deltaFile || 0 == deltaFile.Length) + { + throw new ArgumentNullException("deltaFile"); + } + + if (null == targetFile || 0 == targetFile.Length) + { + throw new ArgumentNullException("targetFile"); + } + + if (null == basisFiles || 0 == basisFiles.Length) + { + return false; + } + uint countOldFiles = (uint) basisFiles.Length; + + if (null != basisSymbolPaths) + { + if (0 != basisSymbolPaths.Length) + { + if ((uint) basisSymbolPaths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisSymbolPaths"); + } + } + } + // a null basisSymbolPaths is allowed. + + if (null != basisIgnoreLengths) + { + if (0 != basisIgnoreLengths.Length) + { + if ((uint) basisIgnoreLengths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisIgnoreLengths"); + } + } + } + else + { + basisIgnoreLengths = new string[countOldFiles]; + } + + if (null != basisIgnoreOffsets) + { + if (0 != basisIgnoreOffsets.Length) + { + if ((uint) basisIgnoreOffsets.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisIgnoreOffsets"); + } + } + } + else + { + basisIgnoreOffsets = new string[countOldFiles]; + } + + if (null != basisRetainLengths) + { + if (0 != basisRetainLengths.Length) + { + if ((uint) basisRetainLengths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisRetainLengths"); + } + } + } + else + { + basisRetainLengths = new string[countOldFiles]; + } + + if (null != basisRetainOffsets) + { + if (0 != basisRetainOffsets.Length) + { + if ((uint) basisRetainOffsets.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisRetainOffsets"); + } + } + } + else + { + basisRetainOffsets = new string[countOldFiles]; + } + + PatchOptionData pod = new PatchOptionData(); + pod.symbolOptionFlags = apiPatchingSymbolFlags; + pod.newFileSymbolPath = targetSymbolPath; + pod.oldFileSymbolPathArray = basisSymbolPaths; + pod.extendedOptionFlags = 0; + PatchOldFileInfoW[] oldFileInfoArray = new PatchOldFileInfoW[countOldFiles]; + string[] newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(',')); + for (uint i = 0; i < countOldFiles; ++i) + { + PatchOldFileInfoW ofi = new PatchOldFileInfoW(); + ofi.oldFileName = basisFiles[i]; + string[] ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(',')); + string[] ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(',')); + string[] retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(',')); + string[] retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(',')); + // Validate inputs + if (ignoreLengthArray.Length != ignoreOffsetArray.Length) + { + throw new ArgumentOutOfRangeException("basisIgnoreLengths"); + } + + if (retainLengthArray.Length != retainOffsetArray.Length) + { + throw new ArgumentOutOfRangeException("basisRetainLengths"); + } + + if (newRetainOffsetArray.Length != retainOffsetArray.Length) + { + // remove all retain range information + retainRangesIgnored = true; + for (uint j = 0; j < countOldFiles; ++j) + { + basisRetainLengths[j] = null; + basisRetainOffsets[j] = null; + } + retainLengthArray = new string[0]; + retainOffsetArray = new string[0]; + newRetainOffsetArray = new string[0]; + for (uint j = 0; j < oldFileInfoArray.Length; ++j) + { + oldFileInfoArray[j].retainRange = null; + } + } + + // Populate IgnoreRange structure + PatchIgnoreRange[] ignoreArray = null; + if (0 != ignoreLengthArray.Length) + { + ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length]; + for (int j = 0; j < ignoreLengthArray.Length; ++j) + { + PatchIgnoreRange ignoreRange = new PatchIgnoreRange(); + ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]); + ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]); + ignoreArray[j] = ignoreRange; + } + ofi.ignoreRange = ignoreArray; + } + + PatchRetainRange[] retainArray = null; + if (0 != newRetainOffsetArray.Length) + { + retainArray = new PatchRetainRange[retainLengthArray.Length]; + for (int j = 0; j < newRetainOffsetArray.Length; ++j) + { + PatchRetainRange retainRange = new PatchRetainRange(); + retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]); + retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]); + retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]); + retainArray[j] = retainRange; + } + ofi.retainRange = retainArray; + } + oldFileInfoArray[i] = ofi; + } + + if (CreatePatchFileExW( + countOldFiles, + oldFileInfoArray, + targetFile, + deltaFile, + PatchOptionFlags(optimizePatchSizeForLargeFiles), + pod, + null, + IntPtr.Zero)) + { + return true; + } + + // determine if this is an error or a need to use whole file. + int err = Marshal.GetLastWin32Error(); + switch(err) + { + case unchecked((int) ERROR_PATCH_BIGGER_THAN_COMPRESSED): + break; + + // too late to exclude this file -- should have been caught before + case unchecked((int) ERROR_PATCH_SAME_FILE): + default: + throw new System.ComponentModel.Win32Exception(err); + } + return false; + } + + /// + /// Extract the delta header. + /// + /// Name of delta file. + /// Name of file to create with the delta's header. + static public void ExtractDeltaHeader(string delta, string deltaHeader) + { + if (!ExtractPatchHeaderToFileW(delta, deltaHeader)) + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + } + + /// + /// Returns the PatchOptionFlags to use. + /// + /// True if optimizing for large files. + /// PATCH_OPTION_FLAG values + static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles) + { + UInt32 flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST; + if (optimizeForLargeFiles) + { + flags |= PATCH_OPTION_USE_LZX_LARGE; + } + return flags; + } + + //--------------------------------------------------------------------- + // From PatchApi.h + //--------------------------------------------------------------------- + + // + // The following contants can be combined and used as the OptionFlags + // parameter in the patch creation apis. + + internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower) + + internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large) + internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal + internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries + internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer) + + internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports + internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks + internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image + internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same + internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower) + internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero + internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps + internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch + internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support) + internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer) + internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally) + + internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007; + + // + // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags: + // + + internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer) + internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer) + + // + // In addition to the standard Win32 error codes, the following error codes may + // be returned via GetLastError() when one of the patch APIs fails. + + internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create + internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create + internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create + internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create + internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create + internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create + + /// + /// Delegate type that the PatchAPI calls for progress notification. + /// + /// . + /// . + /// . + /// True for success + public delegate bool PatchProgressCallback( + IntPtr context, + uint currentPosition, + uint maxPosition + ); + + /// + /// Delegate type that the PatchAPI calls for patch symbol load information. + /// + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// ??? + public delegate bool PatchSymloadCallback( + uint whichFile, // 0 for new file, 1 for first old file, etc + [MarshalAs(UnmanagedType.LPStr)] string symbolFileName, + uint symType, // see SYM_TYPE in imagehlp.h + uint symbolFileCheckSum, + uint symbolFileTimeDate, + uint imageFileCheckSum, + uint imageFileTimeDate, + IntPtr context + ); + + /// + /// Wraps PATCH_IGNORE_RANGE + /// + [StructLayout(LayoutKind.Sequential)] + internal class PatchIgnoreRange + { + public uint offsetInOldFile; + public uint lengthInBytes; + } + + /// + /// Wraps PATCH_RETAIN_RANGE + /// + [StructLayout(LayoutKind.Sequential)] + internal class PatchRetainRange + { + public uint offsetInOldFile; + public uint lengthInBytes; + public uint offsetInNewFile; + } + + /// + /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion) + /// + internal class PatchOldFileInfo + { + public PatchIgnoreRange[] ignoreRange; + public PatchRetainRange[] retainRange; + } + + /// + /// Wraps PATCH_OLD_FILE_INFO_W + /// + internal class PatchOldFileInfoW : PatchOldFileInfo + { + public string oldFileName; + } + + /// + /// Wraps each PATCH_INTERLEAVE_MAP Range + /// + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)] + internal class PatchInterleaveMapRange + { + public uint oldOffset; + public uint oldLength; + public uint newLength; + } + + /// + /// Wraps PATCH_INTERLEAVE_MAP + /// + internal class PatchInterleaveMap + { + public PatchInterleaveMapRange[] ranges = null; + } + + + /// + /// Wraps PATCH_OPTION_DATA + /// + [BestFitMapping(false, ThrowOnUnmappableChar = true)] + internal class PatchOptionData + { + public PatchSymbolFlagsType symbolOptionFlags; // PATCH_SYMBOL_xxx flags + [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode + [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ] + public uint extendedOptionFlags; + public PatchSymloadCallback symLoadCallback = null; + public IntPtr symLoadContext = IntPtr.Zero; + public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer) + public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer) + } + + // + // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode) + // path argument is available, even when used with one of the Unicode APIs + // such as CreatePatchFileW. This is because the unlerlying system services + // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names. + // + + // + // A note about PATCH_RETAIN_RANGE specifiers with multiple old files: + // + // Each old version file must have the same RetainRangeCount, and the same + // retain range LengthInBytes and OffsetInNewFile values in the same order. + // Only the OffsetInOldFile values can differ between old foles for retain + // ranges. + // + + // + // The following prototypes are (some of the) interfaces for creating patches from files. + // + + /// + /// Creates a new delta. + /// + /// Size of oldFileInfoArray. + /// Target file information. + /// Name of updated file. + /// Name of delta to create. + /// PATCH_OPTION_xxx. + /// Optional PATCH_OPTION_DATA structure. + /// Delegate for progress callbacks. + /// Context for progress callback delegate. + /// true if successfull, sets Marshal.GetLastWin32Error() if not. + [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CreatePatchFileExW( + uint oldFileCount, // maximum 255 + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")] + PatchOldFileInfoW[] oldFileInfoArray, + string newFileName, // input file (required) + string patchFileName, // output file (required) + uint optionFlags, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")] + PatchOptionData optionData, + [MarshalAs (UnmanagedType.FunctionPtr)] + PatchProgressCallback progressCallback, + IntPtr context + ); + + /// + /// Extracts delta header from delta. + /// + /// Name of delta file. + /// Name of file to create with delta header. + /// true if successfull, sets Marshal.GetLastWin32Error() if not. + [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ExtractPatchHeaderToFileW( + string patchFileName, // input file + string patchHeaderFileName // output file + ); + + // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks + + /// + /// Marshals arguments for the CreatePatch~ APIs + /// + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + internal class PatchAPIMarshaler : ICustomMarshaler + { + internal static ICustomMarshaler GetInstance(string cookie) + { + return new PatchAPIMarshaler(cookie); + } + + private enum MarshalType + { + PATCH_OPTION_DATA, + PATCH_OLD_FILE_INFO_W + }; + private PatchAPIMarshaler.MarshalType marshalType; + + private PatchAPIMarshaler(string cookie) + { + this.marshalType = (PatchAPIMarshaler.MarshalType) Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie); + } + + // + // Summary: + // Returns the size of the native data to be marshaled. + // + // Returns: + // The size in bytes of the native data. + public int GetNativeDataSize() + { + return Marshal.SizeOf(typeof(IntPtr)); + } + + // + // Summary: + // Performs necessary cleanup of the managed data when it is no longer needed. + // + // Parameters: + // ManagedObj: + // The managed object to be destroyed. + public void CleanUpManagedData(object ManagedObj) + { + } + + // + // Summary: + // Performs necessary cleanup of the unmanaged data when it is no longer needed. + // + // Parameters: + // pNativeData: + // A pointer to the unmanaged data to be destroyed. + public void CleanUpNativeData(IntPtr pNativeData) + { + if (IntPtr.Zero == pNativeData) + { + return; + } + + switch (this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + this.CleanUpPOD(pNativeData); + break; + default: + this.CleanUpPOFI_A(pNativeData); + break; + } + } + + // + // Summary: + // Converts the managed data to unmanaged data. + // + // Parameters: + // ManagedObj: + // The managed object to be converted. + // + // Returns: + // Returns the COM view of the managed object. + public IntPtr MarshalManagedToNative(object ManagedObj) + { + if (null == ManagedObj) + { + return IntPtr.Zero; + } + + switch(this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + return this.MarshalPOD(ManagedObj as PatchOptionData); + case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: + return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]); + default: + throw new InvalidOperationException(); + } + } + + + // + // Summary: + // Converts the unmanaged data to managed data. + // + // Parameters: + // pNativeData: + // A pointer to the unmanaged data to be wrapped. + // + // Returns: + // Returns the managed view of the COM data. + public object MarshalNativeToManaged(IntPtr pNativeData) + { + return null; + } + + // Implementation ************************************************* + + // PATCH_OPTION_DATA offsets + private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32)); + private static readonly int newFileSymbolPathOffset = 2*Marshal.SizeOf(typeof(Int32)); + private static readonly int oldFileSymbolPathArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int extendedOptionFlagsOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int symLoadCallbackOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int symLoadContextOffset = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int interleaveMapArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 4*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int maxLzxWindowSizeOffset = 3*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int patchOptionDataSize = 4*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); + + // PATCH_OLD_FILE_INFO offsets + private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32)); + private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int ignoreRangeArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int retainRangeCountOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int retainRangeArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int patchOldFileInfoSize = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); + + // Methods and data used to preserve data needed for cleanup + + // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount) + private static readonly Dictionary OldFileCounts = new Dictionary(); + private static readonly object OldFileCountsLock = new object(); + + private IntPtr CreateMainStruct(int oldFileCount) + { + int nativeSize; + switch(this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + nativeSize = patchOptionDataSize; + break; + case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: + nativeSize = oldFileCount*patchOldFileInfoSize; + break; + default: + throw new InvalidOperationException(); + } + + IntPtr native = Marshal.AllocCoTaskMem(nativeSize); + + lock (PatchAPIMarshaler.OldFileCountsLock) + { + PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount); + } + + return native; + } + + private static void ReleaseMainStruct(IntPtr native) + { + lock (PatchAPIMarshaler.OldFileCountsLock) + { + PatchAPIMarshaler.OldFileCounts.Remove(native); + } + Marshal.FreeCoTaskMem(native); + } + + private static int GetOldFileCount(IntPtr native) + { + lock (PatchAPIMarshaler.OldFileCountsLock) + { + return PatchAPIMarshaler.OldFileCounts[native]; + } + } + + // Helper methods + + private static IntPtr OptionalAnsiString(string managed) + { + return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed); + } + + private static IntPtr OptionalUnicodeString(string managed) + { + return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed); + } + + // string array must be of the same length as the number of old files + private static IntPtr CreateArrayOfStringA(string[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); + IntPtr native = Marshal.AllocCoTaskMem(size); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i])); + } + + return native; + } + + // string array must be of the same length as the number of old files + private static IntPtr CreateArrayOfStringW(string[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); + IntPtr native = Marshal.AllocCoTaskMem(size); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i])); + } + + return native; + } + + private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + if (null == managed.ranges) + { + return IntPtr.Zero; + } + + if (0 == managed.ranges.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32)) + + managed.ranges.Length*(Marshal.SizeOf(typeof(PatchInterleaveMap)))); + WriteUInt32(native, (uint) managed.ranges.Length); + + for (int i = 0; i < managed.ranges.Length; ++i) + { + Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i*Marshal.SizeOf(typeof(PatchInterleaveMap))), false); + } + return native; + } + + private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr))); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i])); + } + + return native; + } + + private static void WriteUInt32(IntPtr native, uint data) + { + Marshal.WriteInt32(native, unchecked((int) data)); + } + + private static void WriteUInt32(IntPtr native, int offset, uint data) + { + Marshal.WriteInt32(native, offset, unchecked((int) data)); + } + + // Marshal operations + + private IntPtr MarshalPOD(PatchOptionData managed) + { + if (null == managed) + { + throw new ArgumentNullException("managed"); + } + + IntPtr native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length); + Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct + WriteUInt32(native, symbolOptionFlagsOffset, (uint) managed.symbolOptionFlags); + Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath)); + Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray)); + WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags); + + // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null. + if (null == managed.symLoadCallback) + { + Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero); + } + else + { + Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback)); + } + + Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext); + Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray)); + WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize); + return native; + } + + private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed) + { + if (null == managed) + { + throw new ArgumentNullException("managed"); + } + + if (0 == managed.Length) + { + return IntPtr.Zero; + } + + IntPtr native = this.CreateMainStruct(managed.Length); + + for (int i = 0; i < managed.Length; ++i) + { + PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize)); + } + + return native; + } + + private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native) + { + PatchAPIMarshaler.MarshalPOFI(managed, native); + Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName + } + + private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native) + { + Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct + WriteUInt32(native, ignoreRangeCountOffset, + (null == managed.ignoreRange) ? 0 : (uint) managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255 + Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray + WriteUInt32(native, retainRangeCountOffset, + (null == managed.retainRange) ? 0 : (uint) managed.retainRange.Length); // RetainRangeCount // maximum 255 + Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray + } + + private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array) + { + if (null == array) + { + return IntPtr.Zero; + } + + if (0 == array.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchIgnoreRange))); + + for (int i = 0; i < array.Length; ++i) + { + Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchIgnoreRange)))), false); + } + + return native; + } + + private static IntPtr MarshalPRRArray(PatchRetainRange[] array) + { + if (null == array) + { + return IntPtr.Zero; + } + + if (0 == array.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchRetainRange))); + + for (int i = 0; i < array.Length; ++i) + { + Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchRetainRange)))), false); + } + + return native; + } + + // CleanUp operations + + private void CleanUpPOD(IntPtr native) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset)); + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + Marshal.FreeCoTaskMem( + Marshal.ReadIntPtr( + Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset), + i*Marshal.SizeOf(typeof(IntPtr)))); + } + + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)); + } + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset)) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + Marshal.FreeCoTaskMem( + Marshal.ReadIntPtr( + Marshal.ReadIntPtr(native, interleaveMapArrayOffset), + i*Marshal.SizeOf(typeof(IntPtr)))); + } + + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset)); + } + + PatchAPIMarshaler.ReleaseMainStruct(native); + } + + private void CleanUpPOFI_A(IntPtr native) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i*patchOldFileInfoSize)); + } + + PatchAPIMarshaler.ReleaseMainStruct(native); + } + + private static void CleanUpPOFI(IntPtr native) + { + if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset)); + } + + PatchAPIMarshaler.CleanUpPOFIH(native); + } + + private static void CleanUpPOFIH(IntPtr native) + { + if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)); + } + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset)); + } + } + } + } +} -- cgit v1.2.3-55-g6feb