1. 程式人生 > >WPF程式將DLL嵌入到EXE的兩種方法

WPF程式將DLL嵌入到EXE的兩種方法

轉自:https://www.cnblogs.com/luoshupeng/p/3951597.html

 

這一篇可以看作是《Visual Studio 版本轉換工具WPF版開源了》的續,關於《Visual Studio 版本轉換工具WPF版開源了》可以參看地下地址(兩篇是一樣的):

  1. 開源中國 http://my.oschina.net/chinesedragon/blog/308336
  2. CNBLOGS http://www.cnblogs.com/luoshupeng/p/3946635.html

** 部落格園的Markdown編輯器真的不行,還是麻煩大家穩步到

http://my.oschina.net/chinesedragon/blog/309223觀看吧 **

引言

前幾一寫了一個小工具————《Visual Studio版本轉換工具》,由於使用了WPF做為介面,因此這個小程式執行必須附帶兩個DLL:Microsoft.Expression.Interactions.dll和System.Windows.Interactivity.dll,同時由於自己也寫了一個庫,一個小程式需要附帶3個DLL,這種體驗真的很不爽,於是就著手把DLL嵌入到EXE中去。

挫折

對於C#程式,要把DLL嵌入到EXE中去,最權威和最常見的方法就是使用ILMerge這個工具,這是個命令列工具,有很多引數,可以將DLL很完美的嵌入到EXE中去,如果嫌命令列麻煩,也有人開源開發了圖形介面ILMergeGUI,這兩個工具的下載和幫助地址如下:

  1. ILMerge http://www.microsoft.com/en-us/download/details.aspx?id=17630
  2. ILMerge-GUI http://ilmergegui.codeplex.com/
    於是下載了這兩個工具,卻出現將DLL嵌入到EXE中錯誤的情況,在網上查了下原因,竟然是ILMerge不支援WPF程式,我勒了個去,微軟,你讓我說你什麼好呢?
    ILMerge可以將Winform程式的DLL完美地嵌入到EXE中去(這一點我親自測試了下,很不,點贊),但對WPF卻不支援,原因是WPF的DLL中含有資源無法解決,微軟,你好意思說這個工具是你開發的嗎?

Winform程式將DLL嵌入到EXE中(一)——使用命令列 

下載ILMerge或者同時下載ILMerge-GUI,使用圖形介面和使用命令列是同一個道理,只是圖形介面簡單些,所以這裡以命令列說明。
我是下載ILMerge安裝後,把ILMerge.exe複製到C:\Windows目錄下去了,這樣可以直接在命令列中使用而不用去設定環境變數,不管怎樣,只要能夠在命令列下使用這個工具就行。
ILMerge有很多引數,其中有幾個比較重要:

  1. /target:目標,有library和Winexe兩種選擇,當將多個DLL整合成一個DLL時可以使用library,當要整合為EXE時,應該使用Winexe.
  2. /out:輸出,最終生成檔案的路徑和名稱.
  3. /log:輸入,如果輸入是EXE時可以不使用此引數直接寫,而如果輸入時DLL時,最好使用此引數
    還有其它一些引數,使用時可以百度或者google一下,這是我測試的一張圖片:

Winform程式將DLL嵌入到EXE中(二)——使用ILMerge.MSBuild.Tasks

ILMerge也使用Nuget釋出了工具,使用Nuget的好處想必大家都知道,所以推薦使用這種方法
第一步, 使用Nuget圖形或Nuget命令下載ILMerge.MSBuild.Tasks

PM> Install-Package ILMerge.MSBuild.Tasks   

第二步, 把VS專案檔案記事本或者其它文字編輯工具開啟,我使用的是Sublime Text 3,並按照如下格式根據實際情況修改:

