다음을 통해 공유


32비트 관리 코드를 64비트로 마이그레이션

 

Microsoft Corporation

업데이트 날짜: 2005년 5월

적용 대상:
   Microsoft .NET
   Microsoft .NET Framework 2.0

요약: 32비트 관리형 애플리케이션을 64비트로 마이그레이션하는 데 관련된 사항, 마이그레이션에 영향을 줄 수 있는 문제 및 도움이 되는 도구를 알아보세요. (17페이지 인쇄)

콘텐츠

소개
32비트 환경의 관리 코드
64비트 환경에 대한 CLR 입력
마이그레이션 및 플랫폼 호출
마이그레이션 및 COM 상호 운용성
마이그레이션 및 안전하지 않은 코드
마이그레이션 및 마샬링
마이그레이션 및 직렬화
요약

소개

이 백서에서는 다음을 설명합니다.

  • 관리되는 애플리케이션을 32비트에서 64비트로 마이그레이션하는 데 관련된 사항
  • 마이그레이션에 영향을 미칠 수 있는 문제
  • 도움을 줄 수 있는 도구

이 정보는 규범적이지 않습니다. 오히려 64비트로 마이그레이션하는 동안 문제에 취약한 다양한 영역을 숙지하기 위한 것입니다. 이 시점에서 수행할 수 있는 단계의 특정 "cookbook"이 없으며 코드가 64비트에서 작동할 수 있도록 보장합니다. 이 백서에 포함된 정보는 다양한 문제와 검토해야 할 사항을 숙지합니다.

곧 보듯이 관리되는 어셈블리가 100% 형식 안전 코드가 아닌 경우 애플리케이션 및 해당 종속성을 검토하여 64비트로 마이그레이션하는 문제를 확인해야 합니다. 다음 섹션에서 읽게 될 대부분의 항목은 프로그래밍 변경을 통해 해결할 수 있습니다. 또한 32비트 환경과 64비트 환경 모두에서 올바르게 실행하려면 코드를 업데이트할 시간을 따로 설정해야 합니다.

Microsoft .NET은 정보, 사람, 시스템 및 디바이스를 연결하기 위한 소프트웨어 기술 세트입니다. 2002년 1.0 릴리스 이후 조직은 를 배포하는 데 성공했습니다. 사내, ISV(독립 소프트웨어 공급업체) 또는 일부 조합에 의해 빌드되었는지 여부에 관계없이 NET 기반 솔루션입니다. 32비트 환경의 제한을 푸시하는 여러 유형의 .NET 애플리케이션이 있습니다. 이러한 과제에는 실제 주소 지정 가능한 메모리의 필요성과 부동 소수점 성능 향상의 필요성이 포함되지만 이에 국한되지는 않습니다. x64 및 Itanium은 x86에서 얻을 수 있는 것보다 부동 소수점 작업에 더 나은 성능을 제공합니다. 그러나 x64 또는 Itanium에서 얻을 수 있는 결과가 x86에서 얻을 수 있는 결과와 다를 수도 있습니다. 64비트 플랫폼은 이러한 문제를 해결하는 데 도움이 되는 것을 목표로 합니다.

.NET Framework 버전 2.0 릴리스에서 Microsoft는 x64 및 Itanium 64비트 플랫폼에서 실행되는 관리 코드에 대한 지원을 포함합니다.

관리 코드는 단순히 .NET CLR(공용 언어 런타임)이 다음을 비롯한 핵심 서비스 집합을 제공할 수 있는 충분한 정보를 제공하는 "코드"입니다.

  • 메타데이터를 통한 코드 및 데이터에 대한 자체 설명
  • 스택 워크
  • 보안
  • 가비지 수집
  • Just-In-Time 컴파일

관리 코드 외에도 마이그레이션 문제를 조사할 때 이해해야 하는 몇 가지 다른 정의가 있습니다.

관리되는 데이터 - 관리되는 힙에 할당되고 가비지 수집을 통해 수집되는 데이터입니다.

어셈블리 - CLR이 애플리케이션의 내용을 완전히 이해하고 애플리케이션에서 정의한 버전 관리 및 종속성 규칙을 적용할 수 있도록 하는 배포 단위입니다.

형식 안전 코드 - 관리되는 데이터만 사용하고 확인할 수 없는 데이터 형식이나 지원되지 않는 데이터 형식 변환/강제 변환 작업(즉, 비차별 공용 구조체 또는 구조/인터페이스 포인터)을 사용하는 코드입니다. C#, Visual Basic .NET 및 /clr:safe 로 컴파일된 Visual C++ 코드는 형식 안전 코드를 생성합니다.

안전하지 않은 코드 - 포인터 선언 및 작업, 포인터와 정수 형식 간의 변환 수행, 변수 주소 가져오기와 같은 하위 수준 작업을 수행할 수 있는 코드입니다. 이러한 작업을 통해 기본 운영 체제와 상호 작용하거나, 메모리 매핑된 디바이스에 액세스하거나, 시간이 중요한 알고리즘을 구현할 수 있습니다. 네이티브 코드는 안전하지 않습니다.

32비트 환경의 관리 코드

관리 코드를 64비트 환경으로 마이그레이션하는 것과 관련된 복잡성을 이해하려면 32비트 환경에서 관리 코드가 실행되는 방법을 검토해 보겠습니다.

