在C#中使用UpdateResource?

问题描述 投票:0回答:4

我正在尝试以编程方式更改外部可执行文件的图标。我用 google 搜索并找到了很多关于使用 C++ 的问题的信息。基本上,我需要使用 BeginUpdateResource、UpdateResource 和 EndUpdateResource。问题是 - 我不知道在 C# 中将什么传递给 UpdateResource。

这是我到目前为止的代码:

class IconChanger
{
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr BeginUpdateResource(string pFileName,
        [MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage,
        IntPtr lpData, uint cbData);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);

    public enum ICResult
    {
        Success,
        FailBegin,
        FailUpdate,
        FailEnd
    }

    public ICResult ChangeIcon(string exeFilePath, byte[] iconData)
    {
        // Load executable
        IntPtr handleExe = BeginUpdateResource(exeFilePath, false);

        if (handleExe == null)
            return ICResult.FailBegin;

        // Get language identifier
        CultureInfo currentCulture = CultureInfo.CurrentCulture;
        int pid = ((ushort)currentCulture.LCID) & 0x3ff;
        int sid = ((ushort)currentCulture.LCID) >> 10;
        ushort languageID = (ushort)((((ushort)pid) << 10) | ((ushort)sid));

        // Get pointer to data
        GCHandle iconHandle = GCHandle.Alloc(iconData, GCHandleType.Pinned);

        // Replace the icon
        if (UpdateResource(handleExe, "#3", "#1", languageID, iconHandle.AddrOfPinnedObject(), (uint)iconData.Length))
        {
            if (EndUpdateResource(handleExe, false))
                return ICResult.Success;
            else
                return ICResult.FailEnd;
        }
        else
            return ICResult.FailUpdate;
    }
}

关于 lpType - 在 C++ 中,您传递 RT_ICON (或 RT_GROUP_ICON)。我应该在 C# 中传递什么值? 同样的问题也适用于 lpName 参数。 我不确定语言标识符(我在互联网上找到了这个),因为我无法测试它。 我也不确定我是否提供了适当的图标数据。目前,iconData 包含 .ico 文件中的字节。

有人能指出我正确的方向吗?

非常感谢。

c# .net resources icons
4个回答
7
投票

只是一些提示,这很难做到正确。通过谎报 lpType 参数来传递 RT_ICON。将其从字符串更改为 IntPtr 并传递 (IntPtr)3。

lpData 参数非常棘手。您需要按照资源编译器 (rc.exe) 编译数据的方式传递数据。我不知道它是否会破坏 .ico 文件的原始数据。唯一合理的尝试是使用 FileStream 将 .ico 文件中的数据读取到 byte[] 中,您似乎已经在这样做了。我认为该函数实际上是为了将资源从一个二进制图像复制到另一个二进制图像而设计的。你的方法成功的几率不为零。

你还忽略了另一个潜在的问题,程序图标的资源ID不一定是1。通常不是,100往往是一个流行的选择,但任何事情都可以。需要 EnumResourceNames 才能使其可靠。规则是最小编号的 ID 设置文件的图标。我实际上不确定这是否真的意味着资源编译器将最低的数字放在第一位,而 API 可能不会这样做。

一个非常小的故障模式是UpdateResource只能更新编号的资源项,而不能更新命名的资源项。使用名称代替数字并不罕见,但绝大多数图像使用数字作为图标。

当然,如果没有 UAC 清单,这种方法起作用的可能性为零。您正在破解您通常无权写入的文件。


5
投票

我设法使用 ResourceHacker 并以这篇文章为例,在纯 C# 中实现了此功能。只需使用常规 .ico 作为输入即可。在 ResourceHacker (http://www.angusj.com/resourcehacker/) 中,您将看到图标标识符(在我的例子中为 1)和语言标识符(在我的例子中为 1043):

我使用了这个代码:

internal class IconChanger
{

    #region IconReader
    public class Icons : List<Icon>
    {
        public byte[] ToGroupData(int startindex = 1)
        {
            using (var ms = new MemoryStream())
            using (var writer = new BinaryWriter(ms))
            {
                var i = 0;

                writer.Write((ushort)0);  //reserved, must be 0
                writer.Write((ushort)1);  // type is 1 for icons
                writer.Write((ushort)this.Count);  // number of icons in structure(1)

                foreach (var icon in this)
                {

                    writer.Write(icon.Width);
                    writer.Write(icon.Height);
                    writer.Write(icon.Colors);

                    writer.Write((byte)0); // reserved, must be 0
                    writer.Write(icon.ColorPlanes);

                    writer.Write(icon.BitsPerPixel);

                    writer.Write(icon.Size);

                    writer.Write((ushort)(startindex + i));

                    i++;

                }
                ms.Position = 0;

                return ms.ToArray();
            }
        }
    }

    public class Icon
    {

