From 4ef4ece88f9349953606c394e37d589c4a29f787 Mon Sep 17 00:00:00 2001 From: David Hall Date: Fri, 26 Jul 2019 16:44:17 -0600 Subject: [PATCH] Finished unit tests and fixes for winbase.h resource functions --- PInvoke/Kernel32/WinBase.Resource.cs | 130 +++++++++++++-------- UnitTests/PInvoke/Kernel32/Kernel32.csproj | 1 + .../PInvoke/Kernel32/WinBase.ResourceTests.cs | 51 ++++++++ 3 files changed, 133 insertions(+), 49 deletions(-) create mode 100644 UnitTests/PInvoke/Kernel32/WinBase.ResourceTests.cs diff --git a/PInvoke/Kernel32/WinBase.Resource.cs b/PInvoke/Kernel32/WinBase.Resource.cs index 159406dc..19674d13 100644 --- a/PInvoke/Kernel32/WinBase.Resource.cs +++ b/PInvoke/Kernel32/WinBase.Resource.cs @@ -5,65 +5,70 @@ namespace Vanara.PInvoke { public static partial class Kernel32 { - /// Retrieves a handle that can be used by the UpdateResource function to add, delete, or replace resources in a binary module. + /// + /// Retrieves a handle that can be used by the UpdateResource function to add, delete, or replace resources in a binary module. + /// /// /// Type: LPCTSTR /// - /// The binary file in which to update resources. An application must be able to obtain write-access to this file; the file referenced by pFileName - /// cannot be currently executing. If pFileName does not specify a full path, the system searches for the file in the current directory. + /// The binary file in which to update resources. An application must be able to obtain write-access to this file; the file + /// referenced by pFileName cannot be currently executing. If pFileName does not specify a full path, the system searches for the + /// file in the current directory. /// /// /// /// Type: BOOL /// - /// Indicates whether to delete the pFileName parameter's existing resources. If this parameter is TRUE, existing resources are deleted and the - /// updated file includes only resources added with the UpdateResource function. If this parameter is FALSE, the updated file includes - /// existing resources unless they are explicitly deleted or replaced by using UpdateResource. + /// Indicates whether to delete the pFileName parameter's existing resources. If this parameter is TRUE, existing resources + /// are deleted and the updated file includes only resources added with the UpdateResource function. If this parameter is + /// FALSE, the updated file includes existing resources unless they are explicitly deleted or replaced by using UpdateResource. /// /// /// /// Type: HANDLE /// - /// If the function succeeds, the return value is a handle that can be used by the UpdateResource and EndUpdateResource functions. The - /// return value is NULL if the specified file is not a PE, the file does not exist, or the file cannot be opened for writing. To get extended - /// error information, call GetLastError. + /// If the function succeeds, the return value is a handle that can be used by the UpdateResource and EndUpdateResource + /// functions. The return value is NULL if the specified file is not a PE, the file does not exist, or the file cannot be + /// opened for writing. To get extended error information, call GetLastError. /// /// - // HANDLE WINAPI BeginUpdateResource( _In_ LPCTSTR pFileName, _In_ BOOL bDeleteExistingResources); - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648030(v=vs.85).aspx + // HANDLE WINAPI BeginUpdateResource( _In_ LPCTSTR pFileName, _In_ BOOL bDeleteExistingResources); https://msdn.microsoft.com/en-us/library/windows/desktop/ms648030(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("Winbase.h", MSDNShortId = "ms648030")] - public static extern UpdateResourceHandle BeginUpdateResource(string pFileName, [MarshalAs(UnmanagedType.Bool)] bool bDeleteExistingResources); + public static extern SafeHUPDRES BeginUpdateResource(string pFileName, [MarshalAs(UnmanagedType.Bool)] bool bDeleteExistingResources); /// Commits or discards changes made prior to a call to UpdateResource. /// /// Type: HANDLE - /// A module handle returned by the BeginUpdateResource function, and used by UpdateResource, referencing the file to be updated. + /// + /// A module handle returned by the BeginUpdateResource function, and used by UpdateResource, referencing the file to + /// be updated. + /// /// /// /// Type: BOOL /// - /// Indicates whether to write the resource updates to the file. If this parameter is TRUE, no changes are made. If it is FALSE, the - /// changes are made: the resource updates will take effect. + /// Indicates whether to write the resource updates to the file. If this parameter is TRUE, no changes are made. If it is + /// FALSE, the changes are made: the resource updates will take effect. /// /// /// /// Type: BOOL /// - /// Returns TRUE if the function succeeds; FALSE otherwise. If the function succeeds and fDiscard is TRUE, then no resource updates - /// are made to the file; otherwise all successful resource updates are made to the file. To get extended error information, call GetLastError. + /// Returns TRUE if the function succeeds; FALSE otherwise. If the function succeeds and fDiscard is TRUE, then + /// no resource updates are made to the file; otherwise all successful resource updates are made to the file. To get extended error + /// information, call GetLastError. /// /// - // BOOL WINAPI EndUpdateResource( _In_ HANDLE hUpdate, _In_ BOOL fDiscard); - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648032(v=vs.85).aspx + // BOOL WINAPI EndUpdateResource( _In_ HANDLE hUpdate, _In_ BOOL fDiscard); https://msdn.microsoft.com/en-us/library/windows/desktop/ms648032(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("Winbase.h", MSDNShortId = "ms648032")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool EndUpdateResource([In] UpdateResourceHandle hUpdate, [MarshalAs(UnmanagedType.Bool)] bool fDiscard); + public static extern bool EndUpdateResource([In] HUPDRES hUpdate, [MarshalAs(UnmanagedType.Bool)] bool fDiscard); /// - /// Adds, deletes, or replaces a resource in a portable executable (PE) file. There are some restrictions on resource updates in files that contain - /// Resource Configuration (RC Config) data: language-neutral (LN) files and language-specific resource (.mui) files. + /// Adds, deletes, or replaces a resource in a portable executable (PE) file. There are some restrictions on resource updates in + /// files that contain Resource Configuration (RC Config) data: language-neutral (LN) files and language-specific resource (.mui) files. /// /// /// Type: HANDLE @@ -72,33 +77,34 @@ namespace Vanara.PInvoke /// /// Type: LPCTSTR /// - /// The resource type to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), where ID is an integer value - /// representing a predefined resource type. If the first character of the string is a pound sign (#), then the remaining characters represent a decimal - /// number that specifies the integer identifier of the resource type. For example, the string "#258" represents the identifier 258. + /// The resource type to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), where ID + /// is an integer value representing a predefined resource type. If the first character of the string is a pound sign (#), then the + /// remaining characters represent a decimal number that specifies the integer identifier of the resource type. For example, the + /// string "#258" represents the identifier 258. /// /// For a list of predefined resource types, see Resource Types. /// /// /// Type: LPCTSTR /// - /// The name of the resource to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), where ID is a - /// resource ID. When creating a new resource do not use a string that begins with a '#' character for this parameter. + /// The name of the resource to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), + /// where ID is a resource ID. When creating a new resource do not use a string that begins with a '#' character for this parameter. /// /// /// /// Type: WORD /// - /// The language identifier of the resource to be updated. For a list of the primary language identifiers and sublanguage identifiers that make up a - /// language identifier, see the MAKELANGID macro. + /// The language identifier of the resource to be updated. For a list of the primary language identifiers and sublanguage identifiers + /// that make up a language identifier, see the MAKELANGID macro. /// /// /// /// Type: LPVOID /// - /// The resource data to be inserted into the file indicated by hUpdate. If the resource is one of the predefined types, the data must be valid and - /// properly aligned. Note that this is the raw binary data to be stored in the file indicated by hUpdate, not the data provided by LoadIcon, - /// LoadString, or other resource-specific load functions. All data containing strings or text must be in Unicode format. lpData must not point to - /// ANSI data. + /// The resource data to be inserted into the file indicated by hUpdate. If the resource is one of the predefined types, the data + /// must be valid and properly aligned. Note that this is the raw binary data to be stored in the file indicated by hUpdate, not the + /// data provided by LoadIcon, LoadString, or other resource-specific load functions. All data containing strings or + /// text must be in Unicode format. lpData must not point to ANSI data. /// /// If lpData is NULL and cbData is 0, the specified resource is deleted from the file indicated by hUpdate. /// @@ -110,53 +116,53 @@ namespace Vanara.PInvoke /// Type: BOOL /// Returns TRUE if successful or FALSE otherwise. To get extended error information, call GetLastError. /// - // BOOL WINAPI UpdateResource( _In_ HANDLE hUpdate, _In_ LPCTSTR lpType, _In_ LPCTSTR lpName, _In_ WORD wLanguage, _In_opt_ LPVOID lpData, _In_ DWORD cbData); - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648049(v=vs.85).aspx + // BOOL WINAPI UpdateResource( _In_ HANDLE hUpdate, _In_ LPCTSTR lpType, _In_ LPCTSTR lpName, _In_ WORD wLanguage, _In_opt_ LPVOID + // lpData, _In_ DWORD cbData); https://msdn.microsoft.com/en-us/library/windows/desktop/ms648049(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("Winbase.h", MSDNShortId = "ms648049")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool UpdateResource([In] UpdateResourceHandle hUpdate, string lpType, string lpName, ushort wLanguage, [In] IntPtr lpData, uint cbData); + public static extern bool UpdateResource([In] HUPDRES hUpdate, SafeResourceId lpType, SafeResourceId lpName, ushort wLanguage, [In] IntPtr lpData, uint cbData); - /// Provides a handle that can be used by UpdateResource. + /// Provides a handle to an update resource. [StructLayout(LayoutKind.Sequential)] - public struct UpdateResourceHandle : IHandle + public struct HUPDRES : IHandle { private IntPtr handle; - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// An object that represents the pre-existing handle to use. - public UpdateResourceHandle(IntPtr preexistingHandle) => handle = preexistingHandle; + public HUPDRES(IntPtr preexistingHandle) => handle = preexistingHandle; - /// Returns an invalid handle by instantiating a object with . - public static UpdateResourceHandle NULL => new UpdateResourceHandle(IntPtr.Zero); + /// Returns an invalid handle by instantiating a object with . + public static HUPDRES NULL => new HUPDRES(IntPtr.Zero); /// Gets a value indicating whether this instance is a null handle. public bool IsNull => handle == IntPtr.Zero; - /// Performs an explicit conversion from to . + /// Performs an explicit conversion from to . /// The handle. /// The result of the conversion. - public static explicit operator IntPtr(UpdateResourceHandle h) => h.handle; + public static explicit operator IntPtr(HUPDRES h) => h.handle; - /// Performs an implicit conversion from to . + /// Performs an implicit conversion from to . /// The pointer to a handle. /// The result of the conversion. - public static implicit operator UpdateResourceHandle(IntPtr h) => new UpdateResourceHandle(h); + public static implicit operator HUPDRES(IntPtr h) => new HUPDRES(h); /// Implements the operator !=. /// The first handle. /// The second handle. /// The result of the operator. - public static bool operator !=(UpdateResourceHandle h1, UpdateResourceHandle h2) => !(h1 == h2); + public static bool operator !=(HUPDRES h1, HUPDRES h2) => !(h1 == h2); /// Implements the operator ==. /// The first handle. /// The second handle. /// The result of the operator. - public static bool operator ==(UpdateResourceHandle h1, UpdateResourceHandle h2) => h1.Equals(h2); + public static bool operator ==(HUPDRES h1, HUPDRES h2) => h1.Equals(h2); /// - public override bool Equals(object obj) => obj is UpdateResourceHandle h ? handle == h.handle : false; + public override bool Equals(object obj) => obj is HUPDRES h ? handle == h.handle : false; /// public override int GetHashCode() => handle.GetHashCode(); @@ -164,5 +170,31 @@ namespace Vanara.PInvoke /// public IntPtr DangerousGetHandle() => handle; } + + /// Provides a for that is disposed using . + public class SafeHUPDRES : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafeHUPDRES(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafeHUPDRES() : base() { } + + /// Indicates whether to discard any changes rather than write the resource updates to the file. + /// true to ignore changes; otherwise, false. + public bool IgnoreChanges { get; set; } = false; + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator HUPDRES(SafeHUPDRES h) => h.handle; + + /// + protected override bool InternalReleaseHandle() => EndUpdateResource(handle, IgnoreChanges); + } } } \ No newline at end of file diff --git a/UnitTests/PInvoke/Kernel32/Kernel32.csproj b/UnitTests/PInvoke/Kernel32/Kernel32.csproj index eed23566..a446d89d 100644 --- a/UnitTests/PInvoke/Kernel32/Kernel32.csproj +++ b/UnitTests/PInvoke/Kernel32/Kernel32.csproj @@ -48,6 +48,7 @@ + diff --git a/UnitTests/PInvoke/Kernel32/WinBase.ResourceTests.cs b/UnitTests/PInvoke/Kernel32/WinBase.ResourceTests.cs new file mode 100644 index 00000000..841a26b7 --- /dev/null +++ b/UnitTests/PInvoke/Kernel32/WinBase.ResourceTests.cs @@ -0,0 +1,51 @@ +using NUnit.Framework; +using static Vanara.PInvoke.Kernel32; + +namespace Vanara.PInvoke.Tests +{ + [TestFixture] + public partial class WinBaseTests_Resource + { + private const string ResExe = @"C:\Temp\DummyResourceExe.exe"; + private const int ResId1 = 103; + private const int ResId2 = 129; + private const int ResType = (int)ResourceType.RT_DIALOG; + + [Test] + public void UpdateResourceTest() + { + using (var tmp = new TempFile()) + { + Assert.That(CopyFile(ResExe, tmp.FullName, false), ResultIs.Successful); + + // Load the .EXE file that contains the dialog box you want to copy. + using (var hExe = LoadLibraryEx(ResExe, LoadLibraryExFlags.LOAD_LIBRARY_AS_DATAFILE | LoadLibraryExFlags.LOAD_LIBRARY_AS_IMAGE_RESOURCE)) + { + Assert.That(hExe, ResultIs.ValidHandle); + + // Locate the resource in the .EXE file. + var hRes = FindResource(hExe, ResId1, ResType); + Assert.That(hRes, ResultIs.ValidHandle); + + // Load the resource into global memory. + var hResLoad = LoadResource(hExe, hRes); + Assert.That(hResLoad, ResultIs.ValidHandle); + + // Lock the resource into global memory. + var lpResLock = LockResource(hResLoad); + Assert.That(lpResLock, ResultIs.ValidHandle); + + // Open the file to which you want to add the resource resource. + using (var hUpdateRes = BeginUpdateResource(tmp.FullName, false)) + { + Assert.That(hUpdateRes, ResultIs.ValidHandle); + + // Add the resource resource to the update list. + Assert.That(UpdateResource(hUpdateRes, ResType, ResId2, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + lpResLock, SizeofResource(hExe, hRes)), ResultIs.Successful); + } + } + } + } + } +} \ No newline at end of file