관리형 또는 비관리형 애플리케이션을 실행하도록 선택하면 Windows 로더가 호출되고 애플리케이션을 로드한 다음 실행하는 방법을 결정할 책임이 있습니다. 이 프로세스의 일부에는 실행 파일의 PE(이식 가능한 실행) 헤더 내부를 피킹하여 CLR이 필요한지 확인하는 작업이 포함됩니다. 이미 짐작했듯이 PE에는 관리 코드를 나타내는 플래그가 있습니다. 이 경우 Windows 로더는 관리되는 애플리케이션 로드 및 실행을 담당하는 CLR을 시작합니다. 실행할 CLR 버전 결정, AppDomain '샌드박스' 설정 등과 관련된 여러 단계가 있으므로 프로세스에 대한 간단한 설명입니다.

상호 운용성

관리되는 애플리케이션이 실행되면(적절한 보안 권한이 있다고 가정) CLR 상호 운용성 기능을 통해 네이티브 API(Win32 API 포함) 및 COM 개체와 상호 작용할 수 있습니다. 네이티브 플랫폼 API를 호출하든, COM 요청을 하든, 구조를 마샬링하든, 32비트 환경 내에서 완전히 실행될 때 개발자는 데이터 형식 크기 및 데이터 정렬에 대해 생각할 필요가 없습니다.

64비트로의 마이그레이션을 고려할 때 애플리케이션에 있는 종속성을 조사하는 것이 중요합니다.

64비트 환경에 대한 CLR 입력

관리 코드가 32비트 환경과 일치하는 64비트 환경에서 실행되도록 하기 위해 .NET 팀은 Itanium 및 x64 64비트 시스템용 CLR(공용 언어 런타임)을 개발했습니다. CLR은 .NET 언어로 작성된 코드가 32비트 환경에서와 같이 상호 운용될 수 있도록 CLI(공용 언어 인프라) 및 공용 언어 유형 시스템의 규칙을 엄격하게 준수해야 했습니다. 또한 다음은 64비트 환경을 위해 이식 및/또는 개발해야 했던 다른 부분의 목록입니다.

  • 기본 클래스 라이브러리(System.*)
  • Just-In-Time 컴파일러
  • 디버깅 지원
  • .NET Framework SDK

64비트 관리 코드 지원

.NET Framework 버전 2.0은 다음을 실행하는 Itanium 및 x64 64비트 프로세서를 지원합니다.

  • Windows Server 2003 SP1
  • 향후 Windows 64비트 클라이언트 릴리스

(Windows 2000에는 .NET Framework 버전 2.0을 설치할 수 없습니다. .NET Framework 버전 1.0 및 1.1을 사용하여 생성된 출력 파일은 64비트 운영 체제의 WOW64에서 실행됩니다.)

64비트 플랫폼에 .NET Framework 버전 2.0을 설치할 때 관리 코드를 64비트 모드로 실행하는 데 필요한 모든 인프라를 설치할 뿐만 아니라 Windows 기반 Windows 하위 시스템 또는 WoW64(32비트 모드)에서 실행하기 위해 관리 코드에 필요한 인프라를 설치합니다.

간단한 64비트 마이그레이션

100% 형식 안전 코드인 .NET 애플리케이션을 고려합니다. 이 시나리오에서는 32비트 컴퓨터에서 실행하는 .NET 실행 파일을 가져와서 64비트 시스템으로 이동하여 성공적으로 실행할 수 있습니다. 이것이 작동하는 이유는 무엇인가요? 어셈블리는 100% 형식 안전하므로 네이티브 코드 또는 COM 개체에 대한 종속성이 없고 애플리케이션이 CLR의 제어 하에 완전히 실행되는 '안전하지 않은' 코드가 없다는 것을 알고 있습니다. CLR은 JIT(Just-In-Time) 컴파일 결과로 생성된 이진 코드가 32비트와 64비트 간에 다르지만 실행되는 코드는 모두 의미상 동일합니다. (Windows 2000에는 .NET Framework 버전 2.0을 설치할 수 없습니다. .NET Framework 버전 1.0 및 1.1을 사용하여 생성된 출력 파일은 64비트 운영 체제의 WOW64에서 실행됩니다.)

실제로 이전 시나리오는 관리되는 애플리케이션을 로드하는 관점에서 좀 더 복잡합니다. 이전 섹션에서 설명한 대로 Windows 로더는 애플리케이션을 로드하고 실행하는 방법을 결정합니다. 그러나 32비트 환경과 달리 64비트 Windows 플랫폼에서 실행된다는 것은 네이티브 64비트 모드 또는 WoW64에서 애플리케이션을 실행할 수 있는 두 개의 환경이 있음을 의미합니다.

