C++ 개발자의 .NET 개발 환경으로의 이주
새로 옮긴 개발팀에서는 CLI/C++을 사용한다. 이전 회사에서 .NET 개발 환경을 검토했던 경험을 얘기하자면, MFC를 사용하는 어플리케이션을 개발하고 있었기 때문에, 새로운 툴에 대한 적응도 문제도 있었고, .NET 환경으로 이전하기 위한 시간적 여유가 없었기 때문에 개인적으로 일부 컴포넌트에 대해서만 .NET 개발 환경에 대해서 검토를 했었다.
하지만, 어플리케이션이 여전히 Native Binary로 남아있는 상황과 개발 팀이 .NET 환경으로 이주하지 않는 상태에서 .NET 컴포넌트가 섞여 있는 형태의 개발은 효율이 매우 안 좋아서 결국 1년 동안 개발된 .NET 컴포넌트는 모두 제거하였다.
이번 개발 팀에서도 C++ 개발자도 .NET 프레임 웍이 제공하는 라이브러리를 사용하는 것이 이득이라는 명분으로 CLI/C++를 사용하고 있었지만 C++ 코드들은 .NET 규격을 전혀 고려하지 않아서, 코드의 재사용은 템플릿에 의존하고 있었고, 데이터는 포인터로 다루고 있다.
그런데, 포인터와 같은 타입은 .NET 컴포넌트로 노출하기가 매우 꺼려진다. 그래서, 보통 데이터를 포인터로 표현하기 보다는 바이트 스트림이나 포인터의 개념을 감추고 객체를 새로 만들어 노출하는 것이 일반적이다. 하지만, C++ 코드로 이렇게 표현된 데이터를 다루는 것은 매우 불편하다.
.NET의 포인터 타입을 이용하면 C++ 및 .NET 컴포넌트가 동일한 인터페이스를 사용하면서도 C++ 코드들은 여전히 포인터를 이용해서 데이터를 다룰 수 있다. 다만, C# 컴포넌트는 프로젝트에 /safe 옵션을 추가하고 빌드해야 하며, safe { } 블록에서만 포인터를 사용할 수 있다.
이번 글에서는 reflection을 사용하여 포인터 타입을 반환하는 멤버 함수를 호 출한 뒤 반환된 포인터 타입을 사용하는 내용을 다룸으로서 .NET에서 포인터를 다루는 방법과 이 과정에서 CLI/C++가 어떤 역할을 하는지 볼 것이다.
.NET에서 Pointer의 Boxing/UnBoxing
C#은 safe 블록에서 C++와 동일하게 * 기호를 사용하여 바로 포인터 타입을 정의하고 객체를 참조할 수 있다. 그러기 위해서는 우선 C#에서 사용할 수 있는 포인터 타입이 필요하다. CLI/C++는 managed 클래스 뿐만 아니라 native C++ 클래스 역시 .NET 언어에서 참조할 수 있는 객체 타입을 생성한다.
.NET에서 포인터 값은 다른 primitive 타입의 값들과 마찬가지로 boxing/unboxing을 거친다. 포인터 값은 System.Reflection.Pointer 클래스로 boxing 된다. CLI/C++에서는 포인터 값의 boxing을 알아서 해주기 때문에 포인터를 반환하는 경우 멤버 함수의 경우 직접 반환 타입으로 포인터를 사용할 수 있으며, 반환하는 값도 포인터의 값을 그대로 노출할 수 있다.
단지, 프로그래머는 필요한 경우 unboxing만 처리하면 된다. 멤버 함수가 System.Reflection.Pointer 클래스가 아닌 포인터 타입으로 그대로 노출하기 때문에, C++ 코드 역시 기존의 방식대로 함수를 호출하고 포인터 값을 참조하면 된다.
다음의 코드는 포인터 타입을 반환하는 프로퍼티의 코드이다.
property Photo::TiffRaster *OrthoImage {
Photo::TiffRaster *get() {
return _orthoImage ;
}
}
OrthoImage 프로퍼티는 Photo::TiffRaster의 포인터 타입을 반환하는데, CLI/C++에 의해서 Photo::TiffRaster의 .NET 타입이 정의되고, _orthoImage의 포인터 값은 System.Reflection.Pointer 클래스로 boxing 된다.
.NET의 C++ Pointer 타입
리플렉션을 사용하는 경우 C++ 코드라도 C#과 동일하게 포인터 값을 unboxing해서 사용해야 한다.
cli::aray^ params
= gcnew cli::array(0) {} ;
Object ^retValue
= objType->InvokeMember(prop->Name,
BindingFlags::GetProperty,
nullptr,
jobObj,
params) ;
Photo::TiffRaster *tiffInfo = (Photo::TiffRaster *)
System::Reflection::Pointer::Unbox(retValue) ;
위 코드는 리플렉션을 사용하여 프로퍼티를 호출한다.
포인터 값을 직접 boxing 하는 경우 System.Reflection.Pointer.Box 함수에 포인터 타입의 System.Type 객체를 넘겨주어야 한다.
그러나, Photo::TiffRaster *::typeid와 같이 타입의 포인터 타입을 정의하지 않은 typeid는 컴파일 에러가 나기 때문에, typedef를 사용하여 typedef Photo::TiffRaster *TiffRasterPtr ; 과 같이 포인터 타입을 정의한 뒤 TiffRasterPtr::typeid와 같이 포인터 타입의 System.Type 객체를 얻어야 한다.