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)인 것이다.
📖 레퍼런스
포인터를 포함한 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 참고.
댓글