Использование UpdateResource в C #? - PullRequest
6 голосов
/ 08 ноября 2010

Я пытаюсь программно изменить значок внешнего исполняемого файла. Я погуглил и нашел много информации об этой проблеме, используя C ++. В основном мне нужно использовать BeginUpdateResource, UpdateResource и EndUpdateResource. Проблема в том, что я не знаю, что передать UpdateResource в C #.

Вот код, который у меня есть:

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.

Кто-нибудь может указать мне правильное направление?

Большое спасибо.

Ответы [ 4 ]

7 голосов
/ 09 ноября 2010

Просто несколько указателей, это довольно сложно понять правильно. Передайте RT_ICON, обманывая аргумент lpType. Измените его со строки на IntPtr и передайте (IntPtr) 3.

Аргумент lpData довольно хитрый. Вам необходимо передать данные так, как они компилируются компилятором ресурсов (rc.exe). Я понятия не имею, искажает ли он необработанные данные .ico-файла. Единственная разумная вещь - попытаться прочитать данные из .ico-файла с FileStream в байт [], вы, похоже, уже делаете это. Я думаю, что функция была действительно предназначена для копирования ресурса из одного двоичного изображения в другое. Шансы вашего подхода к работе не равны нулю.

Вы также игнорируете еще одну потенциальную проблему, идентификатор ресурса значка программы не обязательно равен 1. Часто это не так, 100 обычно является популярным выбором, но что-то идет. EnumResourceNames потребуется, чтобы сделать его надежным. Правило состоит в том, что идентификатор с наименьшим номером устанавливает значок для файла. На самом деле я не уверен, действительно ли это означает, что компилятор ресурсов ставит наименьшее число первым, чего API, вероятно, не делает.

Очень маленький режим отказа состоит в том, что UpdateResource может обновлять только пронумерованные элементы ресурса, а не именованные. Использование имен вместо чисел не редкость, но в подавляющем большинстве изображений для значков используются цифры.

И, конечно, шансы, что это будет работать без манифеста UAC, равны нулю. Вы взламываете файлы, к которым у вас обычно нет прав записи.

3 голосов
/ 22 апреля 2016

Мне удалось заставить это работать в чистом C #, используя ResourceHacker и эту публикацию в качестве примера. Просто используйте обычный .ico в качестве входных данных. В ResourceHacker (http://www.angusj.com/resourcehacker/) вы увидите идентификатор значка (в моем случае 1) и идентификатор языка (в моем случае 1043):

enter image description here

Я использовал этот код:

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;
    }
}
2 голосов
/ 24 марта 2014

Это решение, которое сработало для меня. Я не смог написать это в .NET, но мне удалось написать C ++ DLL, на которую я ссылаюсь в моем приложении C #.

C ++ DLL

Содержимое решения C ++, из которого я собираю DLL:

#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 #

Это код C #, который я использую для импорта функции ChangeIcon из ранее написанной DLL:

[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 голосов
/ 27 ноября 2014

C # объявление для UpdateResource:

    /// <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 и int, таких как IDI_APPLICATION, вы передаете это целочисленное значение, переинтерпретируя приведение его к указателю, например (char*)3 для RT_ICON.

...