2020년 7월 1일 수요일

C++/CIL 로 C++ C# 에서 쓰기

참고할만한 자료

https://vsts2010.tistory.com/category/C++/CLI


CLR 이란

 Common Language Runtime 의 줄임말이다. 

 이는 닷넷 생태계의 언어들의 호환을 위해 주로 사용된다. 

 예를들어 C# 은 갈비지 컬렉터가 있고 C++ 은 그런거 없다. 이 둘을 연동해서 쓰고 싶은데 이것들이 바로 기계어로 컴파일되면 연동하는게 쉽지 않다. 그래서 소스코드와 네이티브 코드 간의 완충재로서 ClL 로 컴파일을 하고 자바처럼 가상머신 비슷하게 런타임 때 JIT 컴파일러를 통해 기계어로 번역되어 실행된다. 

 CIL 로 컴파일 가능한 언어를 Managed Code, 그냥 C++ 처럼 아닌 언어를 Native Code 라고 부르기도 한다.  CIL 은 Common Interface Language 의 줄임말로 MSlL이라고도 불렸다. 닷넷 생태계의 언어들이 이를 지원한다. 

 C++ 의 경우 설치세부정보에서 C++/CIL 지원을 설치하면 관련 프로젝트로 세팅이 끝나 있다. 이때 .Net Core 랑 .Net Framework 를 구분해서 사용하자. 전자는 리눅스 등과의 호환성을 후자는 윈도우에서만 되지만 여러기능을 같이 쓸 수 있다.



C# 과 C++/CLR 연동

참고할 자료

https://andromedarabbit.net/cplusplus_cli_lecture_2009_04/


1.

 생각보다 쉽다. 사용하는 .net framework 를 통일해서 프로젝트를 각각 생성한다. 예를들어  C++/CIL .net core 프로젝트랑 C# / .net core 프로젝트를 만들자. 그리고 .net Core 의 경우 C# 프로젝트에서 프로젝트 통채로 참조하고 또한 빌드된 dll 도 참조하면 되고, .net Framework 의 경우 프로젝트만 참조하자. 다시말해 Add Reference 로 처리한다.

 주의해야 할 점은 C++/CIL 에선 클래스 등이 생성될 경우 특히 소멸자에서 에로사항이 많이 생긴다는 것이다. 그래서 C++/CIL 에서 외부 라이브러리를 가져와 래퍼로서 사용하는 것이 대부분이다.


2. 

 여기선 삽질한 내용은 산발적으로 메모한다.


 다음은 C# 프로젝트의 속성에서 처리해야할 것들이다..

 1. 플래폼 타켓을 CIL 프로젝트랑 통일하자. 그럼 런타임 오류 하나는 해결한다. (x64, x32)

 2. unsafe mode 를 켜자. void* 같은 타입을 C# 에서 쓰려면 unsafe { your code } 이렇게 써야하고, 저 체크가 필요하기 때문이다. 

 3. 만약 WinApi 를 C++/CIL 에서 사용하고 싶으면 CIL 프로젝트에 사용하는 함수의 라이브러리를 추가하자.

 #pragma comment(lib, "User32.lib")

 #pragma comment(lib, "Gdi32.lib")

 처럼 말이다.


Marshal

참고할 자료

https://six605.tistory.com/426


 마이크로소프트는 Marshal 이라는 클래스로 C# 의 Object 와 C++ 의 잘 쓰이는 클래스를 변환해주는 기능을 구현해 놓았다. C++ -> C# 에서 쓰이는 예제와 C# -> C++ 로 쓰이는 예제를 살펴보자.

#include <msclr/marshal_cppstd.h>

void FrameworkWrapper::Windows_Wrapped::Create(void* hInstance, int width, int height, System::String^ name)
{
    std::wstring nativeMsg = msclr::interop::marshal_as<std::wstring>(name);
    m_windows->Create(static_cast<HINSTANCE>(hInstance), width, height, nativeMsg.c_str());
}

System::String^ 인 것에 주목하자.

managed instance 에서 쓰는 ^ 와 % 는 unmanaged instance 의 * 과 & 와 대충 대응된다.

 또한 hInstance 가 void* 인 것도 주목하자. 타입변환이 맘대로 안되면 전통적인 void* 를 쓰는 것도 좋은 선택이다.


unsafe
{
    HwndSource hwnd = (HwndSource)HwndSource.FromVisual(this);
    IntPtr hinstance = Marshal.GetHINSTANCE(typeof(App).Module);

    Windows_Wrapped windows = new Windows_Wrapped();
    windows.Create(hinstance.ToPointer(), 500, 500, "asf");
    windows.Show();
    while (windows.Update())
    {
    }

    windows.Destory();
}

 위는 winApi 를 띄우는 클래스를 가지고 C# 에서 사용해본 코드다. 

 unsafe 를 사용한 것과, hwnd, hinstance 를 주목해서 보자.


marshal 을 명시적으로 쓰는것인데, 자주 쓰지 않으면 상관없지만 비용이 꽤 쎄다고 한다.

또한 C# 은 기본적으로 Unicode 쓰므로 wchar_t 쪽으로 바꾸는 것이 빠르다고 한다.


Marshal Context

marshal_context ctx;
con_char = ctx.marshal_as<const char*>(cs_string);
con_wchar_t = ctx.marshal_as<const wchar_t*>(cs_string);

marshal 을 쓸 때 marshal_context 를 쓰면 ctx 가 유효하다면 자동으로 메모리확보를 해준다.

위의 예에선 기존 c++ 에선 char, wchar_t 를 위한 공간확보가 먼저 선행되어야하는데, 이 일이 ctx 를 통해 자동으로 진행된다.


댓글 없음:

댓글 쓰기

List