Development Tip

Side-by-Side 어셈블리를 사용하여 x64 또는 x32 버전의 DLL로드

yourdevel 2020. 12. 8. 20:08
반응형

Side-by-Side 어셈블리를 사용하여 x64 또는 x32 버전의 DLL로드


관리되는 C ++ 어셈블리의 두 가지 버전이 있습니다. 하나는 x86 용이고 다른 하나는 x64 용입니다. 이 어셈블리는 AnyCPU에 대해 준수 된 .net 응용 프로그램에 의해 호출됩니다. 우리는 파일 복사 설치를 통해 코드를 배포하고 있으며 계속 그렇게 할 것입니다.

응용 프로그램이 프로세서 아키텍처를 동적으로 선택하는 경우 Side-by-Side 어셈블리 매니페스트를 사용하여 x86 또는 x64 어셈블리를 각각로드 할 수 있습니까? 아니면 파일 복사 배포에서이 작업을 수행하는 다른 방법이 있습니까 (예 : GAC를 사용하지 않음)?


AnyCPU로 컴파일 된 실행 파일에서 플랫폼 별 어셈블리를로드 할 수있는 간단한 솔루션을 만들었습니다. 사용 된 기술은 다음과 같이 요약 할 수 있습니다.

  1. 기본 .NET 어셈블리로드 메커니즘 ( "Fusion"엔진)이 플랫폼 별 어셈블리의 x86 또는 x64 버전을 찾을 수 없는지 확인합니다.
  2. 기본 애플리케이션이 플랫폼 별 어셈블리로드를 시도하기 전에 현재 AppDomain에 사용자 지정 어셈블리 확인자를 설치합니다.
  3. 이제 메인 애플리케이션에 플랫폼 별 어셈블리가 필요할 때 Fusion 엔진은 (1 단계로 인해) 포기하고 사용자 지정 해결 프로그램을 호출합니다 (2 단계로 인해). 커스텀 리졸버에서 현재 플랫폼을 결정하고 디렉토리 기반 조회를 사용하여 적절한 DLL을로드합니다.

이 기술을 설명하기 위해 짧은 명령 줄 기반 자습서를 첨부합니다. Windows XP x86과 Vista SP1 x64에서 결과 바이너리를 테스트했습니다 (배포와 마찬가지로 바이너리를 복사하여).

참고 1 : "csc.exe"는 C-sharp 컴파일러입니다. 이 자습서에서는 경로에 있다고 가정합니다 (내 테스트에서는 "C : \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe"를 사용했습니다).

참고 2 : 테스트 용 임시 폴더를 만들고 현재 작업 디렉터리가이 위치로 설정된 명령 줄 (또는 powershell)을 실행하는 것이 좋습니다.

(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest

1 단계 : 플랫폼 별 어셈블리는 간단한 C # 클래스 라이브러리로 표시됩니다.

// file 'library.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Library
{
    public static class Worker
    {
        public static void Run()
        {
            System.Console.WriteLine("Worker is running");
            System.Console.WriteLine("(Enter to continue)");
            System.Console.ReadLine();
        }
    }
}

2 단계 : 간단한 명령 줄 명령을 사용하여 플랫폼 별 어셈블리를 컴파일합니다.

(cmd.exe from Note 2)
mkdir platform\x86
csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs
mkdir platform\amd64
csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs

3 단계 : 메인 프로그램은 두 부분으로 나뉩니다. "부트 스트 래퍼"는 실행 파일의 기본 진입 점을 포함하며 현재 appdomain에 사용자 지정 어셈블리 해결 프로그램을 등록합니다.

// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
    public static class Bootstrapper
    {
        public static void Main()
        {
            System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
            App.Run();
        }

        private static System.Reflection.Assembly CustomResolve(
            object sender,
            System.ResolveEventArgs args)
        {
            if (args.Name.StartsWith("library"))
            {
                string fileName = System.IO.Path.GetFullPath(
                    "platform\\"
                    + System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
                    + "\\library.dll");
                System.Console.WriteLine(fileName);
                if (System.IO.File.Exists(fileName))
                {
                    return System.Reflection.Assembly.LoadFile(fileName);
                }
            }
            return null;
        }
    }
}