이제 Windows 로더는 PE 헤더에서 검색한 내용에 따라 결정을 내려야 합니다. 짐작할 수 있듯이 이 프로세스를 지원하는 관리 코드에 설정 가능한 플래그가 있습니다. (PE에 설정을 표시하려면 corflags.exe 참조하세요.) 다음 목록은 의사 결정 프로세스에 도움이 되는 PE에 있는 정보를 나타냅니다.

  • 64비트 - 개발자가 64비트 프로세스를 대상으로 하는 어셈블리를 빌드했음을 나타냅니다.
  • 32비트 - 개발자가 32비트 프로세스를 대상으로 하는 어셈블리를 빌드했음을 나타냅니다. 이 instance 어셈블리는 WoW64에서 실행됩니다.
  • 독립적 - 개발자가 코드 이름 "Whidbey"인 Visual Studio 2005를 사용하여 어셈블리를 빌드했음을 나타냅니다. 이상 도구 및 어셈블리는 64비트 또는 32비트 모드에서 실행할 수 있습니다. 이 경우 64비트 Windows 로더는 64비트에서 어셈블리를 실행합니다.
  • 레거시 - 어셈블리를 빌드한 도구가 "Pre-Whidbey"임을 나타냅니다. 이 특정 경우 어셈블리는 WoW64에서 실행됩니다.

참고 PE에는 어셈블리가 특정 아키텍처를 대상으로 하는지 Windows 로더에 알리는 정보도 있습니다. 이 추가 정보는 특정 아키텍처를 대상으로 하는 어셈블리가 다른 아키텍처에 로드되지 않도록 합니다.

C#, Visual Basic .NET 및 C++ Whidbey 컴파일러를 사용하면 PE 헤더에 적절한 플래그를 설정할 수 있습니다. 예를 들어 C# 및 THIRD에는 /platform:{anycpu, x86, Itanium, x64} 컴파일러 옵션이 있습니다.

참고 어셈블리가 컴파일된 후 기술적으로 어셈블리의 PE 헤더에서 플래그를 수정할 수 있지만 이 작업은 권장하지 않습니다.

이러한 플래그가 관리되는 어셈블리에서 어떻게 설정되는지 알고 싶다면 .NET Framework SDK에 제공된 ILDASM 유틸리티를 실행할 수 있습니다. 다음 그림에서는 "레거시" 애플리케이션을 보여 줍니다.

어셈블리를 Win64로 표시하는 개발자가 애플리케이션의 모든 종속성이 64비트 모드에서 실행되도록 결정했습니다. 64비트 프로세스는 프로세스에서 32비트 구성 요소를 사용할 수 없습니다(32비트 프로세스는 프로세스에서 64비트 구성 요소를 로드할 수 없음). 시스템에서 어셈블리를 64비트 프로세스로 로드하는 기능이 자동으로 올바르게 실행된다는 의미는 아닙니다.

따라서 이제 100% 형식의 안전한 관리 코드로 구성된 애플리케이션을 64비트 플랫폼에 복사(또는 xcopy 배포)하고 JIT를 사용하고 64비트 모드에서 .NET으로 성공적으로 실행할 수 있다는 것을 알게 되었습니다.

그러나, 우리는 종종 이상적이지 않은 상황을 볼 수 있으며, 이는 마이그레이션과 관련된 문제에 대한 인식을 높이기 위한 이 논문의 기본 초점을 맞춥니다.

100% 형식이 안전하지 않고 .NET에서 64비트에서 성공적으로 실행될 수 있는 애플리케이션을 사용할 수 있습니다. 다음 섹션에서 설명하는 잠재적인 문제를 염두에 두고 애플리케이션을 주의 깊게 살펴보고 64비트에서 성공적으로 실행할 수 있는지 여부를 결정하는 것이 중요합니다.

마이그레이션 및 플랫폼 호출

.NET의 플랫폼 호출(또는 p/invoke) 기능을 사용하는 것은 관리되지 않는 코드 또는 네이티브 코드를 호출하는 관리 코드를 나타냅니다. 일반적인 시나리오에서 이 네이티브 코드는 시스템의 일부(Windows API 등), 애플리케이션의 일부 또는 타사 라이브러리인 DLL(동적 연결 라이브러리)입니다.

관리되지 않는 코드를 사용하는 것은 64비트로의 마이그레이션에 문제가 있음을 명시적으로 의미하지는 않습니다. 오히려 추가 조사가 필요하다는 지표로 간주되어야 합니다.

Windows의 데이터 형식

모든 애플리케이션과 모든 운영 체제에는 추상 데이터 모델이 있습니다. 많은 애플리케이션이 이 데이터 모델을 명시적으로 노출하지 않지만 모델은 애플리케이션의 코드가 기록되는 방식을 안내합니다. 32비트 프로그래밍 모델(ILP32 모델이라고 함)에서 정수, long 및 포인터 데이터 형식은 길이가 32비트입니다. 대부분의 개발자는 이 모델을 실현하지 않고 사용했습니다.

64비트 Microsoft Windows에서는 데이터 형식 크기의 패리티 가정이 잘못되었습니다. 모든 데이터 형식을 64비트 길이로 만들면 대부분의 애플리케이션에서 크기가 증가할 필요가 없으므로 공간이 낭비됩니다. 그러나 애플리케이션에는 64비트 데이터에 대한 포인터가 필요하며 선택한 경우 64비트 데이터 형식을 가질 수 있는 기능이 필요합니다. 이러한 고려 사항으로 인해 Windows 팀은 LLP64(또는 P64)라는 추상 데이터 모델을 선택했습니다. LLP64 데이터 모델에서는 포인터만 64비트로 확장됩니다. 다른 모든 기본 데이터 형식(정수 및 길이)은 32비트 길이로 유지됩니다.

