یکی کردن اسمبلیهای ارجاعی یک برنامه WPF با فایل خروجی آن
نویسنده: وحید محمدطاهری
تاریخ: ۱۳۹۵/۱۰/۱۳ ۹:۲۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" />
<EmbeddedResource Include="@(AssembliesToEmbed)" Condition="'%(AssembliesToEmbed.Extension)' == '.dll'">
<LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
</Target>
<Target Name="DeleteAllReferenceCopyLocalPaths" AfterTargets="Build">
<Delete Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />
</Target>
همانطور که در تصویر بالا نیز مشاهده میکنید، اسمبلیهای ارجاعی برنامه TestApp به صورت Resource به آن اضافه شدهاند.
در پروژههای WPF، در OnStartup event کلاس App و در پروژههای WinForm در متد Main کلاس Program، قطعه کد زیر را وارد میکنیم:
private void App_OnStartup( object sender, StartupEventArgs e )
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
var assembly = Assembly.GetExecutingAssembly();
foreach (var name in assembly.GetManifestResourceNames())
{
if ( name.ToLower()
.EndsWith( ".resources" ) ||
!name.ToLower()
.EndsWith( ".dll" ) )
continue;
EmbeddedAssembly.Load( name,
name );
}
}
static Assembly OnResolveAssembly( object sender, ResolveEventArgs args )
{
var fields = args.Name.Split( ',' );
var name = fields[0];
var culture = fields[2];
if ( name.EndsWith( ".resources" ) &&
!culture.EndsWith( "neutral" ) )
return null;
return EmbeddedAssembly.Get( args.Name );
} با استفاده از رویداد AssemblyResolve می توان اسمبلی Embed شده را در زمانیکه نیاز به آن است، بارگذاری کرد. کد مربوط به کلاس EmbeddedAssembly نیز به این صورت میباشد:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
public static class EmbeddedAssembly
{
static Dictionary< string, Assembly > _dic;
public static void Load( string embeddedResource,
string fileName )
{
if ( _dic == null )
_dic = new Dictionary< string, Assembly >();
byte[] ba;
Assembly asm;
var curAsm = Assembly.GetExecutingAssembly();
using ( var stm = curAsm.GetManifestResourceStream( embeddedResource ) )
{
if ( stm == null )
return;
ba = new byte[(int)stm.Length];
stm.Read( ba,
0,
(int)stm.Length );
try
{
asm = Assembly.Load( ba );
_dic.Add( asm.GetName().Name,
asm );
return;
}
catch
{
}
}
bool fileOk;
string tempFile;
using ( var sha1 = new SHA1CryptoServiceProvider() )
{
var fileHash = BitConverter.ToString( sha1.ComputeHash( ba ) )
.Replace( "-",
string.Empty );
tempFile = Path.GetTempPath() + fileName;
if ( File.Exists( tempFile ) )
{
var bb = File.ReadAllBytes( tempFile );
var fileHash2 = BitConverter.ToString( sha1.ComputeHash( bb ) )
.Replace( "-",
string.Empty );
fileOk = fileHash == fileHash2;
}
else
{
fileOk = false;
}
}
if ( !fileOk )
{
File.WriteAllBytes( tempFile,
ba );
}
asm = Assembly.LoadFile( tempFile );
_dic.Add( asm.GetName().Name,
asm );
}
public static Assembly Get( string assemblyFullName )
{
if ( _dic == null ||
_dic.Count == 0 )
return null;
var name = new AssemblyName( assemblyFullName ).Name;
return _dic.ContainsKey( name )
? _dic[name]
: null;
}
} با استفاده از متد Load کلاس بالا، کل اسمبلیهایی که بارگذاری شدهاند در یک دیکشنری استاتیک نگهداری میشوند. ابتدا اسمبلیها را با استفاده از []byte بارگذاری میکنیم و در صورتیکه بارگذاری اسمبلی با خطایی مواجه شود، بارگذاری را با استفاده از فایل temp انجام میدهیم (که معمولا برای فایلهای unmanaged این مورد اتفاق میافتد).
با استفاده از متد Get که در زمان نیاز به یک اسمبلی توسط AssemblyResolve فراخوانی میشود، اسمبلی مربوطه از دیکشنری پیدا شده و برگشت داده میشود.
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" />
<EmbeddedResource Include="@(AssembliesToEmbed)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(AssembliesToEmbed.Filename)', '^((?!Microsoft).)*$')) And '%(AssembliesToEmbed.Extension)' == '.dll'">
<LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
</Target>
<Target Name="DeleteAllReferenceCopyLocalPaths" AfterTargets="Build">
<Delete Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Filename)', '^((?!Microsoft).)*$')) Or '%(Extension)' == '.xml'" />
</Target>
[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.MainAssembly)]
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" />
<EmbeddedResource Include="@(AssembliesToEmbed)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(AssembliesToEmbed.Filename)', '^((?!Stimulsoft).)*$')) And '%(AssembliesToEmbed.Extension)' == '.dll'">
<LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
</Target>
<Target Name="DeleteAllReferenceCopyLocalPaths" AfterTargets="Build">
<Delete Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Filename)', '^((?!Stimulsoft).)*$')) Or '%(Extension)' == '.xml'" />
</Target>