如何将一个C++ Windows动态链接库合并到一个C#应用程序可执行文件中?

41

我有一个使用C++动态链接库进行数据输入输出的Windows C#程序。我的目标是将该应用程序部署为单个EXE文件。

创建这样的可执行文件的步骤是什么?


你应该使用BoxedAppPacker或BoxedApp SDK。它们肯定会有所帮助。 - MastAvalons
1
重复的内容:https://dev59.com/X3RB5IYBdhLWcg3wSVm1#11038376 - Mark Lakata
9个回答

21

托管代码和非托管代码的单一程序集部署 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文件将被提取为它们自己的文件,并准备好像您将它们部署为单独的文件一样使用。 只要您对执行目录具有写权限,这应该对您有效。 至少对于原型代码,我认为这种单个程序集部署方式非常方便。

享受吧!

http://weblogs.asp.net/ralfw/archive/2007/02/04/single-assembly-deployment-of-managed-and-unmanaged-code.aspx


3
链接已失效。这就是为什么你应该始终引用链接页面中最相关的部分而不是发布仅包含链接的答案。点击此处了解更多信息。 - BackSlash

3
尝试使用BoxedApp,它允许从内存中加载所有DLL。此外,似乎您甚至可以嵌入.NET运行时。非常适合创建真正独立的应用程序...

3

2

在 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);
}

这是我的原始博客文章:http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/

2
请勿多次发布完全相同的帖子,特别是如果您要将其发布到非常旧的问题中。如果先前的帖子适用于完全相同的答案,请标记该问题为重复。如果您继续以完全相同的文本发布该博客链接,则可能会引起一些垃圾邮件标志。 - Mat

2
你尝试过ILMerge吗?http://research.microsoft.com/~mbarnett/ILMerge.aspx

ILMerge是一个实用程序,可用于将多个.NET程序集合并为单个程序集。它可以在Microsoft .NET Framework Developer Center的工具和实用程序页面免费使用。

如果您使用/clr标志(全部或部分为C++/CLI)构建C++ DLL,则应该可以正常工作:

ilmerge /out:Composite.exe MyMainApp.exe Utility.dll

它不能和普通的(本地的)Windows DLL一起使用。

2
很确定需要在DLL上使用/clr:pure或更高版本。 - Ben Voigt

0

Smart 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已经驻留在那里就从位置加载。
如果程序集完全未托管,您可以参考linkthis来了解如何加载此类dll。

0

如果您想将一个已经存在的应用程序(包括其dll和其他资源,无论它是用什么语言编写的)打包成一个单独的.exe文件,您可以使用SerGreen's Appacker来实现。但是,它会被检测为“运行来自黑客的恶意代码”,因为它会自行解压:

一些杀毒软件可能会将Appacker及其创建的软件包检测为恶意软件。这是因为我使用了一种hacky的方式来打包文件:打包的应用程序读取自己的可执行文件并从中提取其他文件,这让杀毒软件感到非常可疑。这是误报,但它仍然妨碍了使用此应用程序。
-SerGreen在GitHub上说道

要使用它,您只需打开它,点击任何病毒警告(并告诉Windows Defender不要删除它!),然后选择应该打包的目录和解包后要运行的可执行文件。
您还可以选择更改应用程序的解包行为(窗口化/无窗口化解包器、解包目标目录、是否重新打包或忽略对解包后文件的更改等)。


虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。- 来自审查 - Hoppeduppeanut
链接的标题已经说明了一切 - 由SerGreen开发的程序“Appacker”。但是如果链接失效,我很快就会编辑答案,包括README的部分内容。 - Lampe2020
@Hoppeduppeanut 现在我已经扩展了答案,包括 README 中最重要的基本内容(不是命令行如何使用,这可以在原始 README 中找到),以确保当链接失效时,此答案不会变得无效。 - Lampe2020

0

Thinstall是一种解决方案。对于本地Windows应用程序,我建议将DLL作为二进制资源对象嵌入,然后在需要时在运行时提取它。


3
Thinstall的价格非常昂贵,并且在某些Windows安装和第三方shell扩展中会遇到问题。我们在非 .Net 代码方面使用它时效果有限,但是我们发现对于 .Net 来说,ClickOnce和MSI安装是最好的选择。 - user7116
2
如果您主要寻找安装程序,我推荐使用Inno Setup。我们几年前放弃了InstallShield并且再也没有回头看。 http://www.innosetup.com/isinfo.php - titanae

-2

来自Xenocode的PostBuild可以将托管和非托管代码打包成一个单独的exe。


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