본문 바로가기
C#/Interop

P/Invoke Marshal: C#에서 C++(native code) 사용하기

by PlaneK 2021. 8. 2.

Native Code 란?

Native Code는 다른 말로 Unmanaged Language이며, C와 C++가 대표적이다.
Native Code는 컴파일 시 어셈블리어(≒기계어)로 바로 번역된다.

이에 반해 Managed Language는 중간언어(IL)로 우선 컴파일된다.
C#과 JAVA가 대표적이다. 중간언어는 런타임 시 JIT컴파일에 의해 기계어로 번역된다.

 

P/Invoke Marshal ?

보통 어플리케이션은 생산성이 높은 Managed Language로 작성되는데,
특정 영역의 API는 Native Code외 사용할 수 없기 때문에 Native Code를 라이브러리로 사용한다.

✏️ Native Code를 사용하는 이유
Native Code는 직접 하드웨어(graphic, cam 등)를 제어할 수 있는 API를 사용할 수 있다.
특정 분야(멀티미디어 처리 등)의 이전 코드는 Native Code로 작성됐다. 재사용하려면 Interop가 필요.
메모리를 직접 제어할 수 있기 때문에 프로그래머 능력에 따라 엡 성능을 향상시킬 수 있다.

Native Code를 사용하는 이유

 

이런 방식을 상호운용성(Interoperabilty)이라 하고, (줄여서 interop)
이를 가능하게 하는 API, 기술명이 P/Invoke Marshal (Platform Invokation Services)인 것이다.

✏️ 더 알아보기

interop 오버헤드 최적화 하기

📖 레퍼런스
포인터를 포함한 C++ 시그니쳐에 대응되는 C# 코드를 맞추기가 쉽지 않은데, 이는 pinvoke.net에서 Win32 API를 검색해서 대응되는 C# 코드를 얻을 수 있다.

Managed Language, C#에서 DLL을 통해 Native Code, C++을 사용해보자.

 

P/Invoke Marshal: C#에서 C++(native code) 사용하기

사용되는 키워드를 살펴보자.

Native Code 측

키워드 설명
extern "C" { } ㆍ컴파일 시 C 스타일로 심볼을 표현함.
ㆍ시그니쳐로부터 네임 맹글링 배제
cf. 네임 맹글링이란 일정한 규칙에 따라 함수명 등을 변경하는 것.
__declspec(dllexport) ㆍDLL로 내보내질 대상으로 지정함(export 테이블에 저장)
ㆍ클래스, 함수, 변수에 명시할 수 있다.

e.g. #define DllExport __declspec( dllexport ) 매크로로 읽기쉽게 만듬
__stdcall ㆍ표준 함수 호출 규칙
ㆍDLL 라이브러리에서 사용된다.
ㆍ스택을 정리하는 주체 = 함수 호출자 x 피호출된 함수 o

cf. 가변인자를 사용하는 언어에서는 __cdecl 이 사용된다.
해당 규칙은 함수 호출자가 스택을 정리한다.
가변인자를 사용하면 함수 호출자만이 스택 크기를 알 수 있기 때문.
cf. __stdcall vs __cdecl

Managed Code 측

키워드 설명
[DllImport("<DLL name>")] ㆍSystem.Runtime.InteropServices의 Attribute
ㆍDLL에서 함수의 코드를 가져온다.
ㆍ반드시 static extern을 명시해야 한다.

e.g.
[DllImport("mylib")]
public static extern void HelloWorld();

 

// NativeCode,  myapi.h 파일

#include <iostream>

extern "C"
{
	__declspec(dllexport) void __stdcall HelloWorld();
	__declspec(dllexport) int __stdcall Add(int a, int b);
}

myapi.h

// NativeCode, myapi.cpp 파일

#include"myapi.h"

void __stdcall HelloWorld()
{
	std::cout << "Hello world !";
}
int __stdcall Add(int a, int b)
{
	return a + b;
}

myapi.cpp

// ManagedLanguage,  Program.cs 파일

using System.Runtime.InteropServices;

namespace ConsoleApp
{
    class Program
    {
        [DllImport("mylib.dll")]
        public static extern void HelloWorld();
        [DllImport("mylib.dll")]
        public static extern int Add(int a, int b);

        static void Main(string[] args)
        {
            HelloWorld();
            System.Console.WriteLine(Add(1, 2));
        }
    }
}

Program.cs

 

프로젝트 생성 및 설정

C++ 프로젝트를 생성하자.

생성된 C++ 프로젝트 속성을 열고 구성(Configuration) 플랫폼(Platform)을 확인한다.
구성 형식(Configuration Type) 동적 라이브러리(.dll)로 바꾼다.

솔루션에 C# 의 콘솔 어플리케이션 프로젝트를 추가하자.

빌드 종속성에 DLL 프로젝트를 체크한다.

빌드 및 실행

빌드하면 각 프로젝트에서 DLL파일과 exe파일이 출력디렉터리에 생성된다.

dll파일을 콘솔앱 디렉토리로 옮겨주고 실행시키면,

dll 파일의 함수를 실행하는 것을 확인할 수 있다.

 

✔️ DLL을 못찾을 시 체크
1. [DllImport]의 대상 함수명과 실제 함수명이 일치해야함.
     cf. [DllImport]EntryPoint로 지정해 줄 수도 있음.
2. 플랫폼(x64, x84)이 서로 일치해야함.
3. C#의 출력 디렉터리( 속성 > 빌드 > 출력 )에 dll파일이 존재해야함.
    cf. .csproj파일에서 출력 경로 조정 속성 변경 
AppendTargetFrameworkToOutputPath 참고.

 

 

댓글