You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过IO方式读取DLL中带索引的字符串资源?

问题描述

修改Windows 10右键菜单时发现,Visual Studio的菜单项与其他程序不同:

  • Git的右键菜单项HKEY_CLASSES_ROOT\Directory\Background\shell\git_shell显示名称是静态字符串(如"Git Ba&sh Here")
  • Visual Studio的AnyCode项HKEY_CLASSES_ROOT\Directory\Background\shell\AnyCode显示名称采用@<DLL路径>,<索引编号>格式,示例为@C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\2052\VSLauncherUI.dll,-1002

尝试用C#程序通过IO方式读取DLL中的对应资源时,报错E: Unable to read beyond the end of the stream,无法实现Windows读取此类资源的逻辑,求正确的实现方法。

附尝试的代码:

using System;
using System.IO;

namespace ResourceLister
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string dllPath = @"C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\2052\VSLauncherUI.dll";
                if (File.Exists(dllPath))
                {
                    string[] resourceNames = GetResourceNamesFromDll(dllPath);
                    foreach (var resourceName in resourceNames)
                    {
                        Console.WriteLine("ResourceName: {0}", resourceName);
                    }
                }
                else
                {
                    Console.WriteLine("Not exist");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("E: {0}", e.Message);
            }
        }

        static string[] GetResourceNamesFromDll(string dllPath)
        {
            using (var dllStream = new FileStream(dllPath, FileMode.Open, FileAccess.Read))
            {
                using (var dllReader = new BinaryReader(dllStream))
                {
                    dllStream.Position = 0x3C;
                    int peHeaderOffset = dllReader.ReadInt32();
                    dllStream.Position = peHeaderOffset + 0x78;
                    int resourceTableOffset = peHeaderOffset + 0x88;
                    dllStream.Position = resourceTableOffset;
                    int numberOfResources = dllReader.ReadInt16();

                    string[] resourceNames = new string[numberOfResources];
                    for (int i = 0; i < numberOfResources; i++)
                    {
                        dllStream.Position = resourceTableOffset + 2 + (i * 8) + 4;
                        int nameOffset = dllReader.ReadInt32();
                        dllStream.Position = resourceTableOffset + nameOffset;
                        int nameLength = dllReader.ReadByte();
                        byte[] nameBytes = dllReader.ReadBytes(nameLength * 2); 
                        resourceNames[i] = System.Text.Encoding.Unicode.GetString(nameBytes);
                    }

                    return resourceNames;
                }
            }
        }
    }
}
解决方案

不要手动解析PE文件结构,这种方式容易因文件格式细节(如对齐规则、资源表嵌套结构)出错,维护成本极高。Windows中@<DLL路径>,<索引编号>格式的字符串是通过LoadString API加载的字符串资源,推荐两种可靠的实现方式:

方法1:使用P/Invoke调用LoadString API

这是系统解析这类注册表项的原生方式,直接调用Windows API读取指定索引的字符串资源:

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.ComponentModel;

class ResourceReader
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int LoadString(IntPtr hInstance, int uID, StringBuilder lpBuffer, int nBufferMax);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeLibrary(IntPtr hModule);

    private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;

    public static string GetStringFromDll(string dllPath, int resourceId)
    {
        IntPtr hModule = LoadLibraryEx(dllPath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
        if (hModule == IntPtr.Zero)
        {
            throw new Win32Exception();
        }

        try
        {
            // 先获取字符串长度,传入0作为缓冲区大小
            int length = LoadString(hModule, resourceId, null, 0);
            if (length == 0)
            {
                throw new Win32Exception();
            }

            StringBuilder sb = new StringBuilder(length);
            LoadString(hModule, resourceId, sb, length + 1);
            return sb.ToString();
        }
        finally
        {
            FreeLibrary(hModule);
        }
    }

    static void Main()
    {
        try
        {
            string dllPath = @"C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\2052\VSLauncherUI.dll";
            // 注册表中的-1002对应这里的正整数ID 1002
            string result = GetStringFromDll(dllPath, 1002); 
            Console.WriteLine("读取到的字符串:" + result);
        }
        catch (Exception ex)
        {
            Console.WriteLine("错误:" + ex.Message);
        }
    }
}

方法2:使用.NET的ResourceManager(仅适合托管资源)

如果目标DLL包含.NET托管资源,可以用ResourceManager读取,但系统原生DLL的字符串资源通常是非托管的,因此方法1更通用:

using System;
using System.Resources;
using System.Reflection;

class ManagedResourceReader
{
    static void Main()
    {
        try
        {
            Assembly dllAssembly = Assembly.LoadFile(@"C:\Path\To\Your\Managed.dll");
            // 替换为实际的资源命名空间和资源文件名
            ResourceManager rm = new ResourceManager("ResourceNamespace.ResourceFileName", dllAssembly);
            string value = rm.GetString("ResourceName");
            Console.WriteLine(value);
        }
        catch (Exception ex)
        {
            Console.WriteLine("错误:" + ex.Message);
        }
    }
}

关键说明

  • 注册表中的负号索引(如-1002)对应LoadString API中的正整数ID,直接传入正数即可。
  • 系统API已封装所有PE文件解析细节,是最稳定可靠的实现方式。

内容的提问来源于stack exchange,提问作者ycMia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:58:12