64비트 플랫폼용 .NET CLR은 동일한 LLP64 추상 데이터 모델을 사용합니다. .NET에는 널리 알려지지 않은 정수 데이터 형식이 있는데, 이는 특별히 '포인터' 정보를 보유하도록 지정됩니다. IntPtr 의 크기는 실행 중인 플랫폼(예: 32비트 또는 64비트)에 종속됩니다. 다음 코드 조각을 살펴봅니다.

[C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}

32비트 플랫폼에서 실행하면 콘솔에서 다음 출력이 표시됩니다.

SizeOf IntPtr is: 4

64비트 플랫폼에서 콘솔에서 다음 출력을 가져옵니다.

SizeOf IntPtr is: 8

참고 64비트 환경에서 실행 중인지 여부에 관계없이 런타임에 검사 하려면 IntPtr.Size를 한 가지 방법으로 사용하여 이 결정을 내릴 수 있습니다.

마이그레이션 고려 사항

p/invoke를 사용하는 관리되는 애플리케이션을 마이그레이션할 때 다음 항목을 고려합니다.

  • 64비트 버전의 DLL 가용성
  • 데이터 형식 사용

가용성

먼저 결정해야 할 사항 중 하나는 애플리케이션에 종속성이 있는 관리되지 않는 코드를 64비트에서 사용할 수 있는지 여부입니다.

이 코드가 사내에서 개발된 경우 성공 능력이 증가합니다. 물론 테스트, 품질 보증 등에 적합한 리소스와 함께 관리되지 않는 코드를 64비트로 포팅하기 위해 리소스를 할당해야 합니다. (이 백서는 개발 프로세스에 대한 권장 사항을 만드는 것이 아니라 코드를 포팅하기 위해 작업에 리소스를 할당해야 할 수도 있음을 지적하려고 합니다.)

이 코드가 타사에서 온 경우 이 타사에 이미 64비트용으로 사용할 수 있는 코드가 있는지 여부와 타사에서 사용할 수 있도록 할 의향이 있는지 조사해야 합니다.

타사에서 이 코드를 더 이상 지원하지 않거나 타사에서 작업을 수행할 의사가 없는 경우 더 높은 위험 문제가 발생합니다. 이러한 경우는 유사한 기능을 수행하는 사용 가능한 라이브러리에 대한 추가 연구가 필요합니다. 타사에서 고객이 포트를 직접 수행할 수 있도록 할지 여부 등입니다.

종속 코드의 64비트 버전에는 추가 개발 작업을 의미할 수 있는 인터페이스 서명이 변경되었을 수 있으며 애플리케이션의 32비트와 64비트 버전 간의 차이점을 resolve 것이 중요합니다.

데이터 유형

p/invoke를 사용하려면 .NET에서 개발된 코드가 관리 코드가 대상으로 하는 메서드의 프로토타입을 선언해야 합니다. 다음 C 선언을 지정합니다.

[C++]
typedef void * HANDLE
HANDLE GetData();

프로토타입화된 메서드의 예는 다음과 같습니다.

