【问题标题】:Using UpdateResource in C#?在 C# 中使用 UpdateResource?
【发布时间】:2010-11-08 20:31:30
【问题描述】:

我正在尝试以编程方式更改外部可执行文件的图标。我用谷歌搜索并找到了很多关于使用 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 参数也有同样的问题。 我不确定语言标识符(我在 Internet 上找到的),因为我无法对其进行测试。 我也不确定我是否提供了适当的图标数据。目前,iconData 包含来自 .ico 文件的字节。

谁能指出我正确的方向?

非常感谢。

【问题讨论】:

    标签: c# .net resources icons


    【解决方案1】:

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

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

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

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

    当然,这在没有 UAC 清单的情况下也能正常工作的几率为零。您正在破解您通常没有写入权限的文件。

    【讨论】:

    • 感谢您的详尽回答,汉斯。我将使用单个可执行文件,因此将知道图标包含什么标识符。我在 VS 中打开了可执行文件,它显示了 Icon 节点,里面是现有的图标。它有 32512 标识符。我试图对参数“撒谎”,但它不起作用,没有任何更新。如果我使用“Icon”或“RT_ICON”作为参数,图标将作为二进制数据添加,而不是在 Icon 节点中。而是添加了一个新节点,其中包含二进制数据。
    • 我成功了!如果不是因为您的指点和撒谎的解决方法,我将无法做到。我稍后会更新我的问题。再次非常感谢你,汉斯!
    • 嗯,结果比我想象的要好。恭喜。您的 sn-p 将成为其他人的参考。
    • 嘿 Witchunter,请问那个 sn-p 怎么样?
    • @NikolaMalešević 我赞同 Spooky 的评论。用于更改应用程序图标(而不是表单图标)的工作 sn-p 会很棒。
    【解决方案2】:

    我设法使用 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;
        }
    }
    

    【讨论】:

    【解决方案3】:

    这是对我有用的解决方案。我无法在 .NET 中编写它,但设法编写了我在 C# 应用程序中引用的 C++ DLL。

    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
            WORD 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);
    }
    

    希望至少有人会觉得这很有用。

    【讨论】:

    • 非常感谢 Nikola 的分享。我遇到了一些麻烦。我在C:\Users\noi\Desktop\fox.ico 有一个 ico 文件,这个 ico 包含 4 个图像,16、32、48 和 256。当我使用您的技术应用它时,它没有任何影响,请您指教?这是我的代码(它是 js-ctypes)以防万一它可以帮助你给我建议 - github.com/Noitidart/Profilist/blob/master/modules/workers/… - 代码通过没有错误,但是图标没有更新:(
    • 我没有 JS 方面的经验,所以很遗憾,我无法在这方面为您提供帮助。正如我所说,没有 C++ 的帮助,我也无法做到。
    • 感谢@Nikola 的回复,不过是一个问题,与代码语言无关。在您的 C++ 方法ChangeIcon 的参数中,是否可以传递一个 ICO 文件,其中包含该图像的多种尺寸?如果里面有多个尺寸,这是否意味着iconCount 应该大于 1?
    • 现在看我的代码,我想说grData.ImageCount = 1应该改成grData.ImageCount = imageCount。我可能从来没有使用过多种尺寸的图标,所以从来没有遇到过这个问题。所以我想说,是的,这是可能的,但我无法再访问该项目,所以不能 100% 肯定它。
    • 感谢您的意见! :)
    【解决方案4】:

    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 for RT_ICON

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-15
      • 2019-05-02
      • 2020-10-14
      相关资源
      最近更新 更多