aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.Native/PatchAPI
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-03-16 10:34:28 -0700
committerRob Mensching <rob@firegiant.com>2021-03-16 11:01:46 -0700
commit089a08fd6b9398b0e1040f96b8e24ba81acfe05b (patch)
tree1f521b0b91f2d6373a16f3632c5cb4d835303920 /src/WixToolset.Core.Native/PatchAPI
parent533fb3c24290f5c9684a661e2576d857fbee9fb6 (diff)
downloadwix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.gz
wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.bz2
wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.zip
Migrate PInvoke to Core.Native out of Core
Diffstat (limited to 'src/WixToolset.Core.Native/PatchAPI')
-rw-r--r--src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs990
1 files changed, 990 insertions, 0 deletions
diff --git a/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs b/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs
new file mode 100644
index 00000000..04f5a553
--- /dev/null
+++ b/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs
@@ -0,0 +1,990 @@
1// 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.
2
3namespace WixToolset.Core.Native.PatchAPI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data.Symbols;
11
12 /// <summary>
13 /// Interop class for the mspatchc.dll.
14 /// </summary>
15 internal static class PatchInterop
16 {
17 // From WinError.h in the Platform SDK
18 internal const ushort FACILITY_WIN32 = 7;
19
20 /// <summary>
21 /// Parse a number from text in either hex or decimal.
22 /// </summary>
23 /// <param name="source">Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise.</param>
24 /// <returns>Numeric value that source represents.</returns>
25 internal static uint ParseHexOrDecimal(string source)
26 {
27 var value = source.Trim();
28 if (String.Equals(value.Substring(0, 2), "0x", StringComparison.OrdinalIgnoreCase))
29 {
30 return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat);
31 }
32 else
33 {
34 return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat);
35 }
36 }
37
38 /// <summary>
39 /// Create a binary delta file.
40 /// </summary>
41 /// <param name="deltaFile">Name of the delta file to create.</param>
42 /// <param name="targetFile">Name of updated file.</param>
43 /// <param name="targetSymbolPath">Optional paths to updated file's symbols.</param>
44 /// <param name="targetRetainOffsets">Optional offsets to the delta retain sections in the updated file.</param>
45 /// <param name="basisFiles">Optional array of target files.</param>
46 /// <param name="basisSymbolPaths">Optional array of target files' symbol paths (must match basisFiles array).</param>
47 /// <param name="basisIgnoreLengths">Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries).</param>
48 /// <param name="basisIgnoreOffsets">Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries).</param>
49 /// <param name="basisRetainLengths">Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries).</param>
50 /// <param name="basisRetainOffsets">Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries).</param>
51 /// <param name="apiPatchingSymbolFlags">ApiPatchingSymbolFlags value.</param>
52 /// <param name="optimizePatchSizeForLargeFiles">OptimizePatchSizeForLargeFiles value.</param>
53 /// <param name="retainRangesIgnored">Flag to indicate retain ranges were ignored due to mismatch.</param>
54 /// <returns>true if delta file was created, false if whole file should be used instead.</returns>
55 public static bool CreateDelta(
56 string deltaFile,
57 string targetFile,
58 string targetSymbolPath,
59 string targetRetainOffsets,
60 string[] basisFiles,
61 string[] basisSymbolPaths,
62 string[] basisIgnoreLengths,
63 string[] basisIgnoreOffsets,
64 string[] basisRetainLengths,
65 string[] basisRetainOffsets,
66 PatchSymbolFlags apiPatchingSymbolFlags,
67 bool optimizePatchSizeForLargeFiles,
68 out bool retainRangesIgnored
69 )
70 {
71 retainRangesIgnored = false;
72 if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlags.PatchSymbolNoImagehlp | PatchSymbolFlags.PatchSymbolNoFailures | PatchSymbolFlags.PatchSymbolUndecoratedToo)))
73 {
74 throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags");
75 }
76
77 if (null == deltaFile || 0 == deltaFile.Length)
78 {
79 throw new ArgumentNullException("deltaFile");
80 }
81
82 if (null == targetFile || 0 == targetFile.Length)
83 {
84 throw new ArgumentNullException("targetFile");
85 }
86
87 if (null == basisFiles || 0 == basisFiles.Length)
88 {
89 return false;
90 }
91 var countOldFiles = (uint)basisFiles.Length;
92
93 if (null != basisSymbolPaths)
94 {
95 if (0 != basisSymbolPaths.Length)
96 {
97 if ((uint)basisSymbolPaths.Length != countOldFiles)
98 {
99 throw new ArgumentOutOfRangeException("basisSymbolPaths");
100 }
101 }
102 }
103 // a null basisSymbolPaths is allowed.
104
105 if (null != basisIgnoreLengths)
106 {
107 if (0 != basisIgnoreLengths.Length)
108 {
109 if ((uint)basisIgnoreLengths.Length != countOldFiles)
110 {
111 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
112 }
113 }
114 }
115 else
116 {
117 basisIgnoreLengths = new string[countOldFiles];
118 }
119
120 if (null != basisIgnoreOffsets)
121 {
122 if (0 != basisIgnoreOffsets.Length)
123 {
124 if ((uint)basisIgnoreOffsets.Length != countOldFiles)
125 {
126 throw new ArgumentOutOfRangeException("basisIgnoreOffsets");
127 }
128 }
129 }
130 else
131 {
132 basisIgnoreOffsets = new string[countOldFiles];
133 }
134
135 if (null != basisRetainLengths)
136 {
137 if (0 != basisRetainLengths.Length)
138 {
139 if ((uint)basisRetainLengths.Length != countOldFiles)
140 {
141 throw new ArgumentOutOfRangeException("basisRetainLengths");
142 }
143 }
144 }
145 else
146 {
147 basisRetainLengths = new string[countOldFiles];
148 }
149
150 if (null != basisRetainOffsets)
151 {
152 if (0 != basisRetainOffsets.Length)
153 {
154 if ((uint)basisRetainOffsets.Length != countOldFiles)
155 {
156 throw new ArgumentOutOfRangeException("basisRetainOffsets");
157 }
158 }
159 }
160 else
161 {
162 basisRetainOffsets = new string[countOldFiles];
163 }
164
165 var pod = new PatchOptionData();
166 pod.symbolOptionFlags = apiPatchingSymbolFlags;
167 pod.newFileSymbolPath = targetSymbolPath;
168 pod.oldFileSymbolPathArray = basisSymbolPaths;
169 pod.extendedOptionFlags = 0;
170 var oldFileInfoArray = new PatchOldFileInfoW[countOldFiles];
171 var newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(','));
172 for (uint i = 0; i < countOldFiles; ++i)
173 {
174 var ofi = new PatchOldFileInfoW();
175 ofi.oldFileName = basisFiles[i];
176 var ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(','));
177 var ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(','));
178 var retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(','));
179 var retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(','));
180 // Validate inputs
181 if (ignoreLengthArray.Length != ignoreOffsetArray.Length)
182 {
183 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
184 }
185
186 if (retainLengthArray.Length != retainOffsetArray.Length)
187 {
188 throw new ArgumentOutOfRangeException("basisRetainLengths");
189 }
190
191 if (newRetainOffsetArray.Length != retainOffsetArray.Length)
192 {
193 // remove all retain range information
194 retainRangesIgnored = true;
195 for (uint j = 0; j < countOldFiles; ++j)
196 {
197 basisRetainLengths[j] = null;
198 basisRetainOffsets[j] = null;
199 }
200 retainLengthArray = new string[0];
201 retainOffsetArray = new string[0];
202 newRetainOffsetArray = new string[0];
203 for (uint j = 0; j < oldFileInfoArray.Length; ++j)
204 {
205 oldFileInfoArray[j].retainRange = null;
206 }
207 }
208
209 // Populate IgnoreRange structure
210 PatchIgnoreRange[] ignoreArray = null;
211 if (0 != ignoreLengthArray.Length)
212 {
213 ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length];
214 for (var j = 0; j < ignoreLengthArray.Length; ++j)
215 {
216 var ignoreRange = new PatchIgnoreRange();
217 ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]);
218 ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]);
219 ignoreArray[j] = ignoreRange;
220 }
221 ofi.ignoreRange = ignoreArray;
222 }
223
224 PatchRetainRange[] retainArray = null;
225 if (0 != newRetainOffsetArray.Length)
226 {
227 retainArray = new PatchRetainRange[retainLengthArray.Length];
228 for (var j = 0; j < newRetainOffsetArray.Length; ++j)
229 {
230 var retainRange = new PatchRetainRange();
231 retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]);
232 retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]);
233 retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]);
234 retainArray[j] = retainRange;
235 }
236 ofi.retainRange = retainArray;
237 }
238 oldFileInfoArray[i] = ofi;
239 }
240
241 if (CreatePatchFileExW(
242 countOldFiles,
243 oldFileInfoArray,
244 targetFile,
245 deltaFile,
246 PatchOptionFlags(optimizePatchSizeForLargeFiles),
247 pod,
248 null,
249 IntPtr.Zero))
250 {
251 return true;
252 }
253
254 // determine if this is an error or a need to use whole file.
255 var err = Marshal.GetLastWin32Error();
256 switch (err)
257 {
258 case unchecked((int)ERROR_PATCH_BIGGER_THAN_COMPRESSED):
259 break;
260
261 // too late to exclude this file -- should have been caught before
262 case unchecked((int)ERROR_PATCH_SAME_FILE):
263 default:
264 throw new System.ComponentModel.Win32Exception(err);
265 }
266 return false;
267 }
268
269 /// <summary>
270 /// Extract the delta header.
271 /// </summary>
272 /// <param name="delta">Name of delta file.</param>
273 /// <param name="deltaHeader">Name of file to create with the delta's header.</param>
274 static public void ExtractDeltaHeader(string delta, string deltaHeader)
275 {
276 if (!ExtractPatchHeaderToFileW(delta, deltaHeader))
277 {
278 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
279 }
280 }
281
282 /// <summary>
283 /// Returns the PatchOptionFlags to use.
284 /// </summary>
285 /// <param name="optimizeForLargeFiles">True if optimizing for large files.</param>
286 /// <returns>PATCH_OPTION_FLAG values</returns>
287 static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles)
288 {
289 var flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST;
290 if (optimizeForLargeFiles)
291 {
292 flags |= PATCH_OPTION_USE_LZX_LARGE;
293 }
294 return flags;
295 }
296
297 //---------------------------------------------------------------------
298 // From PatchApi.h
299 //---------------------------------------------------------------------
300
301 //
302 // The following contants can be combined and used as the OptionFlags
303 // parameter in the patch creation apis.
304
305 internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower)
306
307 internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large)
308 internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal
309 internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries
310 internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer)
311
312 internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports
313 internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks
314 internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image
315 internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same
316 internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower)
317 internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero
318 internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps
319 internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch
320 internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support)
321 internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer)
322 internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally)
323
324 internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007;
325
326 //
327 // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags:
328 //
329
330 internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer)
331 internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer)
332
333 //
334 // In addition to the standard Win32 error codes, the following error codes may
335 // be returned via GetLastError() when one of the patch APIs fails.
336
337 internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create
338 internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create
339 internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create
340 internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create
341 internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create
342 internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create
343
344 /// <summary>
345 /// Delegate type that the PatchAPI calls for progress notification.
346 /// </summary>
347 /// <param name="context">.</param>
348 /// <param name="currentPosition">.</param>
349 /// <param name="maxPosition">.</param>
350 /// <returns>True for success</returns>
351 public delegate bool PatchProgressCallback(
352 IntPtr context,
353 uint currentPosition,
354 uint maxPosition
355 );
356
357 /// <summary>
358 /// Delegate type that the PatchAPI calls for patch symbol load information.
359 /// </summary>
360 /// <param name="whichFile">.</param>
361 /// <param name="symbolFileName">.</param>
362 /// <param name="symType">.</param>
363 /// <param name="symbolFileCheckSum">.</param>
364 /// <param name="symbolFileTimeDate">.</param>
365 /// <param name="imageFileCheckSum">.</param>
366 /// <param name="imageFileTimeDate">.</param>
367 /// <param name="context">.</param>
368 /// <returns>???</returns>
369 public delegate bool PatchSymloadCallback(
370 uint whichFile, // 0 for new file, 1 for first old file, etc
371 [MarshalAs(UnmanagedType.LPStr)] string symbolFileName,
372 uint symType, // see SYM_TYPE in imagehlp.h
373 uint symbolFileCheckSum,
374 uint symbolFileTimeDate,
375 uint imageFileCheckSum,
376 uint imageFileTimeDate,
377 IntPtr context
378 );
379
380 /// <summary>
381 /// Wraps PATCH_IGNORE_RANGE
382 /// </summary>
383 [StructLayout(LayoutKind.Sequential)]
384 internal class PatchIgnoreRange
385 {
386 public uint offsetInOldFile;
387 public uint lengthInBytes;
388 }
389
390 /// <summary>
391 /// Wraps PATCH_RETAIN_RANGE
392 /// </summary>
393 [StructLayout(LayoutKind.Sequential)]
394 internal class PatchRetainRange
395 {
396 public uint offsetInOldFile;
397 public uint lengthInBytes;
398 public uint offsetInNewFile;
399 }
400
401 /// <summary>
402 /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion)
403 /// </summary>
404 internal class PatchOldFileInfo
405 {
406 public PatchIgnoreRange[] ignoreRange;
407 public PatchRetainRange[] retainRange;
408 }
409
410 /// <summary>
411 /// Wraps PATCH_OLD_FILE_INFO_W
412 /// </summary>
413 internal class PatchOldFileInfoW : PatchOldFileInfo
414 {
415 public string oldFileName;
416 }
417
418 /// <summary>
419 /// Wraps each PATCH_INTERLEAVE_MAP Range
420 /// </summary>
421 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)]
422 internal class PatchInterleaveMapRange
423 {
424 public uint oldOffset;
425 public uint oldLength;
426 public uint newLength;
427 }
428
429 /// <summary>
430 /// Wraps PATCH_INTERLEAVE_MAP
431 /// </summary>
432 internal class PatchInterleaveMap
433 {
434 public PatchInterleaveMapRange[] ranges = null;
435 }
436
437
438 /// <summary>
439 /// Wraps PATCH_OPTION_DATA
440 /// </summary>
441 [BestFitMapping(false, ThrowOnUnmappableChar = true)]
442 internal class PatchOptionData
443 {
444 public PatchSymbolFlags symbolOptionFlags; // PATCH_SYMBOL_xxx flags
445 [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode
446 [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ]
447 public uint extendedOptionFlags;
448 public PatchSymloadCallback symLoadCallback = null;
449 public IntPtr symLoadContext = IntPtr.Zero;
450 public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer)
451 public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer)
452 }
453
454 //
455 // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode)
456 // path argument is available, even when used with one of the Unicode APIs
457 // such as CreatePatchFileW. This is because the unlerlying system services
458 // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names.
459 //
460
461 //
462 // A note about PATCH_RETAIN_RANGE specifiers with multiple old files:
463 //
464 // Each old version file must have the same RetainRangeCount, and the same
465 // retain range LengthInBytes and OffsetInNewFile values in the same order.
466 // Only the OffsetInOldFile values can differ between old foles for retain
467 // ranges.
468 //
469
470 //
471 // The following prototypes are (some of the) interfaces for creating patches from files.
472 //
473
474 /// <summary>
475 /// Creates a new delta.
476 /// </summary>
477 /// <param name="oldFileCount">Size of oldFileInfoArray.</param>
478 /// <param name="oldFileInfoArray">Target file information.</param>
479 /// <param name="newFileName">Name of updated file.</param>
480 /// <param name="patchFileName">Name of delta to create.</param>
481 /// <param name="optionFlags">PATCH_OPTION_xxx.</param>
482 /// <param name="optionData">Optional PATCH_OPTION_DATA structure.</param>
483 /// <param name="progressCallback">Delegate for progress callbacks.</param>
484 /// <param name="context">Context for progress callback delegate.</param>
485 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
486 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
487 [return: MarshalAs(UnmanagedType.Bool)]
488 internal static extern bool CreatePatchFileExW(
489 uint oldFileCount, // maximum 255
490 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")]
491 PatchOldFileInfoW[] oldFileInfoArray,
492 string newFileName, // input file (required)
493 string patchFileName, // output file (required)
494 uint optionFlags,
495 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")]
496 PatchOptionData optionData,
497 [MarshalAs (UnmanagedType.FunctionPtr)]
498 PatchProgressCallback progressCallback,
499 IntPtr context
500 );
501
502 /// <summary>
503 /// Extracts delta header from delta.
504 /// </summary>
505 /// <param name="patchFileName">Name of delta file.</param>
506 /// <param name="patchHeaderFileName">Name of file to create with delta header.</param>
507 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
508 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
509 [return: MarshalAs(UnmanagedType.Bool)]
510 internal static extern bool ExtractPatchHeaderToFileW(
511 string patchFileName, // input file
512 string patchHeaderFileName // output file
513 );
514
515 // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks
516
517 /// <summary>
518 /// Marshals arguments for the CreatePatch~ APIs
519 /// </summary>
520 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
521 internal class PatchAPIMarshaler : ICustomMarshaler
522 {
523 internal static ICustomMarshaler GetInstance(string cookie)
524 {
525 return new PatchAPIMarshaler(cookie);
526 }
527
528 private enum MarshalType
529 {
530 PATCH_OPTION_DATA,
531 PATCH_OLD_FILE_INFO_W
532 };
533
534 private readonly PatchAPIMarshaler.MarshalType marshalType;
535
536 private PatchAPIMarshaler(string cookie)
537 {
538 this.marshalType = (PatchAPIMarshaler.MarshalType)Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie);
539 }
540
541 //
542 // Summary:
543 // Returns the size of the native data to be marshaled.
544 //
545 // Returns:
546 // The size in bytes of the native data.
547 public int GetNativeDataSize()
548 {
549 return Marshal.SizeOf(typeof(IntPtr));
550 }
551
552 //
553 // Summary:
554 // Performs necessary cleanup of the managed data when it is no longer needed.
555 //
556 // Parameters:
557 // ManagedObj:
558 // The managed object to be destroyed.
559 public void CleanUpManagedData(object ManagedObj)
560 {
561 }
562
563 //
564 // Summary:
565 // Performs necessary cleanup of the unmanaged data when it is no longer needed.
566 //
567 // Parameters:
568 // pNativeData:
569 // A pointer to the unmanaged data to be destroyed.
570 public void CleanUpNativeData(IntPtr pNativeData)
571 {
572 if (IntPtr.Zero == pNativeData)
573 {
574 return;
575 }
576
577 switch (this.marshalType)
578 {
579 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
580 this.CleanUpPOD(pNativeData);
581 break;
582 default:
583 this.CleanUpPOFI_A(pNativeData);
584 break;
585 }
586 }
587
588 //
589 // Summary:
590 // Converts the managed data to unmanaged data.
591 //
592 // Parameters:
593 // ManagedObj:
594 // The managed object to be converted.
595 //
596 // Returns:
597 // Returns the COM view of the managed object.
598 public IntPtr MarshalManagedToNative(object ManagedObj)
599 {
600 if (null == ManagedObj)
601 {
602 return IntPtr.Zero;
603 }
604
605 switch (this.marshalType)
606 {
607 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
608 return this.MarshalPOD(ManagedObj as PatchOptionData);
609 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
610 return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]);
611 default:
612 throw new InvalidOperationException();
613 }
614 }
615
616
617 //
618 // Summary:
619 // Converts the unmanaged data to managed data.
620 //
621 // Parameters:
622 // pNativeData:
623 // A pointer to the unmanaged data to be wrapped.
624 //
625 // Returns:
626 // Returns the managed view of the COM data.
627 public object MarshalNativeToManaged(IntPtr pNativeData)
628 {
629 return null;
630 }
631
632 // Implementation *************************************************
633
634 // PATCH_OPTION_DATA offsets
635 private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32));
636 private static readonly int newFileSymbolPathOffset = 2 * Marshal.SizeOf(typeof(Int32));
637 private static readonly int oldFileSymbolPathArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
638 private static readonly int extendedOptionFlagsOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
639 private static readonly int symLoadCallbackOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
640 private static readonly int symLoadContextOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr));
641 private static readonly int interleaveMapArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 4 * Marshal.SizeOf(typeof(IntPtr));
642 private static readonly int maxLzxWindowSizeOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr));
643 private static readonly int patchOptionDataSize = 4 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr));
644
645 // PATCH_OLD_FILE_INFO offsets
646 private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32));
647 private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
648 private static readonly int ignoreRangeArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
649 private static readonly int retainRangeCountOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
650 private static readonly int retainRangeArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
651 private static readonly int patchOldFileInfoSize = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr));
652
653 // Methods and data used to preserve data needed for cleanup
654
655 // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount)
656 private static readonly Dictionary<IntPtr, int> OldFileCounts = new Dictionary<IntPtr, int>();
657 private static readonly object OldFileCountsLock = new object();
658
659 private IntPtr CreateMainStruct(int oldFileCount)
660 {
661 int nativeSize;
662 switch (this.marshalType)
663 {
664 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
665 nativeSize = patchOptionDataSize;
666 break;
667 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
668 nativeSize = oldFileCount * patchOldFileInfoSize;
669 break;
670 default:
671 throw new InvalidOperationException();
672 }
673
674 var native = Marshal.AllocCoTaskMem(nativeSize);
675
676 lock (PatchAPIMarshaler.OldFileCountsLock)
677 {
678 PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount);
679 }
680
681 return native;
682 }
683
684 private static void ReleaseMainStruct(IntPtr native)
685 {
686 lock (PatchAPIMarshaler.OldFileCountsLock)
687 {
688 PatchAPIMarshaler.OldFileCounts.Remove(native);
689 }
690 Marshal.FreeCoTaskMem(native);
691 }
692
693 private static int GetOldFileCount(IntPtr native)
694 {
695 lock (PatchAPIMarshaler.OldFileCountsLock)
696 {
697 return PatchAPIMarshaler.OldFileCounts[native];
698 }
699 }
700
701 // Helper methods
702
703 private static IntPtr OptionalAnsiString(string managed)
704 {
705 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed);
706 }
707
708 private static IntPtr OptionalUnicodeString(string managed)
709 {
710 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed);
711 }
712
713 // string array must be of the same length as the number of old files
714 private static IntPtr CreateArrayOfStringA(string[] managed)
715 {
716 if (null == managed)
717 {
718 return IntPtr.Zero;
719 }
720
721 var size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
722 var native = Marshal.AllocCoTaskMem(size);
723
724 for (var i = 0; i < managed.Length; ++i)
725 {
726 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i]));
727 }
728
729 return native;
730 }
731
732 // string array must be of the same length as the number of old files
733 private static IntPtr CreateArrayOfStringW(string[] managed)
734 {
735 if (null == managed)
736 {
737 return IntPtr.Zero;
738 }
739
740 var size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
741 var native = Marshal.AllocCoTaskMem(size);
742
743 for (var i = 0; i < managed.Length; ++i)
744 {
745 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i]));
746 }
747
748 return native;
749 }
750
751 private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed)
752 {
753 if (null == managed)
754 {
755 return IntPtr.Zero;
756 }
757
758 if (null == managed.ranges)
759 {
760 return IntPtr.Zero;
761 }
762
763 if (0 == managed.ranges.Length)
764 {
765 return IntPtr.Zero;
766 }
767
768 var native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32))
769 + managed.ranges.Length * (Marshal.SizeOf(typeof(PatchInterleaveMap))));
770 WriteUInt32(native, (uint)managed.ranges.Length);
771
772 for (var i = 0; i < managed.ranges.Length; ++i)
773 {
774 Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i * Marshal.SizeOf(typeof(PatchInterleaveMap))), false);
775 }
776 return native;
777 }
778
779 private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed)
780 {
781 if (null == managed)
782 {
783 return IntPtr.Zero;
784 }
785
786 var native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr)));
787
788 for (var i = 0; i < managed.Length; ++i)
789 {
790 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i]));
791 }
792
793 return native;
794 }
795
796 private static void WriteUInt32(IntPtr native, uint data)
797 {
798 Marshal.WriteInt32(native, unchecked((int)data));
799 }
800
801 private static void WriteUInt32(IntPtr native, int offset, uint data)
802 {
803 Marshal.WriteInt32(native, offset, unchecked((int)data));
804 }
805
806 // Marshal operations
807
808 private IntPtr MarshalPOD(PatchOptionData managed)
809 {
810 if (null == managed)
811 {
812 throw new ArgumentNullException("managed");
813 }
814
815 var native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length);
816 Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct
817 WriteUInt32(native, symbolOptionFlagsOffset, (uint)managed.symbolOptionFlags);
818 Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath));
819 Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray));
820 WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags);
821
822 // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null.
823 if (null == managed.symLoadCallback)
824 {
825 Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero);
826 }
827 else
828 {
829 Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback));
830 }
831
832 Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext);
833 Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray));
834 WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize);
835 return native;
836 }
837
838 private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed)
839 {
840 if (null == managed)
841 {
842 throw new ArgumentNullException("managed");
843 }
844
845 if (0 == managed.Length)
846 {
847 return IntPtr.Zero;
848 }
849
850 var native = this.CreateMainStruct(managed.Length);
851
852 for (var i = 0; i < managed.Length; ++i)
853 {
854 PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize));
855 }
856
857 return native;
858 }
859
860 private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native)
861 {
862 PatchAPIMarshaler.MarshalPOFI(managed, native);
863 Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName
864 }
865
866 private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native)
867 {
868 Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct
869 WriteUInt32(native, ignoreRangeCountOffset,
870 (null == managed.ignoreRange) ? 0 : (uint)managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255
871 Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray
872 WriteUInt32(native, retainRangeCountOffset,
873 (null == managed.retainRange) ? 0 : (uint)managed.retainRange.Length); // RetainRangeCount // maximum 255
874 Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray
875 }
876
877 private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array)
878 {
879 if (null == array)
880 {
881 return IntPtr.Zero;
882 }
883
884 if (0 == array.Length)
885 {
886 return IntPtr.Zero;
887 }
888
889 var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchIgnoreRange)));
890
891 for (var i = 0; i < array.Length; ++i)
892 {
893 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchIgnoreRange)))), false);
894 }
895
896 return native;
897 }
898
899 private static IntPtr MarshalPRRArray(PatchRetainRange[] array)
900 {
901 if (null == array)
902 {
903 return IntPtr.Zero;
904 }
905
906 if (0 == array.Length)
907 {
908 return IntPtr.Zero;
909 }
910
911 var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchRetainRange)));
912
913 for (var i = 0; i < array.Length; ++i)
914 {
915 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchRetainRange)))), false);
916 }
917
918 return native;
919 }
920
921 // CleanUp operations
922
923 private void CleanUpPOD(IntPtr native)
924 {
925 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset));
926
927 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset))
928 {
929 for (var i = 0; i < GetOldFileCount(native); ++i)
930 {
931 Marshal.FreeCoTaskMem(
932 Marshal.ReadIntPtr(
933 Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset),
934 i * Marshal.SizeOf(typeof(IntPtr))));
935 }
936
937 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset));
938 }
939
940 if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset))
941 {
942 for (var i = 0; i < GetOldFileCount(native); ++i)
943 {
944 Marshal.FreeCoTaskMem(
945 Marshal.ReadIntPtr(
946 Marshal.ReadIntPtr(native, interleaveMapArrayOffset),
947 i * Marshal.SizeOf(typeof(IntPtr))));
948 }
949
950 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset));
951 }
952
953 PatchAPIMarshaler.ReleaseMainStruct(native);
954 }
955
956 private void CleanUpPOFI_A(IntPtr native)
957 {
958 for (var i = 0; i < GetOldFileCount(native); ++i)
959 {
960 PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i * patchOldFileInfoSize));
961 }
962
963 PatchAPIMarshaler.ReleaseMainStruct(native);
964 }
965
966 private static void CleanUpPOFI(IntPtr native)
967 {
968 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset))
969 {
970 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset));
971 }
972
973 PatchAPIMarshaler.CleanUpPOFIH(native);
974 }
975
976 private static void CleanUpPOFIH(IntPtr native)
977 {
978 if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset))
979 {
980 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset));
981 }
982
983 if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset))
984 {
985 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset));
986 }
987 }
988 }
989 }
990}