0Pricing
C# Academy · 강의

P/Invoke 기초

DllImport로 네이티브 함수를 선언하고 호출하며 기본 형식, 문자열, 구조체의 마샬링을 이해합니다.

P/Invoke 기초은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

P/Invoke란 무엇입니까

플랫폼 호출 서비스(P/Invoke)를 사용하면 C#에서 네이티브 공유 라이브러리(Windows의 .dll, Linux의 .so, macOS의 .dylib)에 있는 함수를 호출할 수 있습니다. 이는 .NET에서 Win32 API 또는 모든 C-ABI 라이브러리를 호출하는 표준 메커니즘입니다.

첫 P/Invoke 호출

네이티브 함수를 [DllImport]로 선언하고 라이브러리 이름을 지정하십시오. CLR이 라이브러리 검색 및 로드와 인수 마샬링을 처리합니다.

using System.Runtime.InteropServices;

// Calling MessageBoxW from user32.dll (Windows)
internal static partial class NativeMethods
{
    [DllImport("user32.dll",
        EntryPoint = "MessageBoxW",
        CharSet = CharSet.Unicode,
        SetLastError = true)]
    internal static extern int MessageBox(
        IntPtr hwnd,
        string text,
        string caption,
        uint type);
}

// Call it:
NativeMethods.MessageBox(IntPtr.Zero, "Hello!", "P/Invoke", 0);

// Windows-only — wrap in RuntimeInformation.IsOSPlatform check
// for cross-platform code

사용자 지정 네이티브 라이브러리 호출

P/Invoke는 운영 체제 API뿐 아니라 C로 내보낸 모든 함수에서 작동합니다. 네이티브 라이브러리를 빌드하고, C 연결을 사용하여 함수를 내보낸 다음 C#에서 호출하십시오.

// mymath.h / mymath.c:
// extern "C" double AddNumbers(double a, double b) { return a + b; }
// Compile: gcc -shared -o libmymath.so mymath.c

// C# declaration:
[DllImport("mymath",              // libmymath.so / mymath.dll
    EntryPoint = "AddNumbers",
    CallingConvention = CallingConvention.Cdecl)]
private static extern double AddNumbers(double a, double b);

// Call:
double result = AddNumbers(3.14, 2.72); // 5.86

// .NET resolves the library via:
// 1. Absolute path if provided
// 2. App directory
// 3. OS library path (PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH)

기본 형식 마샬링

CLR은 관리 코드 표현과 네이티브 표현 사이에서 대부분의 기본 형식을 자동으로 마샬링합니다. 매핑을 알면 미묘한 버그를 방지할 수 있습니다.

// C Type       → C# Type
// int (32-bit)  → int  or  System.Int32
// long (64-bit) → long or  System.Int64
// float         → float
// double        → double
// bool          → [MarshalAs(UnmanagedType.Bool)] bool
// char*  (ANSI) → string  (CharSet.Ansi)
// wchar_t*      → string  (CharSet.Unicode)
// void*         → IntPtr
// size_t        → UIntPtr

// Example with explicit marshalling:
[DllImport("libc", EntryPoint = "strlen", CharSet = CharSet.Ansi)]
private static extern UIntPtr StrLen(
    [MarshalAs(UnmanagedType.LPStr)] string s);

int len = (int)StrLen("hello"); // 5

구조체를 네이티브 코드에 전달

[StructLayout(LayoutKind.Sequential)]을 사용하여 구조체가 네이티브 코드가 예상하는 대로 메모리에 정확히 배치되도록 하십시오.

