C#은 관리 코드와 네이티브(비관리) 코드를 손쉽게 연결할 수 있는 여러 가지 메커니즘을 제공합니다. 여기서는 가장 많이 사용되는 P/Invoke와 C++/CLI, 그리고 최신 .NET 7+의 NativeAOT 연동 방법을 간략히 살펴보겠습니다.
1. P/Invoke (Platform Invocation Services)
P/Invoke는 Windows API, C 라이브러리 등 기존 네이티브 DLL 함수를 직접 호출할 때 사용합니다. DllImport 특성을 이용해 함수 시그니처를 선언하면 됩니다.
using System;
using System.Runtime.InteropServices;
class NativeMethods
{
// winmm.dll의 Beep 함수 호출 예시
[DllImport("winmm.dll", SetLastError = true)]
public static extern bool Beep(uint dwFreq, uint dwDuration);
// 사용자 정의 C 라이브러리 (myutils.dll) 함수
[DllImport("myutils.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);
}
class Program
{
static void Main()
{
// Beep 실행
NativeMethods.Beep(750, 300);
// Add 함수 호출
int result = NativeMethods.Add(5, 7);
Console.WriteLine($"Add result: {result}");
}
}
주요 포인트
DllImport에 지정하는 문자열은 DLL 파일명(확장자 포함)입니다.- 매개변수와 반환형은 정확히 매핑해야 합니다. 잘못 매핑하면 런타임 오류나 메모리 손상이 발생할 수 있습니다.
- 필요에 따라
CharSet,CallingConvention,SetLastError등을 설정합니다.
2. C++/CLI를 활용한 래퍼 클래스
P/Invoke가 간단한 함수 호출에 적합하지만, 복잡한 구조체·클래스·콜백을 다루려면 C++/CLI가 유리합니다. C++/CLI 프로젝트에서 네이티브 코드를 래핑하고, 이를 C#에서 관리 코드처럼 사용할 수 있습니다.
// C++/CLI 프로젝트 (MyNativeWrapper)
#pragma once
// 네이티브 헤더 포함
#include "NativeLib.h"
using namespace System;
namespace MyNativeWrapper {
public ref class Calculator
{
private:
::NativeCalculator* nativePtr; // 네이티브 객체 포인터
public:
Calculator() { nativePtr = new ::NativeCalculator(); }
~Calculator() { this->!Calculator(); }
!Calculator() { delete nativePtr; }
int Add(int a, int b) { return nativePtr->Add(a, b); }
};
}
위와 같이 만든 어셈블리를 C# 프로젝트에 참조하면 다음과 같이 사용할 수 있습니다.
using MyNativeWrapper;
class Program
{
static void Main()
{
var calc = new Calculator();
int sum = calc.Add(10, 15);
Console.WriteLine($"Sum = {sum}");
}
}
3. .NET 7+ NativeAOT와 함수 포인터
.NET 7부터는 NativeAOT(전역 네이티브 실행 파일)와 UnmanagedCallersOnly 특성을 이용해 C# 메서드를 네이티브에서 직접 호출할 수 있습니다. 이는 성능이 중요한 게임 엔진이나 임베디드 환경에서 유용합니다.
using System;
using System.Runtime.InteropServices;
class NativeExport
{
// 네이티브에서 호출 가능한 메서드
[UnmanagedCallersOnly(EntryPoint = "Multiply")]
public static int Multiply(int a, int b) => a * b;
}
// 네이티브 C 코드 (call_csharp.c)
/*
extern int Multiply(int a, int b);
int main()
{
int r = Multiply(6, 7);
printf("Result: %d\n", r);
return 0;
}
*/
빌드 옵션 예시 (dotnet CLI)
dotnet publish -c Release -r win-x64 -p:PublishAot=true
생성된 exe는 네이티브 DLL처럼 동작하며, C/C++ 코드에서 직접 함수를 호출할 수 있습니다.
4. 실전 팁
- 구조체를 전달할 때는
StructLayout(LayoutKind.Sequential)를 명시하고, 필드 정렬을 정확히 맞춥니다. - 콜백이 필요하면
delegate를 정의하고Marshal.GetFunctionPointerForDelegate로 함수 포인터를 얻어 네이티브에 전달합니다. - 디버깅이 어려운 경우
Dependency Walker혹은dumpbin /exports로 DLL 내 함수를 확인하고,SetLastError = true와Marshal.GetLastWin32Error()로 오류 원인을 파악합니다.
위 내용만 숙지해도 C#과 기존 네이티브 라이브러리 간의 연동을 손쉽게 구현할 수 있습니다. 상황에 맞는 방법을 선택해 효율적인 인터옵을 구현해 보세요.
'C#' 카테고리의 다른 글
| C#에서 OpenCV 사용하기 (0) | 2026.09.01 |
|---|---|
| C#으로 이미지 처리와 컴퓨터 비전 구현 (0) | 2026.09.01 |
| gRPC를 활용한 서비스 간 통신과 인터페이스 정의 (0) | 2026.08.31 |
| SignalR를 사용한 실시간 웹 애플리케이션 개발 (0) | 2026.08.30 |
| C#에서 WebSocket으로 실시간 통신하기 (0) | 2026.08.30 |