        public byte Width { get; set; }
        public byte Height { get; set; }
        public byte Colors { get; set; }

        public uint Size { get; set; }

        public uint Offset { get; set; }

        public ushort ColorPlanes { get; set; }

        public ushort BitsPerPixel { get; set; }

        public byte[] Data { get; set; }

    }

    public class IconReader
    {

        public Icons Icons = new Icons();

        public IconReader(Stream input)
        {
            using (BinaryReader reader = new BinaryReader(input))
            {
                reader.ReadUInt16(); // ignore. Should be 0
                var type = reader.ReadUInt16();
                if (type != 1)
                {
                    throw new Exception("Invalid type. The stream is not an icon file");
                }
                var num_of_images = reader.ReadUInt16();

                for (var i = 0; i < num_of_images; i++)
                {
                    var width = reader.ReadByte();
                    var height = reader.ReadByte();
                    var colors = reader.ReadByte();
                    reader.ReadByte(); // ignore. Should be 0

                    var color_planes = reader.ReadUInt16(); // should be 0 or 1

                    var bits_per_pixel = reader.ReadUInt16();

                    var size = reader.ReadUInt32();

                    var offset = reader.ReadUInt32();

                    this.Icons.Add(new Icon()
                    {
                        Colors = colors,
                        Height = height,
                        Width = width,
                        Offset = offset,
                        Size = size,
                        ColorPlanes = color_planes,
                        BitsPerPixel = bits_per_pixel
                    });
                }

                // now get the Data
                foreach (var icon in Icons)
                {
                    if (reader.BaseStream.Position < icon.Offset)
                    {
                        var dummy_bytes_to_read = (int)(icon.Offset - reader.BaseStream.Position);
                        reader.ReadBytes(dummy_bytes_to_read);
                    }

                    var data = reader.ReadBytes((int)icon.Size);

                    icon.Data = data;
                }

            }
        }

    }
    #endregion

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int UpdateResource(IntPtr hUpdate, uint lpType, ushort lpName, ushort wLanguage, byte[] lpData, uint cbData);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr BeginUpdateResource(string pFileName, [MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);

    public enum ICResult
    {
        Success,
        FailBegin,
        FailUpdate,
        FailEnd
    }

    const uint RT_ICON = 3;
    const uint RT_GROUP_ICON = 14;

    public ICResult ChangeIcon(string exeFilePath, string iconFilePath)
    {
        using (FileStream fs = new FileStream(iconFilePath, FileMode.Open, FileAccess.Read))
        {
            var reader = new IconReader(fs);

            var iconChanger = new IconChanger();
            return iconChanger.ChangeIcon(exeFilePath, reader.Icons);
        }
    }

    public ICResult ChangeIcon(string exeFilePath, Icons icons)
    {
        // Load executable
        IntPtr handleExe = BeginUpdateResource(exeFilePath, false);

        if (handleExe == null) return ICResult.FailBegin;

        ushort startindex = 1;
        ushort index = startindex;
        ICResult result = ICResult.Success;

        var ret = 1;

        foreach (var icon in icons)
        {
            // Replace the icon
            // todo :Improve the return value handling of UpdateResource
            ret = UpdateResource(handleExe, RT_ICON, index, 0, icon.Data, icon.Size);

            index++;
        }

        var groupdata = icons.ToGroupData();

        // todo :Improve the return value handling of UpdateResource
        ret = UpdateResource(handleExe, RT_GROUP_ICON, startindex, 0, groupdata, (uint)groupdata.Length);
        if (ret == 1)
        {
            if (EndUpdateResource(handleExe, false))
                result = ICResult.Success;
            else
                result = ICResult.FailEnd;
        }
        else
            result = ICResult.FailUpdate;

        return result;
    }
}

4
投票

这是对我有用的解决方案。我无法在 .NET 中编写它,但设法编写了

C++
DLL,我在我的
C#
应用程序中引用了它。

C++ DLL

我正在构建 DLL 的 C++ 解决方案的内容:

    #include <io.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <windows.h>
    