[C#]

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public static extern int DoWork( int x, int y );

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public unsafe static extern int GetData();

64비트 마이그레이션 문제를 살펴보겠습니다.

첫 번째 예제에서는 두 개의 (2) 32비트 정수로 전달되는 DoWork 메서드를 호출하며 32비트 정수가 반환될 것으로 예상합니다. 64비트 플랫폼에서 실행 중이지만 정수는 여전히 32비트입니다. 이 특정 예제에는 마이그레이션 노력을 방해해야 하는 것은 없습니다.

두 번째 예제에서는 64비트에서 성공적으로 실행되도록 코드를 일부 변경해야 합니다. 여기서 수행하는 작업은 GetData 메서드를 호출하고 정수가 반환될 것으로 예상하지만 함수가 실제로 int 포인터를 반환한다고 선언하는 것입니다. 여기서는 정수는 32비트이지만 64비트 포인터는 8바이트입니다. 알고 보니 포인터와 정수의 길이가 4바이트인 것으로 가정하여 32비트 세계에서 꽤 많은 코드가 작성되었습니다. 64비트 세계에서는 더 이상 사실이 아닙니다.

이 마지막 경우 int 대신 IntPtr을 사용하도록 메서드 선언을 변경하여 문제를 해결할 수 있습니다.

public unsafe static extern IntPtr GetData();

이 변경은 32비트 및 64비트 환경 모두에서 작동합니다. IntPtr은 플랫폼에 따라 다릅니다.

관리되는 애플리케이션에서 p/invoke를 사용한다고 해서 64비트 플랫폼으로 마이그레이션할 수 없다는 의미는 아닙니다. 문제가 있을 것임을 의미하지도 않습니다. 의미는 관리되는 애플리케이션에 있는 관리되지 않는 코드에 대한 종속성을 검토하고 문제가 있는지 확인해야 한다는 것입니다.

마이그레이션 및 COM 상호 운용성

COM 상호 운용성은 .NET 플랫폼의 가정된 기능입니다. 플랫폼 호출에 대한 이전 설명과 마찬가지로 COM 상호 운용성을 사용하는 것은 관리 코드가 관리되지 않는 코드를 호출하고 있음을 의미합니다. 그러나 플랫폼 호출과 달리 COM 상호 운용성은 관리되지 않는 코드가 COM 구성 요소인 것처럼 관리 코드를 호출하는 기능을 갖는 것을 의미합니다.

다시 한 번 비관리 COM 코드를 사용한다고 해서 64비트로 마이그레이션하는 데 문제가 있는 것은 아닙니다. 오히려 추가 조사가 필요하다는 지표로 간주되어야 합니다.

마이그레이션 고려 사항

.NET Framework 버전 2.0 릴리스에서는 아키텍처 간 상호 운용성을 지원하지 않는다는 점을 이해하는 것이 중요합니다. 좀 더 간결하게 하기 위해 동일한 프로세스에서 32비트와 64비트 간의 COM 상호 운용성을 사용할 수 없습니다. 그러나 Out-of-process COM 서버가 있는 경우 32비트와 64비트 간의 COM 상호 운용성을 사용할 수 있습니다. Out-of-process COM 서버를 사용할 수 없는 경우 32비트 COM 개체와 상호 운용할 수 있도록 프로그램이 WoW64에서 실행되도록 관리되는 어셈블리를 Win64 또는 Agnostic이 아닌 Win32로 표시하려고 합니다.

다음은 관리 코드가 64비트 환경에서 COM을 호출하는 COM 상호 운용성을 사용하기 위해 고려해야 하는 다양한 고려 사항에 대한 설명입니다. 특히,

  • 64비트 버전의 DLL 가용성
  • 데이터 형식 사용
  • 형식 라이브러리

가용성

종속 코드의 64비트 버전 가용성에 대한 p/invoke 섹션의 설명은 이 섹션과도 관련이 있습니다.

데이터 유형

종속 코드의 64비트 버전 데이터 형식에 대한 p/invoke 섹션의 설명도 이 섹션과 관련이 있습니다.

형식 라이브러리

어셈블리와 달리 형식 라이브러리는 '중립'으로 표시할 수 없습니다. Win32 또는 Win64로 표시되어야 합니다. 또한 COM이 실행되는 각 환경에 대해 형식 라이브러리를 등록해야 합니다. tlbimp.exe 사용하여 형식 라이브러리에서 32비트 또는 64비트 어셈블리를 생성합니다.

관리되는 애플리케이션에서 COM 상호 운용성을 사용한다고 해서 64비트 플랫폼으로 마이그레이션할 수 없다는 의미는 아닙니다. 문제가 있을 것임을 의미하지도 않습니다. 의미는 관리되는 애플리케이션의 종속성을 검토하고 문제가 있는지 확인해야 한다는 것입니다.

마이그레이션 및 안전하지 않은 코드

핵심 C# 언어는 데이터 형식으로 포인터를 생략할 때 C 및 C++와 특히 다릅니다. 대신 C#은 참조와 가비지 수집기에서 관리하는 개체를 만드는 기능을 제공합니다. 핵심 C# 언어에서는 초기화되지 않은 변수, "매달려 있는" 포인터 또는 범위를 벗어나는 배열을 인덱싱하는 식을 가질 수 없습니다. 따라서 C 및 C++ 프로그램을 일상적으로 괴롭히는 버그의 전체 범주가 제거됩니다.

C 또는 C++의 거의 모든 포인터 형식 구문에는 C#에 대응하는 참조 형식이 있지만 포인터 형식에 대한 액세스가 필요한 상황이 있습니다. 예를 들어 기본 운영 체제와 상호 작용하거나, 메모리 매핑된 디바이스에 액세스하거나, 시간이 중요한 알고리즘을 구현하는 것은 포인터에 액세스하지 않고는 가능하거나 실용적이지 않을 수 있습니다. 이러한 필요성을 해결하기 위해 C#은 안전하지 않은 코드를 작성하는 기능을 제공합니다.

안전하지 않은 코드에서는 포인터를 선언하고 작동하고, 포인터와 정수 형식 간에 변환을 수행하고, 변수 주소를 사용하는 등의 작업을 수행할 수 있습니다. 어떤 의미에서 안전하지 않은 코드를 작성하는 것은 C# 프로그램 내에서 C 코드를 작성하는 것과 비슷합니다.

안전하지 않은 코드는 사실 개발자와 사용자 모두의 관점에서 "안전한" 기능입니다. 안전하지 않은 코드는 안전하지 않은 한정자로 명확하게 표시되어야 하므로 개발자는 실수로 안전하지 않은 기능을 사용할 수 없습니다.

마이그레이션 고려 사항

안전하지 않은 코드와 관련된 잠재적인 문제를 논의하기 위해 다음 예제를 살펴보겠습니다. 관리 코드는 관리되지 않는 DLL을 호출합니다. 특히 100개 항목을 반환하는 GetDataBuffer 라는 메서드가 있습니다(이 예제에서는 고정된 수의 항목을 반환함). 이러한 각 항목은 정수와 포인터로 구성됩니다. 아래 샘플 코드는 이 반환된 데이터를 처리하는 안전하지 않은 함수를 보여 주는 관리 코드에서 발췌한 것입니다.

[C#]

public unsafe int UnsafeFn() {
   IntPtr * inputBuffer = sampleDLL.GetDataBuffer();
   IntPtr * ptr = inputBuffer;
   int   result = 0;

   for ( int idx = 0; idx < 100; idx ++ ) {
      // Add 'int' from DLL to our result
      result = result + ((int) *ptr);

// Increment pointer over int (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

      // Increment pointer over pointer (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
   }
   return result;
}

참고 이 특정 예제는 안전하지 않은 코드를 사용하지 않고 수행할 수 있습니다. 더 구체적으로 말하자면, 사용되었을 수 있는 마샬링과 같은 다른 기술이 있습니다. 그러나 이러한 목적을 위해 안전하지 않은 코드를 사용하고 있습니다.

UnsafeFn은 100개 항목을 반복하고 정수 데이터의 합계를 계산합니다. 데이터 버퍼를 탐색할 때 코드는 정수와 포인터를 모두 단계별로 실행해야 합니다. 32비트 환경에서 이 코드는 정상적으로 작동합니다. 그러나 앞에서 설명한 것처럼 포인터는 64비트 환경에서 8바이트이므로 코드 세그먼트(아래 참조)는 일반적인 프로그래밍 기술(예: 정수와 동등한 포인터 처리)을 사용하기 때문에 제대로 작동하지 않습니다.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

이 코드가 32비트 및 64비트 환경에서 작동하려면 코드를 다음으로 변경해야 합니다.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( IntPtr ) );

방금 살펴보았듯이 안전하지 않은 코드를 사용해야 하는 인스턴스가 있습니다. 대부분의 경우 다른 인터페이스에 대한 관리 코드의 종속성의 결과로 필요합니다. 안전하지 않은 코드가 존재하는 이유와 관계없이 마이그레이션 프로세스의 일부로 검토해야 합니다.

위에서 사용한 예제는 비교적 간단하며 64비트에서 프로그램을 작동하도록 수정하는 것은 간단합니다. 분명히 더 복잡한 안전하지 않은 코드의 많은 예가 있습니다. 일부는 심층 검토가 필요하며 관리 코드가 사용하는 접근 방식을 다시 한 단계 뒤로 물러서서 재고해야 할 수 있습니다.

이미 읽은 내용을 반복하려면 관리되는 애플리케이션에서 안전하지 않은 코드를 사용한다고 해서 64비트 플랫폼으로 마이그레이션할 수 없다는 의미는 아닙니다. 문제가 있을 것이라는 의미도 아닙니다. 즉, 관리되는 애플리케이션에 있는 안전하지 않은 코드를 모두 검토하고 문제가 있는지 확인해야 합니다.

마이그레이션 및 마샬링

마샬링에서는 관리되지 않는 메모리를 할당하고, 관리되지 않는 메모리 블록을 복사하고, 관리되지 않는 형식으로 변환하는 메서드 컬렉션과 관리되지 않는 코드와 상호 작용할 때 사용되는 기타 메서드를 제공합니다.

마샬링이 .NET Marshal 클래스를 통해 매니페스트됩니다. Visual Basic에서 정적 또는 공유 되는 Marshal 클래스에 정의된 메서드는 관리되지 않는 데이터 작업에 필수적입니다. 관리되는 프로그래밍 모델과 관리되지 않는 프로그래밍 모델 간에 브리지를 제공해야 하는 사용자 지정 마샬러를 빌드하는 고급 개발자는 일반적으로 정의된 대부분의 메서드를 사용합니다.

마이그레이션 고려 사항

마샬링하면 애플리케이션을 64비트로 마이그레이션하는 것과 관련된 더 복잡한 문제 중 일부가 발생합니다. 개발자가 마샬링을 통해 수행하려는 작업의 특성, 즉 관리 코드와 비관리 코드 간의 구조화된 정보를 전송하는 경우 시스템을 지원하기 위해 정보를 제공하는 것을 볼 수 있습니다.

레이아웃 측면에서 개발자가 만들 수 있는 두 가지 특정 선언이 있습니다. 이러한 선언은 일반적으로 코딩 특성을 사용하여 이루어집니다.

LayoutKind.Sequential

.NET Framework SDK 도움말에 제공된 정의를 검토해 보겠습니다.

"개체의 멤버는 관리되지 않는 메모리로 내보낼 때 나타나는 순서대로 순차적으로 배치됩니다. 멤버는 StructLayoutAttribute.Pack에 지정된 포장에 따라 배치되며 연결되지 않을 수 있습니다."

레이아웃이 정의된 순서에 따라 다릅니다. 그런 다음 관리되는 선언과 관리되지 않는 선언이 유사한지 확인하기만 하면 됩니다. 그러나, 우리는 또한 포장도 중요한 성분이라고 들었습니다. 이 시점에서 개발자가 명시적으로 개입하지 않으면 기본 팩 값이 있다는 사실에 놀라지 않을 것입니다. 이미 짐작했듯이 기본 팩 값은 32비트와 64비트 시스템 간에 동일하지 않습니다.

인접하지 않은 멤버에 대한 정의의 문은 기본 팩 크기가 있기 때문에 메모리에 배치된 데이터가 바이트 0, 바이트 1, 바이트2 등이 아닐 수 있다는 사실을 나타냅니다. 대신 첫 번째 멤버는 바이트 0이지만 두 번째 멤버는 바이트 4일 수 있습니다. 시스템은 잘못된 정렬 문제를 처리하지 않고도 컴퓨터가 멤버에 액세스할 수 있도록 이 기본 압축을 수행합니다.

다음은 포장에 세심한 주의를 기울여야 하는 영역이며, 동시에 시스템이 기본 모드에서 작동하도록 하려고 합니다.

다음은 관리 코드에 정의된 구조체와 관리되지 않는 코드에 정의된 해당 구조체의 예입니다. 이 예제에서 두 환경에서 팩 값을 설정하는 방법을 주의 깊게 기록해 두어야 합니다.

[C#]
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class XYZ {
      public byte arraysize = unchecked((byte)-1);
      [MarshalAs(UnmanagedType.ByValArray, SizeConst=52)]
      public int[] padding = new int[13];
};
[unmanaged c++]
#pragma pack(1)
typedef struct{
      BYTE arraysize;      // = (byte)-1;
      int      padding[13];
} XYZ;

LayoutKind.Explicit

.NET FrameworkSDK 도움말에 제공된 정의를 검토해 보겠습니다.

"관리되지 않는 메모리에 있는 개체의 각 멤버의 정확한 위치는 명시적으로 제어됩니다. 각 멤버는 FieldOffsetAttribute 를 사용하여 형식 내에서 해당 필드의 위치를 나타내야 합니다."

여기서 개발자는 정보 마샬링을 돕기 위해 정확한 오프셋을 제공할 것이라고 들었습니다. 따라서 개발자는 FieldOffset 특성의 정보를 올바르게 지정해야 합니다.

그렇다면 잠재적인 문제는 어디에 있을까요? 필드 오프셋은 진행 중인 데이터 멤버 크기의 크기를 알고 정의됩니다. 모든 데이터 형식 크기가 32비트와 64비트 사이의 크기는 아님을 기억해야 합니다. 특히 포인터의 길이는 4바이트 또는 8바이트입니다.

이제 특정 환경을 대상으로 관리되는 소스 코드를 업데이트해야 하는 경우가 있습니다. 아래 예제에서는 포인터를 포함하는 구조를 보여 주세요. 포인터를 IntPtr로 만들었지만 64비트로 이동할 때는 여전히 차이가 있습니다.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(8)] public int typeValue;
    }

