mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 03:20:25 +08:00
541 lines
24 KiB
C#
541 lines
24 KiB
C#
// Copyright (C) 2025, The Duplicati Team
|
|
// https://duplicati.com, hello@duplicati.com
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
namespace Duplicati.Library.Snapshots.USN
|
|
{
|
|
//The signatures in this file are from http://pinvoke.net
|
|
|
|
/// <summary>
|
|
/// Various Windows specific calls to support USN
|
|
/// </summary>
|
|
[SupportedOSPlatform("windows")]
|
|
internal static class Win32USN
|
|
{
|
|
public const int ERROR_HANDLE_EOF = 38;
|
|
public const int ERROR_INSUFFICIENT_BUFFER = 122;
|
|
public const int ERROR_SUCCESS = 0;
|
|
public const int ERROR_JOURNAL_ENTRY_DELETED = 1181;
|
|
|
|
#region Structures
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct USN_JOURNAL_DATA_V0
|
|
{
|
|
/// <summary>
|
|
/// The current journal identifier. A journal is assigned a new identifier on creation and can be
|
|
/// stamped with a new identifier in the course of its existence.
|
|
/// The NTFS file system uses this identifier for an integrity check.
|
|
/// </summary>
|
|
public readonly long UsnJournalID;
|
|
|
|
/// <summary>
|
|
/// The number of first record that can be read from the journal.
|
|
/// </summary>
|
|
public readonly long FirstUsn;
|
|
|
|
/// <summary>
|
|
/// The number of next record to be written to the journal.
|
|
/// </summary>
|
|
public readonly long NextUsn;
|
|
|
|
/// <summary>
|
|
/// The first record that was written into the journal for this journal instance.
|
|
/// Enumerating the files or directories on a volume can return a USN lower than this value
|
|
/// (in other words, a FirstUsn member value less than the LowestValidUsn member value).
|
|
/// If it does, the journal has been stamped with a new identifier since the last USN was written.
|
|
/// In this case, LowestValidUsn may indicate a discontinuity in the journal, in which changes
|
|
/// to some or all files or directories on the volume may have occurred that are not recorded in the change journal.
|
|
/// </summary>
|
|
public readonly long LowestValidUsn;
|
|
|
|
/// <summary>
|
|
/// The largest USN that the change journal supports. An administrator must delete
|
|
/// the change journal as the value of NextUsn approaches this value.
|
|
/// </summary>
|
|
public readonly long MaxUsn;
|
|
|
|
/// <summary>
|
|
/// The target maximum size for the change journal, in bytes. The change journal can grow larger
|
|
/// than this value, but it is then truncated at the next NTFS file system checkpoint to less than this value.
|
|
/// </summary>
|
|
public readonly long MaximumSize;
|
|
|
|
/// <summary>
|
|
/// The number of bytes of disk memory added to the end and removed from the beginning of the change
|
|
/// journal each time memory is allocated or deallocated. In other words, allocation and deallocation
|
|
/// take place in units of this size. An integer multiple of a cluster size is a reasonable value for this member.
|
|
/// </summary>
|
|
public readonly long AllocationDelta; //DWORDLONG AllocationDelta
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct READ_USN_JOURNAL_DATA_V0
|
|
{
|
|
public long StartUsn;
|
|
public USNReason ReasonMask;
|
|
public uint ReturnOnlyOnClose;
|
|
public ulong Timeout;
|
|
public ulong BytesToWaitFor;
|
|
public long UsnJournalID;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct USN_RECORD_V2
|
|
{
|
|
public readonly uint RecordLength;
|
|
public readonly ushort MajorVersion;
|
|
public readonly ushort MinorVersion;
|
|
public readonly ulong FileReferenceNumber;
|
|
public readonly ulong ParentFileReferenceNumber;
|
|
public readonly long Usn;
|
|
public readonly long TimeStamp; // strictly, this is a LARGE_INTEGER in C
|
|
public readonly USNReason Reason;
|
|
public readonly uint SourceInfo;
|
|
public readonly uint SecurityId;
|
|
public readonly FileAttributes FileAttributes;
|
|
public readonly ushort FileNameLength;
|
|
public readonly ushort FileNameOffset;
|
|
// immediately after the FileNameOffset comes an array of WCHARs containing the FileName
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct MFT_ENUM_DATA
|
|
{
|
|
public ulong StartFileReferenceNumber;
|
|
public long LowUsn;
|
|
public long HighUsn;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct BY_HANDLE_FILE_INFORMATION
|
|
{
|
|
public readonly uint FileAttributes;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
|
|
public readonly uint VolumeSerialNumber;
|
|
public readonly uint FileSizeHigh;
|
|
public readonly uint FileSizeLow;
|
|
public readonly uint NumberOfLinks;
|
|
public readonly uint FileIndexHigh;
|
|
public readonly uint FileIndexLow;
|
|
}
|
|
#endregion
|
|
|
|
#region Enums
|
|
[Flags]
|
|
public enum EMethod : uint
|
|
{
|
|
Buffered = 0,
|
|
InDirect = 1,
|
|
OutDirect = 2,
|
|
Neither = 3
|
|
}
|
|
|
|
[Flags]
|
|
public enum FsCtl : uint
|
|
{
|
|
/// <summary>
|
|
/// Causes a journal to be queried when used with DeviceIoControl
|
|
/// </summary>
|
|
/// <remarks>FSCTL_QUERY_USN_JOURNAL</remarks>
|
|
QueryUSNJournal = 0x000900f4,
|
|
|
|
/// <summary>
|
|
/// Causes a journal to be read when used with DeviceIoControl
|
|
/// </summary>
|
|
/// <remarks>FSCTL_READ_USN_JOURNAL</remarks>
|
|
ReadUSNJournal = 0x000900bb,
|
|
|
|
/// <summary>
|
|
/// Enumerates the update sequence number (USN) data between two specified boundaries to obtain master file table (MFT) records.
|
|
/// </summary>
|
|
/// <remarks>FSCTL_ENUM_USN_DATA</remarks>
|
|
EnumUSNData = 0x000900b3
|
|
}
|
|
|
|
[Flags]
|
|
public enum FileAccess : uint
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
GenericRead = 0x80000000,
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
GenericWrite = 0x40000000,
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
GenericExecute = 0x20000000,
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
GenericAll = 0x10000000
|
|
}
|
|
|
|
[Flags]
|
|
public enum FileShare : uint
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
None = 0x00000000,
|
|
/// <summary>
|
|
/// Enables subsequent open operations on an object to request read access.
|
|
/// Otherwise, other processes cannot open the object if they request read access.
|
|
/// If this flag is not specified, but the object has been opened for read access, the function fails.
|
|
/// </summary>
|
|
Read = 0x00000001,
|
|
/// <summary>
|
|
/// Enables subsequent open operations on an object to request write access.
|
|
/// Otherwise, other processes cannot open the object if they request write access.
|
|
/// If this flag is not specified, but the object has been opened for write access, the function fails.
|
|
/// </summary>
|
|
Write = 0x00000002,
|
|
/// <summary>
|
|
/// Enables subsequent open operations on an object to request delete access.
|
|
/// Otherwise, other processes cannot open the object if they request delete access.
|
|
/// If this flag is not specified, but the object has been opened for delete access, the function fails.
|
|
/// </summary>
|
|
Delete = 0x00000004,
|
|
/// <summary>
|
|
/// Combination of read and write
|
|
/// </summary>
|
|
ReadWrite = Read | Write,
|
|
/// <summary>
|
|
/// Combo flag that specifies all access
|
|
/// </summary>
|
|
All = None | Read | Write
|
|
}
|
|
|
|
public enum CreationDisposition : uint
|
|
{
|
|
/// <summary>
|
|
/// Creates a new file. The function fails if a specified file exists.
|
|
/// </summary>
|
|
New = 1,
|
|
/// <summary>
|
|
/// Creates a new file, always.
|
|
/// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes,
|
|
/// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
|
|
/// </summary>
|
|
CreateAlways = 2,
|
|
/// <summary>
|
|
/// Opens a file. The function fails if the file does not exist.
|
|
/// </summary>
|
|
OpenExisting = 3,
|
|
/// <summary>
|
|
/// Opens a file, always.
|
|
/// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
|
|
/// </summary>
|
|
OpenAlways = 4,
|
|
/// <summary>
|
|
/// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
|
|
/// The calling process must open the file with the GENERIC_WRITE access right.
|
|
/// </summary>
|
|
TruncateExisting = 5
|
|
}
|
|
|
|
[Flags]
|
|
public enum FileAttributes : uint
|
|
{
|
|
None = 0x0,
|
|
Readonly = 0x00000001,
|
|
Hidden = 0x00000002,
|
|
System = 0x00000004,
|
|
Directory = 0x00000010,
|
|
Archive = 0x00000020,
|
|
Device = 0x00000040,
|
|
Normal = 0x00000080,
|
|
Temporary = 0x00000100,
|
|
SparseFile = 0x00000200,
|
|
ReparsePoint = 0x00000400,
|
|
Compressed = 0x00000800,
|
|
Offline = 0x00001000,
|
|
NotContentIndexed = 0x00002000,
|
|
Encrypted = 0x00004000,
|
|
WriteThrough = 0x80000000,
|
|
Overlapped = 0x40000000,
|
|
NoBuffering = 0x20000000,
|
|
RandomAccess = 0x10000000,
|
|
SequentialScan = 0x08000000,
|
|
DeleteOnClose = 0x04000000,
|
|
BackupSemantics = 0x02000000,
|
|
PosixSemantics = 0x01000000,
|
|
OpenReparsePoint = 0x00200000,
|
|
OpenNoRecall = 0x00100000,
|
|
FirstPipeInstance = 0x00080000
|
|
}
|
|
|
|
[Flags]
|
|
public enum USNReason : uint
|
|
{
|
|
USN_REASON_DATA_OVERWRITE = 0x00000001,
|
|
USN_REASON_DATA_EXTEND = 0x00000002,
|
|
USN_REASON_DATA_TRUNCATION = 0x00000004,
|
|
USN_REASON_NAMED_DATA_OVERWRITE = 0x00000010,
|
|
USN_REASON_NAMED_DATA_EXTEND = 0x00000020,
|
|
USN_REASON_NAMED_DATA_TRUNCATION = 0x00000040,
|
|
USN_REASON_FILE_CREATE = 0x00000100,
|
|
USN_REASON_FILE_DELETE = 0x00000200,
|
|
USN_REASON_EA_CHANGE = 0x00000400,
|
|
USN_REASON_SECURITY_CHANGE = 0x00000800,
|
|
USN_REASON_RENAME_OLD_NAME = 0x00001000,
|
|
USN_REASON_RENAME_NEW_NAME = 0x00002000,
|
|
USN_REASON_INDEXABLE_CHANGE = 0x00004000,
|
|
USN_REASON_BASIC_INFO_CHANGE = 0x00008000,
|
|
USN_REASON_HARD_LINK_CHANGE = 0x00010000,
|
|
USN_REASON_COMPRESSION_CHANGE = 0x00020000,
|
|
USN_REASON_ENCRYPTION_CHANGE = 0x00040000,
|
|
USN_REASON_OBJECT_ID_CHANGE = 0x00080000,
|
|
USN_REASON_REPARSE_POINT_CHANGE = 0x00100000,
|
|
USN_REASON_STREAM_CHANGE = 0x00200000,
|
|
USN_REASON_CLOSE = 0x80000000,
|
|
USN_REASON_ANY =
|
|
USN_REASON_DATA_OVERWRITE |
|
|
USN_REASON_DATA_EXTEND |
|
|
USN_REASON_DATA_TRUNCATION |
|
|
USN_REASON_NAMED_DATA_OVERWRITE |
|
|
USN_REASON_NAMED_DATA_EXTEND |
|
|
USN_REASON_NAMED_DATA_TRUNCATION |
|
|
USN_REASON_FILE_CREATE |
|
|
USN_REASON_FILE_DELETE |
|
|
USN_REASON_EA_CHANGE |
|
|
USN_REASON_SECURITY_CHANGE |
|
|
USN_REASON_RENAME_OLD_NAME |
|
|
USN_REASON_RENAME_NEW_NAME |
|
|
USN_REASON_INDEXABLE_CHANGE |
|
|
USN_REASON_BASIC_INFO_CHANGE |
|
|
USN_REASON_HARD_LINK_CHANGE |
|
|
USN_REASON_COMPRESSION_CHANGE |
|
|
USN_REASON_ENCRYPTION_CHANGE |
|
|
USN_REASON_OBJECT_ID_CHANGE |
|
|
USN_REASON_REPARSE_POINT_CHANGE |
|
|
USN_REASON_STREAM_CHANGE |
|
|
USN_REASON_CLOSE
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Function calls
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// The CreateFile function creates or opens a file, file stream, directory, physical disk, volume, console buffer, tape drive,
|
|
/// communications resource, mailslot, or named pipe. The function returns a handle that can be used to access an object.
|
|
/// </summary>
|
|
/// <param name="lpFileName"></param>
|
|
/// <param name="dwDesiredAccess"> access to the object, which can be read, write, or both</param>
|
|
/// <param name="dwShareMode">The sharing mode of an object, which can be read, write, both, or none</param>
|
|
/// <param name="SecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure that determines whether or not the returned handle can
|
|
/// be inherited by child processes. Can be null</param>
|
|
/// <param name="dwCreationDisposition">An action to take on files that exist and do not exist</param>
|
|
/// <param name="dwFlagsAndAttributes">The file attributes and flags. </param>
|
|
/// <param name="hTemplateFile">A handle to a template file with the GENERIC_READ access right. The template file supplies file attributes
|
|
/// and extended attributes for the file that is being created. This parameter can be null</param>
|
|
/// <returns>If the function succeeds, the return value is an open handle to a specified file. If a specified file exists before the function
|
|
/// all and dwCreationDisposition is CREATE_ALWAYS or OPEN_ALWAYS, a call to GetLastError returns ERROR_ALREADY_EXISTS, even when the function
|
|
/// succeeds. If a file does not exist before the call, GetLastError returns 0 (zero).
|
|
/// If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
|
|
/// </returns>
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
|
public static extern SafeFileHandle CreateFile(
|
|
string lpFileName,
|
|
FileAccess dwDesiredAccess,
|
|
FileShare dwShareMode,
|
|
IntPtr SecurityAttributes,
|
|
CreationDisposition dwCreationDisposition,
|
|
FileAttributes dwFlagsAndAttributes,
|
|
IntPtr hTemplateFile
|
|
);
|
|
|
|
/// <summary>
|
|
/// Sends the dwIoControlCode to the device specified by hDevice.
|
|
/// </summary>
|
|
/// <param name="hDevice">Safe handle to the device </param>
|
|
/// <param name="IoControlCode">Device IO Control Code to send</param>
|
|
/// <param name="InBuffer">Input buffer if required</param>
|
|
/// <param name="nInBufferSize">Size of input buffer</param>
|
|
/// <param name="OutBuffer">Output buffer if required</param>
|
|
/// <param name="nOutBufferSize">Size of output buffer</param>
|
|
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
|
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
|
/// <returns>'true' if successful, otherwise 'false'</returns>
|
|
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
|
public static extern bool DeviceIoControl(
|
|
SafeFileHandle hDevice,
|
|
FsCtl IoControlCode,
|
|
[In] READ_USN_JOURNAL_DATA_V0 InBuffer,
|
|
uint nInBufferSize,
|
|
[In] IntPtr OutBuffer,
|
|
uint nOutBufferSize,
|
|
ref uint pBytesReturned,
|
|
[In] IntPtr overlapped //[In] ref System.Threading.NativeOverlapped Overlapped
|
|
);
|
|
|
|
/// <summary>
|
|
/// Sends the dwIoControlCode to the device specified by hDevice.
|
|
/// </summary>
|
|
/// <param name="hDevice">Safe handle to the device </param>
|
|
/// <param name="IoControlCode">Device IO Control Code to send</param>
|
|
/// <param name="InBuffer">Input buffer if required</param>
|
|
/// <param name="nInBufferSize">Size of input buffer</param>
|
|
/// <param name="OutBuffer">Output buffer if required</param>
|
|
/// <param name="nOutBufferSize">Size of output buffer</param>
|
|
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
|
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
|
/// <returns>'true' if successful, otherwise 'false'</returns>
|
|
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
|
public static extern bool DeviceIoControl(
|
|
SafeFileHandle hDevice,
|
|
FsCtl IoControlCode,
|
|
[In] ref MFT_ENUM_DATA InBuffer,
|
|
uint nInBufferSize,
|
|
[In] IntPtr OutBuffer,
|
|
uint nOutBufferSize,
|
|
ref uint pBytesReturned,
|
|
[In] IntPtr overlapped //[In] ref System.Threading.NativeOverlapped Overlapped
|
|
);
|
|
|
|
/// <summary>
|
|
/// Sends the dwIoControlCode to the device specified by hDevice.
|
|
/// </summary>
|
|
/// <param name="hDevice">Safe handle to the device </param>
|
|
/// <param name="dwIoControlCode">Device IO Control Code to send</param>
|
|
/// <param name="lpInBuffer">Input buffer if required</param>
|
|
/// <param name="nInBufferSize">Size of input buffer</param>
|
|
/// <param name="lpOutBuffer">Output buffer if required</param>
|
|
/// <param name="nOutBufferSize">Size of output buffer</param>
|
|
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
|
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
|
/// <returns>'true' if successful, otherwise 'false'</returns>
|
|
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
|
public static extern bool DeviceIoControl(
|
|
SafeFileHandle hDevice,
|
|
uint dwIoControlCode,
|
|
IntPtr lpInBuffer,
|
|
uint nInBufferSize,
|
|
IntPtr lpOutBuffer,
|
|
uint nOutBufferSize,
|
|
out uint pBytesReturned,
|
|
IntPtr overlapped
|
|
);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool GetFileInformationByHandle(
|
|
[In] SafeFileHandle hFile,
|
|
[Out] out BY_HANDLE_FILE_INFORMATION lpFileInformation
|
|
);
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Sends the control code to the device specified by handle.
|
|
/// </summary>
|
|
/// <typeparam name="TStructure"></typeparam>
|
|
/// <param name="handle"></param>
|
|
/// <param name="code"></param>
|
|
/// <param name="structure"></param>
|
|
/// <param name="bufferSize">Maximum size of returned buffer</param>
|
|
/// <returns></returns>
|
|
public static bool ControlWithInput<TStructure>(
|
|
SafeFileHandle handle, FsCtl code,
|
|
ref TStructure structure, int bufferSize, out byte[] buffer)
|
|
where TStructure : struct
|
|
{
|
|
uint datalen;
|
|
bool controlResult;
|
|
|
|
buffer = new byte[bufferSize];
|
|
var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
var structureHandle = GCHandle.Alloc(structure, GCHandleType.Pinned);
|
|
var bufferPointer = bufferHandle.AddrOfPinnedObject();
|
|
var structurePointer = structureHandle.AddrOfPinnedObject();
|
|
|
|
try
|
|
{
|
|
controlResult =
|
|
DeviceIoControl(handle, (uint)code,
|
|
structurePointer, (uint)Marshal.SizeOf(structure),
|
|
bufferPointer, (uint)buffer.Length,
|
|
out datalen, IntPtr.Zero);
|
|
}
|
|
finally
|
|
{
|
|
structureHandle.Free();
|
|
bufferHandle.Free();
|
|
}
|
|
|
|
Array.Resize(ref buffer, (int)datalen);
|
|
|
|
return controlResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the control code to the device specified by handle.
|
|
/// </summary>
|
|
/// <typeparam name="TStructure"></typeparam>
|
|
/// <param name="handle"></param>
|
|
/// <param name="code"></param>
|
|
/// <param name="structure"></param>
|
|
internal static bool ControlWithOutput<TStructure>(
|
|
SafeFileHandle handle, FsCtl code, ref TStructure structure)
|
|
where TStructure : struct
|
|
{
|
|
bool controlResult;
|
|
|
|
//get our object pointer
|
|
var structureHandle = GCHandle.Alloc(structure, GCHandleType.Pinned);
|
|
var structurePointer = structureHandle.AddrOfPinnedObject();
|
|
|
|
try
|
|
{
|
|
controlResult =
|
|
DeviceIoControl(handle, (uint)code,
|
|
IntPtr.Zero, 0, structurePointer,
|
|
(uint)Marshal.SizeOf(structure),
|
|
out _, IntPtr.Zero);
|
|
}
|
|
finally
|
|
{
|
|
// always release GH handle
|
|
structureHandle.Free();
|
|
}
|
|
|
|
if (controlResult)
|
|
{
|
|
structure = (TStructure)Marshal.PtrToStructure(structurePointer, typeof(TStructure));
|
|
}
|
|
|
|
return controlResult;
|
|
|
|
}
|
|
}
|
|
}
|