Assembly.Load를 이용해 GAC에 등록된 어셈블리를 로드하는 방법
GAC에 등록된 어셈블리를 로드하는 방법이 애매합니다. 물론 다음과 같이 하면 어셈블리 로드가 됩니다.
string assemblyName = "Microsoft.SqlServer.Types, Version=12.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91";
Assembly asm = Assembly.Load(assemblyName);
하지만 저렇게 어셈블리 이름을 완전하게 지정하는 것이 애매할 때가 있습니다. 가장 큰 문제는 Version 값인데요. 대상 PC에 어떤 버전의 어셈블리가 설치되어 있는지 확실하지 않을 때가 있습니다.
이런 경우 해결책은 2가지 정도가 있습니다.
- Assembly.LoadWithPartialName을 이용하는 방법
- 그냥 특정 버전의 어셈블리를 로컬에 복사해서 배포하는 방법
첫 번째 LoadWithPartialName은 현재
[obsolete] 특성이 적용된 상태라서 왠지 쓰기가 불안합니다. 두 번째 방법이 그나마 나은데요. 이것도 좀 그런 것이... 대상 PC의 GAC에 설치된 최신 버전의 어셈블리가 그 제품의 최신 버전과만 연동할 수 있는 경우가 가능하기 때문입니다.
방법이 없을까??? 하고 검색을 좀 해보았는데, 색다른 해결책이 하나 나왔습니다.
Is it possible to Load an assembly from the GAC without the FullName?
; http://stackoverflow.com/questions/6121276/is-it-possible-to-load-an-assembly-from-the-gac-without-the-fullname
fusion.dll의 어셈블리 정보를 반환하는 IAssemblyCache.QueryAssemblyInfo 메서드를 직접 사용하는 것입니다. 위의 코드를 대략 정리해 보면 다음과 같습니다.
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("fusion.dll")]
private static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, int reserved);
static void Main(string[] args)
{
string gacAsmPath = GetAssemblyPath("Microsoft.SqlServer.Types");
Assembly sqlServerTypes = Assembly.LoadFrom(gacAsmPath);
}
public static string GetAssemblyPath(string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
string finalName = name;
AssemblyInfo aInfo = new AssemblyInfo();
aInfo.cchBuf = 1024;
aInfo.currentAssemblyPath = new String('\0', aInfo.cchBuf);
IAssemblyCache ac;
int hr = CreateAssemblyCache(out ac, 0);
if (hr >= 0)
{
hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo);
if (hr < 0)
{
return null;
}
}
return aInfo.currentAssemblyPath;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct AssemblyInfo
{
public int cbAssemblyInfo;
public int assemblyFlags;
public long assemblySizeInKB;
[MarshalAs(UnmanagedType.LPWStr)]
public string currentAssemblyPath;
public int cchBuf;
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
public interface IAssemblyCache
{
void Reserved0();
[PreserveSig]
int QueryAssemblyInfo(int flags, [MarshalAs(UnmanagedType.LPWStr)] string assemblyName, ref AssemblyInfo assemblyInfo);
}
}
그런데, 아쉽게도 GetAssemblyPath 메서드는 해당 어셈블리의 GAC 전체 경로를 반환하는 것이지, 어셈블리 이름을 반환하지는 않습니다. 그래서 Assembly.LoadFrom을 사용해야 하는데 이것도 역시 문제가 될 수 있습니다. 왜냐하면, Load와 LoadFrom의 바인딩 문맥이 다르기 때문입니다.
BindingContext - Load() vs. LoadFrom()
; https://imhumanvirus.tistory.com/186
약간은 불편하지만, AppDomain을 이용해 안전하게 어셈블리 이름만 구할 수도 있습니다. 즉, 다음과 같이 구현하시면 끝!
static void Main(string[] args)
{
string gacAsmPath = GetAssemblyPath("Microsoft.SqlServer.Types");
AppDomain appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
appDomain.SetData("asmName", gacAsmPath);
appDomain.DoCallBack(
() => {
string asmName = AppDomain.CurrentDomain.GetData("asmName") as string;
if (string.IsNullOrEmpty(asmName) == true)
{
return;
}
Assembly target = Assembly.LoadFrom(asmName);
AppDomain.CurrentDomain.SetData("asmName", target.FullName);
});
string assemblyName = appDomain.GetData("asmName") as string;
AppDomain.Unload(appDomain);
Assembly asm = Assembly.Load(assemblyName);
Console.WriteLine(assemblyName + " in " + asm.FullName);
}
(
첨부 파일은 위의 예제를 포함하고 있습니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]