请注意,清单资源名称中包含原始文件名和项目相关子目录。
其他答案缺少的关键信息(使该答案远不如有用)是项目名称和原始文件所在的任何文件夹/目录都表示为资源名称中的组件。整个清单资源名称使用“.”字符分隔成这些组件。
具备这些知识后,您应该能够正确处理您的资源。特别是,您需要删除名称的第一个组件(即项目名称),然后将名称除最后一个组件之外的所有内容视为目录名称。
这有点棘手,因为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)
{
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……实际上不需要从头开始编写安装程序来实现安装程序)。