如何获取exe文件中嵌入资源的文件名?

4
在Visual Studio中,我有一个构建操作设置为“嵌入式资源”的文件。我想在运行时提取此文件,但我无法获得文件名:
// returns null
Assembly.GetExecutingAssembly().GetManifestResourceInfo(resourceName).FileName

3
一旦资源被嵌入,就没有文件名了。因为你的EXE可能在任何地方运行,包括与编译它的机器不同的机器上,那么文件名对你有什么作用呢?你真正想做什么?你试图解决什么具体问题? - Peter Duniho
我正在将我的应用程序依赖项打包到我的exe文件中。我避免使用安装程序,因为它们可能需要管理员权限或某些机器可能会阻止它。 - happymeal
抱歉,那并没有回答我所提出的问题。你为什么要从资源中获取原始文件名?如果你得到了这个文件名,你会用它做什么? - Peter Duniho
一旦我获得了文件名,我将把资源流复制到它们相应的输出文件名中,并根据Assembly.GetEntryAssembly().Location与exe组合在一起。此后,我的应用程序exe将能够使用dll和配置文件。 - happymeal
1
@happymeal 哦,你想避免由于阻止/权限问题而安装程序...所以你写了一个安装程序。这很有道理。 - Luaan
2个回答

6
请注意,清单资源名称中包含原始文件名和项目相关子目录。
其他答案缺少的关键信息(使该答案远不如有用)是项目名称和原始文件所在的任何文件夹/目录都表示为资源名称中的组件。整个清单资源名称使用“.”字符分隔成这些组件。
具备这些知识后,您应该能够正确处理您的资源。特别是,您需要删除名称的第一个组件(即项目名称),然后将名称除最后一个组件之外的所有内容视为目录名称。
这有点棘手,因为a)您的文件名可能具有扩展名(因此已经具有“。”字符),并且b)编译器将以基本方式转义任何在文件夹/子目录名称中找到的“.”字符。
以下是显示基本方法的代码示例:
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(Environment.NewLine,
            Assembly.GetEntryAssembly().GetManifestResourceNames()));
        Assembly assembly = Assembly.GetEntryAssembly();
        string exeDirectory = Path.GetDirectoryName(assembly.Location);

        foreach (string resourceName in assembly.GetManifestResourceNames())
        {
            string fileName = _GetFileNameFromResourceName(resourceName),
                directory = Path.GetDirectoryName(fileName);

            if (!string.IsNullOrEmpty(directory))
            {
                Directory.CreateDirectory(directory);
            }

            using (Stream outputStream =
                File.OpenWrite(Path.Combine(exeDirectory, fileName)))
            {
                assembly.GetManifestResourceStream(resourceName).CopyTo(outputStream);
            }
        }
    }

    private static string _GetFileNameFromResourceName(string resourceName)
    {
        // NOTE: this code assumes that all of the file names have exactly one
        // extension separator (i.e. "dot"/"period" character). I.e. all file names
        // do have an extension, and no file name has more than one extension.
        // Directory names may have periods in them, as the compiler will escape these
        // by putting an underscore character after them (a single character
        // after any contiguous sequence of dots). IMPORTANT: the escaping
        // is not very sophisticated; do not create folder names with leading
        // underscores, otherwise the dot separating that folder from the previous
        // one will appear to be just an escaped dot!

        StringBuilder sb = new StringBuilder();
        bool escapeDot = false, haveExtension = false;

        for (int i = resourceName.Length - 1; i >= 0 ; i--)
        {
            if (resourceName[i] == '_')
            {
                escapeDot = true;
                continue;
            }

            if (resourceName[i] == '.')
            {
                if (!escapeDot)
                {
                    if (haveExtension)
                    {
                        sb.Append('\\');
                        continue;
                    }
                    haveExtension = true;
                }
            }
            else
            {
                escapeDot = false;
            }

            sb.Append(resourceName[i]);
        }

        string fileName = Path.GetDirectoryName(sb.ToString());

        fileName = new string(fileName.Reverse().ToArray());

        return fileName;
    }
}

使用上述代码创建一个新项目,然后将嵌入式资源添加到项目中,可以按照您的喜好在项目文件夹中或不在其中,以查看结果。

注意: 您可能会发现过滤资源名称列表很有用。例如,如果您的项目是使用设计器添加的传统资源(即显示项目属性,然后单击“资源”选项卡并在那里添加资源,而不是显式地添加为嵌入式资源),则会得到名为MyProjectName.Resources.resources的资源。否则,您将获得每个清单资源。

注意: 请记住,要写入可执行文件所在目录,您可能需要比当前进程具有更高级别的特权。例如,如果用户将您的EXE作为管理员复制到“Program Files”目录中的某个目录中,然后尝试以受限用户身份运行程序。正如评论者Luaan在您的问题下面提到的那样,似乎最好实现某种安装程序,而不是仅仅使用此方法。 (Visual Studio提供了对基本Install Shield产品的免费访问权限,或者您可以使用例如WiX……实际上不需要从头开始编写安装程序来实现安装程序)。


2
支持一个未经检查的协议,即所有资源文件名都不应超过一个点,比仅保留以资源名称为键和文件名为值的字典更糟糕。 - astef
4
也许您错过了答案中的陈述:“这里有一个代码示例,展示了基本方法”。提供说明性代码而不必为他们做所有工作是非常正常的。那并没有使回答“没有用”。我猜您想要一些可以直接复制/粘贴到自己的程序中的东西,并且不会满足于其他任何东西? - Peter Duniho
问题在于无法从资源名称字符串中获取真实的文件名。你编写的代码只适用于一些特殊的文件名,对于大多数互联网搜索者来说,你的答案是有害的。 - astef
@astef: “对于大多数互联网搜索者来说,你的答案是有害的”-- 真的吗?你真的相信对于大多数寻求解决方案的人来说,他们会有带有多个扩展名的文件名,并且同时无法阅读有关此事的评论警告并调整其方法以适应吗?对于我来说,这似乎是一个消失得很小的情况下的惊人大量人口。 - Peter Duniho
我相信你的回答会有害,特别是当它被标记为答案时。每个人都可以阅读你的评论,但对于期望得到通用解决方案的搜索者来说,仍然会耗费很多时间。你不想滚动查看仅适用于某些时区或某些特殊用户等的解决方案。 - astef
4
只有在不解释其限制或问题以及如何安全避免它们的情况下,它才会有害。一支枪是有害的,但正确的知识可以显著降低出现问题的几率。 - Erik Philips

1
请注意,GetManifestResourceInfo(resourceName).FileName 只获取包含清单资源的文件的名称,如果它与清单文件不同。关于清单,请参见此处: http://www.dotnetspark.com/kb/682-what-is-manifest.aspx 您可以使用 Assembly.GetManifestResourceNames 方法获取程序集中所有资源的名称列表。
例如:
// str 包含所有嵌入式资源名称
 string[] str = assembly.GetManifestResourceNames();

该程序可以提供嵌入式资源列表,但不包含文件名。 - happymeal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接