<!-- Code to merge the assemblies into one:setup.exe -->  
<UsingTask TaskName="ILMerge.MSBuild.Tasks.ILMerge" AssemblyFile="$(SolutionDir)\packages\ILMerge.MSBuild.Tasks.1.0.0.3\tools\ILMerge.MSBuild.Tasks.dll" />  
<Target Name="AfterBuild">  
    <ItemGroup>
      <MergeAsm Include="$(OutputPath)$(TargetFileName)" />
      <MergeAsm Include="$(OutputPath)LIB1_To_MERGE.dll" /> <!-- 這兒改成需要做嵌入的dll名 -->
      <MergeAsm Include="$(OutputPath)LIB2_To_MERGE.dll" />
    </ItemGroup>
    <PropertyGroup>
      <MergedAssembly>$(ProjectDir)$(OutDir)MERGED_ASSEMBLY_NAME.exe</MergedAssembly><!-- 這兒改成需要做輸出的exe名 -->
    </PropertyGroup>
    <Message Text="ILMerge @(MergeAsm) -&gt; $(MergedAssembly)" Importance="high" />
    <ILMerge InputAssemblies="@(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" />
</Target>

這樣編譯後就可以了。

WPF程式將DLL嵌入到EXE中(一)——將DLL自動轉換為嵌入資源

第一步,修改專案檔案,將DLL自動轉換為嵌入資源。
把VS專案檔案記事本或者其它文字編輯工具開啟,我使用的是Sublime Text 3,並將下面內容新增到檔案末尾:

<Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
</Target>

第二步,修改App.xaml檔案,在程式啟動時載入資源

public partial class App : Application
{
    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        Assembly executingAssembly = Assembly.GetExecutingAssembly();
        var executingAssemblyName = executingAssembly.GetName();
        var resName = executingAssemblyName.Name + ".resources";

        AssemblyName assemblyName = new AssemblyName(args.Name); string path = "";
        if (resName == assemblyName.Name)
        {
            path = executingAssemblyName.Name + ".g.resources"; ;
        }
        else
        {
            path = assemblyName.Name + ".dll";
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }
        }

        using (Stream stream = executingAssembly.GetManifestResourceStream(path))
        {
            if (stream == null)
                return null;

            byte[] assemblyRawBytes = new byte[stream.Length];
            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
            return Assembly.Load(assemblyRawBytes);
        }
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    }
}

第三步,dll嵌入exe後,目錄中的dll就沒用了,配置Post buid 指令碼自動刪除dll:

cd $(TargetDir)
del *.dll

有些情況下,以上方法也不行,那麼可以嘗試 Eazfuscator.NET
Eazfuscator.NET以前免費,現在已經變成收費軟體了,不過找個免費的3.3版本也可以支援VS2010和VS2012

WPF程式將DLL嵌入到EXE中(二)——使用LibZ Container

LibZ是ILMerge的另外一個選擇,它同樣可以把DLL嵌入到EXE中去,在我的測試中它可以完成WPF程式的DLL嵌入到EXE中去,但好像這個元件使用的人不是很多。
LibZ Container的專案主頁是http://libz.codeplex.com/
LibZ同樣提供了Nuget下載,使用Nuget有很多好處,所以推薦使用這種方式
使用Nuget圖形或者命令下載LibZ.Bootstrap

Install-Package LibZ.Bootstrap

然後,配置Post buid 指令碼:

set LIBZ=$(SolutionDir)packages\LibZ.Bootstrap.1.1.0.2\tools\libz.exe
%LIBZ% inject-dll --assembly VSConverter.WPF.exe --include *.dll --move

編譯通過後就可以了。這裡需要注意的是--assembly後的引數是專案生成的檔名.
LibZ還有很多用法,可以到專案文件學習。

參考資料

  1. Combining multiple assemblies into a single EXE for a WPF application
  2. DLL嵌入exe中
  3. LibZ專案

Nuget是個十分強大的工具,使用Nuget在很多時候可以使解決方法變得簡單,給Nuget贊一個!
再做下廣告,Visual Studio版本轉換工具WPF版的程式碼託管地址是:http://git.oschina.net/shupengluo/VSConverter,歡迎交流。

最後,再小小地鄙視下微軟,^_^