我有一个使用C++动态链接库进行数据输入输出的Windows C#程序。我的目标是将该应用程序部署为单个EXE文件。
创建这样的可执行文件的步骤是什么?
托管代码和非托管代码的单一程序集部署 2007年2月4日星期日
.NET开发人员喜欢XCOPY部署。而且他们喜欢单一程序集组件。至少我总是感到有点不安,如果我必须使用某个组件并需要记住还要与该组件的主程序集一起包含的文件清单。因此,最近当我不得不开发一个托管代码组件并不得不用来自C DLL的一些非托管代码进行增强时(感谢Marcus Heege帮助我完成这项工作!),我考虑如何使两个DLL更容易部署。如果这只是两个程序集,我可以使用ILmerge将它们打包成一个文件。但对于托管以及非托管DLL的混合代码组件,这种方法不起作用。
所以,以下是我的解决方案:
我将我想要部署的任何DLL作为嵌入式资源与我的组件的主程序集一起包含进来。 然后我设置了一个类构造函数来提取这些DLL,如下所示。类构造函数在每个AppDomain中仅被调用一次,因此我认为它的开销可以忽略不计。
namespace MyLib
{
public class MyClass
{
static MyClass()
{
ResourceExtractor.ExtractResourceToFile("MyLib.ManagedService.dll", "managedservice.dll");
ResourceExtractor.ExtractResourceToFile("MyLib.UnmanagedService.dll", "unmanagedservice.dll");
}
...
在这个例子中,我把两个DLL作为资源包含进来,一个是非托管代码的DLL,另一个是托管代码的DLL(只是为了演示目的),以展示这种技术如何适用于两种类型的代码。
将这些DLL提取到它们自己的文件中的代码很简单:
public static class ResourceExtractor
{
public static void ExtractResourceToFile(string resourceName, string filename)
{
if (!System.IO.File.Exists(filename))
using (System.IO.Stream s = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.Create))
{
byte[] b = new byte[s.Length];
s.Read(b, 0, b.Length);
fs.Write(b, 0, b.Length);
}
}
}
操作托管代码程序集与通常相同,但有一些不同之处。您在组件的主项目(此处为MyLib)中引用它(这里是ManagedService.dll),但将复制本地属性设置为false。此外,您将程序集作为现有项链接,并将构建操作设置为嵌入式资源。
对于非托管代码(此处为UnmanagedService.dll),您只需将DLL作为现有项链接,并将构建操作设置为嵌入式资源。要访问其函数,请像往常一样使用DllImport属性,例如:
[DllImport("unmanagedservice.dll")] public extern static int Add(int a, int b);
就是这样!只要您使用静态构造函数创建类的第一个实例,嵌入的DLL文件将被提取为它们自己的文件,并准备好像您将它们部署为单独的文件一样使用。 只要您对执行目录具有写权限,这应该对您有效。 至少对于原型代码,我认为这种单个程序集部署方式非常方便。
享受吧!
使用Fody.Costura nuget包
就这样!
来源: http://www.manuelmeyer.net/2016/01/net-power-tip-10-merging-assemblies/
在 Visual Studio 中右键单击你的项目,选择“项目属性”->“资源”->“添加资源” ->“添加现有文件…”
然后将以下代码添加到 App.xaml.cs 或其等效文件中。
public App()
{
AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
dllName = dllName.Replace(".", "_");
if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = (byte[])rm.GetObject(dllName);
return System.Reflection.Assembly.Load(bytes);
}
ILMerge是一个实用程序,可用于将多个.NET程序集合并为单个程序集。它可以在Microsoft .NET Framework Developer Center的工具和实用程序页面免费使用。
如果您使用/clr
标志(全部或部分为C++/CLI)构建C++ DLL,则应该可以正常工作:
ilmerge /out:Composite.exe MyMainApp.exe Utility.dll
/clr:pure
或更高版本。 - Ben VoigtSmart Assembly可以做到这些以及更多。如果您的dll具有非托管代码,它将不允许您将dll合并为单个程序集,而是可以将所需的依赖项作为资源嵌入到主exe中。它的反面是,它不是免费的。
您可以通过将dll嵌入到资源中,然后依赖于AppDomain的Assembly ResolveHandler
来手动执行此操作。当涉及混合模式dll时,我发现许多ResolveHandler
方法的变体和风格对我无效(所有这些方法都会将dll字节读取到内存中并从中读取)。它们都适用于托管dll。以下是对我有效的方法:
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string assemblyName = new AssemblyName(args.Name).Name;
if (assemblyName.EndsWith(".resources"))
return null;
string dllName = assemblyName + ".dll";
string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName);
using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
{
byte[] data = new byte[stream.Length];
s.Read(data, 0, data.Length);
//or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length);
File.WriteAllBytes(dllFullPath, data);
}
return Assembly.LoadFrom(dllFullPath);
};
}
GetMyApplicationSpecificPath()
不是任何临时目录,因为其他程序或您自己可能会尝试擦除临时文件(虽然在您的程序访问dll时它不会被删除,但至少是个麻烦。AppData是一个好的位置)。还要注意,您必须每次都写入字节,不能仅仅因为dll已经驻留在那里就从位置加载。如果您想将一个已经存在的应用程序(包括其dll和其他资源,无论它是用什么语言编写的)打包成一个单独的.exe文件,您可以使用SerGreen's Appacker来实现。但是,它会被检测为“运行来自黑客的恶意代码”,因为它会自行解压:
一些杀毒软件可能会将Appacker及其创建的软件包检测为恶意软件。这是因为我使用了一种hacky的方式来打包文件:打包的应用程序读取自己的可执行文件并从中提取其他文件,这让杀毒软件感到非常可疑。这是误报,但它仍然妨碍了使用此应用程序。
-SerGreen在GitHub上说道
要使用它,您只需打开它,点击任何病毒警告(并告诉Windows Defender不要删除它!),然后选择应该打包的目录和解包后要运行的可执行文件。
您还可以选择更改应用程序的解包行为(窗口化/无窗口化解包器、解包目标目录、是否重新打包或忽略对解包后文件的更改等)。
Thinstall是一种解决方案。对于本地Windows应用程序,我建议将DLL作为二进制资源对象嵌入,然后在需要时在运行时提取它。