64비트에서는 실제로 8이 아닌 오프셋 12에서 시작되므로 구조체의 마지막 데이터 멤버에 대한 필드 오프셋을 조정해야 합니다.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(12)] public int typeValue;
    }

마샬링 사용은 관리 코드와 비관리 코드 간의 복잡한 상호 운용성이 필요한 경우 현실입니다. 이 강력한 기능을 사용하는 것은 32비트 애플리케이션을 64비트 환경으로 마이그레이션할 수 있다는 지표가 아닙니다. 그러나 마샬링 사용과 관련된 복잡성 때문에 세부 사항에 주의해야 하는 영역입니다.

코드를 분석하면 각 플랫폼에 별도의 이진 파일이 필요한지 여부와 압축과 같은 문제를 해결하기 위해 관리되지 않는 코드를 수정해야 하는지 여부도 표시됩니다.

마이그레이션 및 직렬화

serialization은 지속시키거나 전송할 수 있는 형태로 개체 상태를 변환하는 프로세스입니다. serialization과 짝을 이루는 것은 스트림을 개체로 변환하는 deserialization입니다. 이 두 프로세스를 통해 데이터를 쉽게 저장하고 전송할 수 있습니다.

.NET Framework에서는 다음 두 가지의 serialization 기술이 있습니다.

  • 이진 serialization은 형식 정확도를 유지하므로 애플리케이션의 여러 호출 간에 개체 상태를 유지하는 데 유용합니다. 예를 들어, 개체를 클립보드로 serialize하면 여러 애플리케이션 간에 개체를 공유할 수 있습니다. 개체를 스트림, 디스크, 메모리, 네트워크 등으로 serialize할 수 있습니다. .NET Remoting은 serialization을 사용하여 한 컴퓨터 또는 애플리케이션 도메인에서 다른 컴퓨터 또는 애플리케이션 도메인으로 "값별" 개체를 전달합니다.
  • XML serialization은 public 속성과 필드만 serialize하며 형식 정확도를 유지하지 않습니다. 데이터를 사용하는 애플리케이션을 제한하지 않고 데이터를 제공하거나 사용하려고 할 때 유용합니다. XML은 공개 표준이기 때문에 웹을 통해 정보를 공유할 때 적합합니다. SOAP도 마찬가지로 공개 표준이어서 적합한 선택입니다.

