C++로 만든 라이브러리를 C#에서 사용하기 2

최근에 잠깐 C++/CLI에 대해서 살펴볼 일이 있어서, 무려 4년적에 적었던 ‘C++로 만든 라이브러리를 C#에서 사용하기‘를 살펴봤다.

당시와는 달리, 검색하면 관련된 내용을 찾기가 수월해진 것 같다. C++/CLI가 사용되는 경우가 늘었나 라고 생각했지만, VS2010에서 CLR프로젝트의 자동완성 기능은 제대로 동작하지 않는다. 스마트 기기류에 대응하느라 바쁜 Microsoft라 여기까지 신경 쓸 여력은 없나 보다.

일전의 설명만으론 부족한 부분을 보충하기위해 테스트코드를 만들었다.

Visual Studio 2010을 사용했으며, 솔루션의 구성은 다음과 같다.

LibAdder C++ 라이브러리
TestLibAdder LibAdder 테스트
LibAdderCS LibAdder의 인터페이스를 ManagedCode로 감싸주는 C++/CLI 라이브러리
TestLibAdderCS LibAdderCS 테스트

즉, LibAdder(C++) -> LibAdderCS(C++/CLI) -> TestLibAdderCS(C#) 이다.

예제파일은 github 에서 받을 수 있다.

참조사이트1(Managed String을 Unmanaged String으로 변환하기)
참조사이트2(델리게이트를 함수포인터로 변환하기)

C++로 만든 라이브러리를 C#에서 사용하기

내가 만든 라이브러리가 제대로 동작하는지, 그리고, 이 라이브러리는 사용하는데 있어서 문제가 없는지를 확인하기 위해서, 이를 사용한 테스트 클라이언트를 만들기로 했다. 단, 여기에 C#을 사용하고 싶었다. C#에서 간단히 처리할 수 있는 문제를 가지고 MFC와 씨름하면서 시간을 허비하기는 싫었기 때문에.

몇 가지 방법 중에 내가 선택한 방법은 C++/CLI로 C++라이브러리를 감싼 다음 이를 C#에서 사용하는 것이다. 이 방법을 선택한 이유는 순전히, 시도해본 다른 방법이 모두 실패했기 때문이다. 방법이 없다 라기보다는 내가 잘 모르고 익숙하지 않아서 그렇다. 그렇다고 C++/CLI를 사용하는데 익숙한가 하면, 그것도 아니다. 난 C++/CLI는 정말인지 필요 없다고 생각한다. 딱 한가지만 빼고. 바로 C++라이브러리를 감싸는 것. 하여튼, 난 C++/CLI를 하나도 몰랐다. 지금도 잘 모르는 상태이기 때문에, 아래에 내가 설명하는 내용에는 다소의 오류가 있을 수도 있음을 알아주기 바란다.

C++/CLI는 C++의 문법을 이해한다. 이 말은 C++/CLI에서 Unmanaged Code를 사용할 수 있다는 의미이다. 따라서 C#이 C++/CLI의 문법을 100% 이해할 수는 없다. 기본적으로 C#은 Managed Code이기 때문이다. 따라서 C++/CLI로 C++라이브러리를 잘만 감싸면, C#에서 아무런 수고 없이 이를 사용할 수 있다. 참고로 내가 사용한 Visual Studio의 버전은 2005이다.

Visual C++의 CLR타입 Class Library 프로젝트를 생성하고, C++로 만든 라이브러리를 포함시킨다. 사실, 이것은 Unmanaged C++을 사용할 때의 라이브러리 사용과정과 전혀 다르지 않다. 이제부터 할 일은 C++라이브러리 중에서 노출할 필요가 있는 인터페이스들을 Managed C++로 작성해주기만 하면 된다. 이 글의 주요 주제 되겠다.

C++의 데이터타입은 C++/CLI에서 아래의 표와 같이 바꾼다.

Unmanaged Data Type

Managed Data Type

Ref.

class

ref class

struct

value struct

enum

enum class

std::string

String^

PtrToStringAnsi()

std::map

Dictionary^

std::vector

List^

std::list

List^

function pointer

delegate

반드시 이와 같이 할 필요는 없다. std::vector를 Array로 바꾸어도 전혀 상관없다. 목표는 C++에서 데이터를 다루듯이 C#에서 이를 다룰 수 있도록 하는 것이다.

C++/CLI의 struct는 C++과는 달리, value-type으로만 사용된다. reference-type으로 사용하기위해서는 class를 사용해야 한다.

ref class로 선언되지 않은 클래스는 GC(Garbage Collection)의 관리를 받지 않으며, Unmanaged Code이다.

C++/CLI에서의 ^는 C++의 *에 해당한다. 따라서 ^형으로 선언된 변수는 gcnew키워드로 생성해야 한다. gcnew로 할당된 메모리는 GC가 자동으로 해제하므로, 그에 대해 신경 쓸 필요 없다.

Unmanaged String은 Managed String에 직접 대입될 수 없으므로, PtrToStringAnsi()함수로 사전에 변환되어야 한다. 다음은 Managed String과 Unmanaged String사이에 사용되는 매크로이다.

C++라이브러리에서 사용되는 함수 포인터는 C++/CLI에서 적절한 델리게이트로 바뀌어야한다. GetFunctionPointerForDelegate()함수를 사용할 수도 있지만, 나의 경우는 좀 더 간단하게, Unmanaged Code로 작성한 콜백함수에서 Managed Code로 작성한 콜백함수(델리게이트)를 호출했다. 가장 까다롭게 생각했고, 실제로 시간도 많이 잡아먹은 부분이 콜백함수의 처리였지만, 의외로 싱겁게 끝나버렸다.

이렇게 만들어진 라이브러리는 C#에서 바로 사용할 수 있다. 모든 인터페이스를 Managed Code로 재 작성 해야 한다는 번거로움이 있긴 하지만, 적어도 내 생각에는 이것이 가장 무난하고 또, 만만한 방법이라고 생각된다.


2012.01.31 추가.

위의 매크로는 VS2008에서는 동작하지 않는다. 참조


2012.12.04 추가.

예제