"프로그램"은 응용 프로그램의 "실제"구현입니다 (App.Run은 Bootstrapper.Main의 끝에서 호출 됨).

// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
    public static class App
    {
        public static void Run()
        {
            Cross.Platform.Library.Worker.Run();
        }
    }
}

4 단계 : 명령 줄에서 기본 애플리케이션을 컴파일합니다.

(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs

5 단계 : 이제 끝났습니다. 우리가 만든 디렉토리의 구조는 다음과 같아야합니다 :

(C:\TEMP\CrossPlatformTest, root dir)
    platform (dir)
        amd64 (dir)
            library.dll
        x86 (dir)
            library.dll
    program.exe
    *.cs (source files)

32 비트 플랫폼에서 program.exe를 실행하면 platform \ x86 \ library.dll이로드됩니다. 64 비트 플랫폼에서 program.exe를 실행하면 platform \ amd64 \ library.dll이로드됩니다. Worker.Run 메서드 끝에 Console.ReadLine ()을 추가하여 작업 관리자 / 프로세스 탐색기를 사용하여로드 된 DLL을 조사하거나 Visual Studio / Windows 디버거를 사용하여 프로세스에 연결하여 호출 스택 등

When program.exe is run, our custom assembly resolver is attached to current appdomain. As soon as .NET starts loading the Program class, it sees a dependency on 'library' assembly, so it tries loading it. However, no such assembly is found (because we've hidden it in platform/* subdirectories). Luckily, our custom resolver knows our trickery and based on the current platform it tries loading the assembly from appropriate platform/* subdirectory.


My version, similar to @Milan, but with several important changes:

  • Works for ALL DLLs that were not found
  • Can be turned on and off
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase is used instead of Path.GetFullPath() because the current directory might be different, e.g. in hosting scenarios, Excel might load your plugin but the current directory will not be set to your DLL.

  • Environment.Is64BitProcess is used instead of PROCESSOR_ARCHITECTURE, as we should not depend on what the OS is, rather how this process was started - it could have been x86 process on a x64 OS. Before .NET 4, use IntPtr.Size == 8 instead.

Call this code in a static constructor of some main class that is loaded before all else.

public static class MultiplatformDllLoader
{
    private static bool _isEnabled;

    public static bool Enable
    {
        get { return _isEnabled; }
        set
        {
            lock (typeof (MultiplatformDllLoader))
            {
                if (_isEnabled != value)
                {
                    if (value)
                        AppDomain.CurrentDomain.AssemblyResolve += Resolver;
                    else
                        AppDomain.CurrentDomain.AssemblyResolve -= Resolver;
                    _isEnabled = value;
                }
            }
        }
    }

    /// Will attempt to load missing assembly from either x86 or x64 subdir
    private static Assembly Resolver(object sender, ResolveEventArgs args)
    {
        string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
        string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                                               Environment.Is64BitProcess ? "x64" : "x86",
                                               assemblyName);

        return File.Exists(archSpecificPath)
                   ? Assembly.LoadFile(archSpecificPath)
                   : null;
    }
}

Have a look at SetDllDirectory. I used it around the dynamically loading of an IBM spss assembly for both x64 and x86. It also solved paths for non assembly support dll's loaded by the assemblies in my case was the case with the spss dll's.

http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx


You can use the corflags utility to force an AnyCPU exe to load as an x86 or x64 executable, but that doesn't totally meet the file copy deployment requirement unless you choose which exe to copy based on the target.


This solution can work for non managed assemblies as well. I have created a simple example similar to Milan Gardian's great example. The example I created dynamically loads a Managed C++ dll into a C# dll compiled for the Any CPU platform. The solution makes use of the InjectModuleInitializer nuget package to subscribe to the AssemblyResolve event before the dependencies of the assembly are loaded.

https://github.com/kevin-marshall/Managed.AnyCPU.git

참고URL : https://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll

반응형