마이그레이션 고려 사항

직렬화에 대해 생각할 때는 달성하려는 작업을 염두에 두어야 합니다. 64비트로 마이그레이션할 때 유의해야 할 한 가지 질문은 서로 다른 플랫폼 간에 직렬화된 정보를 공유할 것인지 여부입니다. 즉, 는 64비트 관리형 애플리케이션이 32비트 관리형 애플리케이션에 의해 저장된 정보를 읽거나 역직렬화합니다.

답변은 솔루션의 복잡성을 높이는 데 도움이 됩니다.

  • 플랫폼을 설명하기 위해 고유한 serialization 루틴을 작성할 수 있습니다.
  • 각 플랫폼이 자체 데이터를 읽고 쓸 수 있도록 허용하면서 정보 공유를 제한할 수 있습니다.
  • 직렬화 중인 항목을 다시 확인하고 일부 문제를 방지하는 데 도움이 되도록 변경을 수행할 수 있습니다.

그렇다면 serialization과 관련하여 고려해야 할 사항은 무엇인가요?

  • IntPtr은 플랫폼에 따라 길이가 4바이트 또는 8바이트입니다. 정보를 직렬화하면 플랫폼별 데이터를 출력에 기록하게 됩니다. 즉, 이 정보를 공유하려고 하면 문제가 발생할 수 있습니다.

