C++/CLI 네이티브 및 매니지드 코드 상호 운용성 핵심 기법

네이티브 클래스 내 매니지드 객체 참조 관리

네이티브 C++ 클래스에서 .NET 매니지드 객체를 멤버 변수로 유지해야 할 때 gcroot 템플릿을 사용합니다. 이를 통해 가비지 컬렉터(GC)가 객체를 수거하지 못하도록 방지하면서도 네이티브 포인터처럼 다룰 수 있습니다.

#include <vcclr.h>

// 매니지드 클래스 정의
public ref class ManagedWorker {
public:
    void ExecuteTask() { 
        // 매니지드 환경에서 작업 수행
    }
};

// 네이티브 클래스 정의
class NativeController {
private:
    gcroot<ManagedWorker^> m_workerRef;
public:
    NativeController(gcroot<ManagedWorker^> worker) : m_workerRef(worker) {}
    
    void Run() {
        // 매니지드 객체의 메서드 호출
        m_workerRef->ExecuteTask();
    }
};

// 사용 예시
void InitializeSystem() {
    ManagedWorker^ workerInstance = gcnew ManagedWorker();
    gcroot<ManagedWorker^> wrappedWorker = workerInstance;

    // 네이티브 객체에 매니지드 참조 전달
    NativeController* controller = new NativeController(wrappedWorker);
    controller->Run();

    delete controller;
}

pin_ptr를 활용한 메모리 고정 및 포인터 전달

pin_ptr (고정 포인터)는 가비지 컬렉션이 발생하더라도 매니지드 힙에 있는 객체의 메모리 주소가 이동하지 않도록 고정(Pinning)하는 역할을 합니다. 매니지드 객체의 주소를 네이티브 함수에 전달할 때, GC에 의한 메모리 재배치로 인해 잘못된 메모리를 접근하는 것을 방지하는 데 필수적입니다.

pin_ptr는 지역 변수로만 사용 가능하며, 다음과 같은 용도로는 사용할 수 없습니다:

  • 함수의 매개변수
  • 함수의 반환 타입
  • 클래스의 멤버 변수
  • 제네릭 타입의 제약 조건

배열 요소에 대한 포인터 고정

// pin_array_example.cpp
// compile with: /clr
using namespace System;

#pragma unmanaged
void FillNativeBuffer(double* buffer, int length) {
    for (int i = 0; i < length; ++i) {
        buffer[i] = i * 1.5;
    }
}
#pragma managed

public ref class DataAnalyzer {
private:
    array<double>^ m_values;

public:
    DataAnalyzer(int size) {
        m_values = gcnew array<double>(size);
    }

    void PopulateData() {
        // 배열의 첫 번째 요소를 고정
        pin_ptr<double> pinnedData = &m_values[0];
        double* nativePtr = pinnedData;
        FillNativeBuffer(nativePtr, m_values->Length);
    }

    double CalculateAverage() {
        double sum = 0.0;
        for each (double val in m_values) {
            sum += val;
        }
        return sum / m_values->Length;
    }
};

int main() {
    DataAnalyzer^ analyzer = gcnew DataAnalyzer(20);
    analyzer->PopulateData();
    Console::WriteLine("Average: {0}", analyzer->CalculateAverage());
    return 0;
}

내부 포인터(Interior Pointer)와의 변환

매니지드 힙 내부의 주소를 가리키는 interior_ptrpin_ptr로 자동 변환될 수 있습니다.

// interior_ptr_conversion.cpp
// compile with: /clr
using namespace System;

ref struct Coordinate {
    Coordinate() : X(10), Y(20) {}
    int X;
    int Y;
};

int main() {
    Coordinate^ pointA = gcnew Coordinate();
    Coordinate^ pointB = gcnew Coordinate();

    // 내부 포인터(interior_ptr) 생성
    interior_ptr<int> ptrX = &(pointA->X);

    // 고정 포인터(pin_ptr) 생성
    pin_ptr<int> pinnedY = &(pointB->Y);

    // 내부 포인터를 고정 포인터에 할당 (자동 변환)
    pinnedY = ptrX;

    Console::WriteLine("Pinned Value: {0}", *pinnedY);
    return 0;
}

고정 포인터의 타입 캐스팅

고정된 포인터는 다른 네이티브 포인터 타입으로 캐스팅하여 바이트 단위의 메모리 조작이 가능합니다.

// pin_ptr_casting.cpp
// compile with: /clr
using namespace System;

ref class ImagePixel {
public:
    int ColorValue;
};

int main() {
    ImagePixel^ pixel = gcnew ImagePixel();
    pin_ptr<int> pinnedColor = &pixel->ColorValue;

    *pinnedColor = 16777215; // White in RGB
    Console::WriteLine("Original: {0}", pixel->ColorValue);

    // 포인터 타입 캐스팅
    unsigned char* bytePtr = reinterpret_cast<unsigned char*>(pinnedColor);
    bytePtr[0] = 255; // Modify the first byte (e.g., Red channel)

    Console::WriteLine("Modified: {0}", pixel->ColorValue);
    return 0;
}

네이티브 char*와 매니지드 바이트 배열 간 변환

네이티브 C++의 char* 버퍼와 .NET의 array<unsigned char>^ 간의 데이터 교환은 네트워크나 파일 I/O 작업에서 자주 발생합니다. pin_ptr를 사용하면 매니지드 배열의 내부 버퍼를 직접 네이티브 포인터로 노출하여 오버헤드 없는 데이터 처리가 가능합니다.

// buffer_interop.cpp
// compile with: /clr
using namespace System;
using namespace System::IO;

#pragma unmanaged
void ProcessRawData(unsigned char* data, size_t dataSize) {
    // 네이티브 환경에서 데이터 처리 (예: 암호화, 압축 등)
    for (size_t i = 0; i < dataSize; ++i) {
        data[i] ^= 0x55; // 간단한 XOR 연산 예시
    }
}
#pragma managed

ref class SecureTransfer {
private:
    array<unsigned char>^ m_dataBuffer;

public:
    SecureTransfer(int capacity) {
        m_dataBuffer = gcnew array<unsigned char>(capacity);
    }

    void PrepareAndProcess() {
        if (m_dataBuffer == nullptr) return;

        // 배열의 시작 주소를 고정하여 네이티브 포인터로 변환
        pin_ptr<unsigned char> pinnedBuffer = &m_dataBuffer[0];
        unsigned char* rawPtr = pinnedBuffer;

        ProcessRawData(rawPtr, m_dataBuffer->Length);
    }

    void Transmit(Stream^ outputStream) {
        if (m_dataBuffer != nullptr && outputStream != nullptr) {
            outputStream->Write(m_dataBuffer, 0, m_dataBuffer->Length);
        }
    }
};

태그: C++/CLI .NET Interop gcroot pin_ptr Visual C++

8월 20일 05:13에 게시됨