// C struct:
// struct Point { int x; int y; };
// void DrawPoint(struct Point p);

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport("graphics", EntryPoint = "DrawPoint",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void DrawPoint(Point p);

// Pass by value:
DrawPoint(new Point { X = 10, Y = 20 });

// Pass by pointer (ref or out):
[DllImport("graphics", EntryPoint = "GetCenter",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void GetCenter(out Point center);
GetCenter(out var pt);

오류 처리: GetLastWin32Error

Windows API 함수는 GetLastError()를 통해 오류를 알립니다. [DllImport]에서 SetLastError = true를 설정하고 호출 후 Marshal.GetLastWin32Error()를 호출하십시오.

[DllImport("kernel32.dll",
    EntryPoint = "CreateFileW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern IntPtr CreateFile(
    string fileName,
    uint desiredAccess,
    uint shareMode,
    IntPtr securityAttributes,
    uint creationDisposition,
    uint flagsAndAttributes,
    IntPtr templateFile);

const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING  = 3;

var handle = CreateFile("test.txt", GENERIC_READ, 0,
    IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);

if (handle == (IntPtr)(-1))
{
    int error = Marshal.GetLastWin32Error();
    throw new System.ComponentModel.Win32Exception(error);
}

문자열 및 버퍼 마샬링

문자열 마샬링에는 인코딩과 소유권에 세심한 주의가 필요합니다. 출력 버퍼에는 StringBuilder를 사용하고 인코딩을 지정하려면 MarshalAs 특성을 사용하십시오.

// Read into a buffer:
[DllImport("kernel32.dll",
    EntryPoint = "GetComputerNameW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern bool GetComputerName(
    System.Text.StringBuilder lpBuffer,
    ref uint nSize);

uint size = 256;
var buffer = new System.Text.StringBuilder((int)size);
if (GetComputerName(buffer, ref size))
    Console.WriteLine(buffer.ToString());

// Return a string owned by native code (don't free it):
[DllImport("mylib", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string GetVersion();

함수 포인터 및 콜백

관리 delegate를 C 함수 포인터로 전달하십시오. CLR은 중계 함수를 생성하지만 delegate를 계속 유지해야 합니다(참조를 보유해야 함). 그렇지 않으면 가비지 컬렉터가 이를 수집하여 네이티브 코드가 충돌합니다.

// C callback signature: typedef int (*Comparer)(const void*, const void*);
// void qsort(void* base, size_t nitems, size_t size, Comparer compar);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int CompareCallback(IntPtr a, IntPtr b);

[DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
private static extern void QSort(
    int[] data, UIntPtr count, UIntPtr size, CompareCallback compare);

// Keep the delegate ALIVE for the duration of the call:
private static readonly CompareCallback _compare =
    (a, b) => Marshal.ReadInt32(a).CompareTo(Marshal.ReadInt32(b));

var data = new[] { 5, 2, 8, 1, 3 };
QSort(data, (UIntPtr)data.Length, (UIntPtr)sizeof(int), _compare);

NativeLibrary API

NativeLibrary 클래스는 명시적 라이브러리 로드, 함수 포인터 확인 및 플랫폼 간 경로 사용자 지정을 제공합니다. 이는 암묵적인 라이브러리 이름 확인을 대신하는 현대적인 방법입니다.

using System.Runtime.InteropServices;

// Explicit load:
var handle = NativeLibrary.Load("/usr/lib/libssl.so.3");

// Resolve a function pointer:
var addPtr = NativeLibrary.GetExport(handle, "AddNumbers");
var addFn = Marshal.GetDelegateForFunctionPointer<Func<double,double,double>>(addPtr);
double result = addFn(1.0, 2.0);

// Unload:
NativeLibrary.Free(handle);

// Custom resolver (called when DllImport can't find a library):
NativeLibrary.SetDllImportResolver(typeof(MyNative).Assembly,
    (libName, assembly, searchPath) =>
    {
        if (libName == "mymath")
            return NativeLibrary.Load("/opt/mymath/libmymath.so");
        return IntPtr.Zero;
    });

실전: OpenSSL 호출

P/Invoke를 사용하여 C#에서 OpenSSL의 SHA-256 다이제스트 함수를 호출하는 실제 사례입니다.

internal static class OpenSslInterop
{
    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_MD_CTX_new();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern void EVP_MD_CTX_free(IntPtr ctx);

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_sha256();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern int EVP_DigestInit_ex(IntPtr ctx, IntPtr type, IntPtr engine);

    // In practice, use System.Security.Cryptography.SHA256 instead:
    // var hash = SHA256.HashData(data);
    // P/Invoke to OpenSSL is only needed when you require
    // non-managed cryptographic operations or specific OpenSSL features
}

빠른 확인

delegate를 네이티브 콜백으로 전달할 때 계속 유지해야 하는 이유는 무엇입니까?

복습: P/Invoke 기초

핵심 요점:

  • P/Invoke: [DllImport] 선언을 통해 네이티브 C-ABI 함수를 호출합니다
  • CLR은 기본 형식을 자동으로 마샬링하며, 문자열과 사용자 지정 형식에는 [MarshalAs]를 지정합니다
  • [StructLayout(LayoutKind.Sequential)]: 구조체의 메모리 레이아웃이 네이티브 측의 예상과 일치하도록 보장합니다
  • SetLastError = true로 설정하고 Marshal.GetLastWin32Error()를 호출하여 Win32 오류를 처리합니다
  • 네이티브 콜백으로 전달하는 델리게이트 참조를 계속 유지합니다
  • NativeLibrary: 플랫폼 간 경로를 제어하기 위한 명시적 로드, 확인 및 사용자 지정 리졸버를 제공합니다

자주 묻는 질문

“P/Invoke 기초” 강의는 무료인가요?

네 — “P/Invoke 기초” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“P/Invoke 기초”에서 뭘 배우나요?

DllImport로 네이티브 함수를 선언하고 호출하며 기본 형식, 문자열, 구조체의 마샬링을 이해합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C# Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“P/Invoke 기초” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. P/Invoke 기초
  2. LibraryImport와 소스 생성 P/Invoke
  3. 안전하지 않은 코드, 포인터 및 고정 버퍼
  4. COM 상호 운용 및 런타임 호출 가능 래퍼
← C# Academy(으)로 돌아가기