이전 섹션에서 마샬링 및 오프셋에 대한 논의를 고려할 경우 serialization이 압축 정보를 해결하는 방법에 대한 질문 또는 두 가지를 생각해 볼 수 있습니다. 이진 serialization .NET의 경우 바이트 기반 읽기를 사용하고 데이터를 올바르게 처리하여 serialization 스트림에 대한 올바른 정렬되지 않은 액세스를 내부적으로 사용합니다.

앞에서 살펴본 것처럼 serialization을 사용하면 64비트로 마이그레이션하는 것을 막을 수 없습니다. XML serialization을 사용하는 경우 serialization 프로세스 중에 에서 네이티브 관리 형식으로 변환하여 플랫폼 간의 차이점으로부터 격리해야 합니다. 이진 serialization을 사용하면 더 풍부한 솔루션을 제공하지만 다양한 플랫폼이 직렬화된 정보를 공유하는 방법에 대한 결정을 내려야 하는 상황을 만듭니다.

요약

64비트로 마이그레이션이 진행 중이며 Microsoft는 32비트 관리형 애플리케이션에서 64비트로 최대한 간단하게 전환하기 위해 노력하고 있습니다.

그러나 64비트 환경에서 32비트 코드를 실행하고 마이그레이션하는 내용을 보지 않고 실행할 수 있다고 가정하는 것은 비현실적입니다.

앞에서 설명한 것처럼 100% 형식 안전 관리 코드가 있는 경우 실제로 64비트 플랫폼에 복사하여 64비트 CLR에서 성공적으로 실행할 수 있습니다.

그러나 관리되는 애플리케이션은 다음 중 어느 것 또는 전부와 관련될 가능성이 높습니다.

  • p/invoke를 통해 플랫폼 API 호출
  • COM 개체 호출
  • 안전하지 않은 코드 사용
  • 마샬링을 정보 공유 메커니즘으로 사용
  • 상태를 유지하는 방법으로 serialization 사용

이러한 작업 중 어떤 작업을 수행하든 숙제를 수행하고 코드가 수행하는 작업과 사용하는 종속성을 조사하는 것이 중요합니다. 이 숙제를 수행하면 다음 중 어느 것 또는 전부를 할 수 있는 선택을 살펴봐야 합니다.

  • 변경 없이 코드를 마이그레이션합니다.
  • 코드를 변경하여 64비트 포인터를 올바르게 처리합니다.
  • 다른 공급업체 등과 협력하여 64비트 버전의 제품을 제공합니다.
  • 마샬링 및/또는 serialization을 처리하기 위해 논리를 변경합니다.

관리 코드를 64비트로 마이그레이션하지 않기로 결정하는 경우가 있을 수 있습니다. 이 경우 Windows 로더가 시작 시 올바른 작업을 수행할 수 있도록 어셈블리를 표시하는 옵션이 있습니다. 다운스트림 종속성은 전체 애플리케이션에 직접적인 영향을 미칩니다.

Fxcop

또한 마이그레이션을 지원하는 데 사용할 수 있는 도구도 알고 있어야 합니다.

현재 Microsoft에는 .NET 관리 코드 어셈블리에서 Microsoft .NET Framework 디자인 지침을 준수하는지 확인하는 코드 분석 도구인 FxCop이라는 도구가 있습니다. 리플렉션, MSIL 구문 분석 및 호출 그래프 분석을 사용하여 명명 규칙, 라이브러리 디자인, 지역화, 보안 및 성능 영역에서 200개 이상의 결함을 어셈블리에 검사합니다. FxCop에는 GUI 및 명령줄 버전의 도구와 SDK가 모두 포함되어 사용자 고유의 규칙을 만듭니다. 자세한 내용은 FxCop 웹 사이트를 참조하세요. Microsoft는 마이그레이션 작업을 지원하는 정보를 제공하는 추가 FxCop 규칙을 개발하는 중입니다.

또한 런타임에 실행 중인 환경을 결정하는 데 도움이 되는 관리되는 라이브러리 함수도 있습니다.

  • System.IntPtr.Size - 32비트 또는 64비트 모드에서 실행 중인지 확인합니다.
  • System.Reflection.Module.GetPEKind - 프로그래밍 방식으로 .exe 또는 .dll 쿼리하여 특정 플랫폼 또는 WOW64에서만 실행되도록 했는지 확인합니다.

발생할 수 있는 모든 문제를 해결하기 위한 구체적인 절차 집합은 없습니다. 이 백서는 이러한 문제에 대한 인식을 높이고 가능한 대안을 제시하기 위한 것입니다.