    extern "C"
    {
        #pragma pack(push, 2)
        typedef struct 
        {
            WORD Reserved1;       // reserved, must be 0
            WORD ResourceType;    // type is 1 for icons
            WORD ImageCount;      // number of icons in structure (1)
            BYTE Width;           // icon width (32)
            BYTE Height;          // icon height (32)
            BYTE Colors;          // colors (0 means more than 8 bits per pixel)
            BYTE  Reserved2;      // reserved, must be 0
            WORD  Planes;         // color planes
            WORD  BitsPerPixel;   // bit depth
            DWORD ImageSize;      // size of structure
            DWORD ResourceID;     // resource ID
        } GROUPICON;
        #pragma pack(pop)
    
        __declspec(dllexport) void __stdcall ChangeIcon(char *executableFile, char *iconFile, INT16 imageCount)
        {
            int len = strlen(executableFile) + 1;
            wchar_t *executableFileEx = new wchar_t[len];
            memset(executableFileEx, 0, len);
            ::MultiByteToWideChar(CP_ACP, NULL, executableFile, -1, executableFileEx, len);
    
            len = strlen("MAINICON") + 1;
            wchar_t *mainIconEx = new wchar_t[len];
            memset(mainIconEx, 0, len);
            ::MultiByteToWideChar(CP_ACP, NULL, "MAINICON", -1, mainIconEx, len);
    
            HANDLE hWhere = BeginUpdateResource(executableFileEx, FALSE);
    
            char *buffer;    // Buffer to store raw icon data
            long buffersize; // Length of buffer
            int hFile;       // File handle
           
            hFile = open(iconFile, O_RDONLY | O_BINARY);
            if (hFile == -1)
                return; // If file doesn't exist, can't be opened etc. 
           
            // Calculate buffer length and load file into buffer
            buffersize = filelength(hFile);
            buffer = (char *)malloc(buffersize);
            read(hFile, buffer, buffersize);
            close(hFile);
    
            // Calculate header size
            int headerSize = 6 + imageCount * 16;
    
            UpdateResource(
                hWhere,  // Handle to executable
                RT_ICON, // Resource type - icon
                MAKEINTRESOURCE(1), // Make the id 1
                MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), // Default language
                buffer + headerSize, // Skip the header bytes
                buffersize - headerSize  // Length of buffer
            );
    
    
            GROUPICON grData;
    
            grData.Reserved1 = 0;     // reserved, must be 0
            grData.ResourceType = 1;  // type is 1 for icons
            grData.ImageCount = 1;    // number of icons in structure (1)
    
            grData.Width = 32;        // icon width (32)
            grData.Height = 32;       // icon height (32)
            grData.Colors = 0;        // colors (256)
            grData.Reserved2 = 0;     // reserved, must be 0
            grData.Planes = 2;        // color planes
            grData.BitsPerPixel = 32; // bit depth
            grData.ImageSize = buffersize - 22; // size of image
            grData.ResourceID = 1;       // resource ID is 1
    
            UpdateResource(
                hWhere,
                RT_GROUP_ICON,
                // RT_GROUP_ICON resources contain information
                // about stored icons
                mainIconEx,
                // MAINICON contains information about the
                // application's displayed icon
                MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
                &grData,
                // Pointer to this structure
                sizeof(GROUPICON)
            );
    
            delete buffer; // free memory
             
            // Perform the update, don't discard changes
            EndUpdateResource(hWhere, FALSE);
        }
    }

C#代码

这是我用来从之前编写的 DLL 中导入

ChangeIcon
函数的 C# 代码:

[DllImport("IconChanger.dll")]
static extern void ChangeIcon(String executableFile, String iconFile, short imageCount);

/// <summary>
/// Changes the executable's icon
/// </summary>
/// <param name="exeFilePath">Path to executable file</param>
/// <param name="iconFilePath">Path to icon file</param>
static public void ChangeIcon(string exeFilePath, string iconFilePath)
{
    short imageCount = 0;

    using (StreamReader sReader = new StreamReader(iconFilePath))
    {
        using (BinaryReader bReader = new BinaryReader(sReader.BaseStream))
        {
            // Retrieve icon count inside icon file
            bReader.ReadInt16();
            bReader.ReadInt16();
            imageCount = bReader.ReadInt16();
        }
    }

    // Change the executable's icon
    ChangeIcon(exeFilePath, iconFilePath, imageCount);
}

希望至少有人会发现这很有用。


0
投票

UpdateResource 的 C# 声明:

    /// <summary>
    /// 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.
    /// </summary>
    /// <param name="hUpdate">A module handle returned by the BeginUpdateResource function, referencing the file to be updated. </param>
    /// <param name="lpType">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. </param>
    /// <param name="lpName">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.</param>
    /// <param name="wLanguage">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. </param>
    /// <param name="lpData">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. Prior to Windows 7: If lpData is NULL and cbData is nonzero, the specified resource is NOT deleted and an exception is thrown.</param>
    /// <param name="cbData">The size, in bytes, of the resource data at lpData. </param>
    /// <returns>Returns TRUE if successful or FALSE otherwise. To get extended error information, call GetLastError.</returns>
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
    public static extern Int32 UpdateResourceW(void* hUpdate, char* lpType, char* lpName, UInt16 wLanguage, [CanBeNull] void* lpData, UInt32 cbData);

对于字符串资源类型或名称,您只需传递字符串即可。 对于像

RT_ICON
这样的系统预定义类型和像
IDI_APPLICATION
这样的 int ID,您可以将该整数值重新解释转换为指针,例如
(char*)3
代表
RT_ICON

© www.soinside.com 2019 - 2024. All rights reserved.