2017년 9월 28일 목요일

ActiveX <-> C# 포인터 매개변수 전달 - Passing pointer parameters between activex and c#

vc++로 작성된 activex 와 c#으로 작성된 호스트 프로그램 간에 포인터 값을 전달해야하는 경우는

1. c# -> activex 메소드 호출
2. activex -> c# 이벤트 호출

두 가지 경우가 있다. activex가 c#과 연동하기 위해서는 activex 에서 적절한 메소드를 만든다음 커맨드창에서

aximp /source MyActiveX.ocx

해서 만들어진 인터페이스용 cs 소스파일과 dll 을 사용한다.
이때 메소드의 매개변수를 c++의 BYTE* 와같이 포인터로해도 c#에서는 포인터를 매개변수로 사용할 수 없으므로 만들어진 cs 파일에는 ref byte 타입으로 변경되어 포인터값을 전달할 수 없다.
2의 경우도 마찬가지로 aximp 커맨드를 통해 생성되는 인터페이스용 cs 파일에는 포인터 타입이 들어가지않는다.

activex가 아닌 dll 인 경우 c# 함수 프로토타입 선언에서 byte[] 타입으로 매개변수를 지정해서 쓰면되지만 activex(ocx)는 aximp 로 생성된 소스를 사용해야하므로 난감한 상황이 발생한다.

결론적으로 포인터는 결국 32(혹은 64)비트 정수값일 뿐이며 사용하기에 따라 BYTE*, LONG* 등으로 캐스팅해서 사용할 뿐이다. (결국 해석의 차이)
따라서 매개변수를 굳이 포인터 타입으로 할 이유가 없다. activex 쪽에서 LONGLONG 타입(64비트 프로세스인 경우 대비해서 LONG보다 LONGLONG 권장)으로 매개변수를 지정한다음 포인터값(주소)을 넘겨주거나 넘겨받으면 그만이다.

1. 포인터 매개변수 전달 예 - activex

void MyActiveXCtrl::SetIntArrayData(LONGLONG pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());

// TODO: Add your dispatch handler code here
        int *data = (int *)pData;
        ...
}

aximp /source MyActiveX.ocx => cs 파일 생성

1. 포인터 매개변수 전달 예 - c#

int[] data = new int[10];
for (int i = 0; i < data.Length; i++) data[i] = i;

IntPtr ptr = Marshal.AllocHGlobal(sizeof(int) * data.Length);
Marshal.Copy(data, 0, ptr, data.Length);

myAxCtrl.SetIntArrayData((long)ptr); => c# long은 vc++ LONGLONG 이므로

Marshal.FreeHGlobal(ptr);


2. 포인터 매개변수 전달 예 - activex

void MyActiveXCtrl::OnDataEvent(LONG size, LONGLONG pData)
{
    FireEvent(eventidOnDataEvent, ...., pData);
}


2. 포인터 매개변수 전달 예 - c#

myAxCtrl.OnDataEvent += axAxCtrl_OnDataEvent;

private void axAxCtrl_OnDataEvent(_AXMyActiveXCtrlEvents_OnDataEvent e)
{
    byte[] btaImage = new byte[e.size];
    unsafe
    {
        byte* p = (byte*)e.pData;
        Marshal.Copy((IntPtr)p, btaImage, 0, e.size);
    }
    // now you can use btaImage data
    ...
}


1번의 경우 c# -> activex 로 포인터를 전달하는데 원하는 데이터(여기서는 int 배열)를 IntPtr 변수에 복사한다음 포인터값을 전달한다. 이때 포인터를 받는 c++에서는 LONGLONG 타입을 int* 로 타입캐스팅해서 사용하면된다.

2번의 경우 activex -> c# 으로 포인터를 전달하는데 포인터 size를 지정한다음 LONGLONG 타입(64비트) 정수에 포인터 주소값을 넣어서 c# 쪽으로 전달한다.
c#에서는 전달받은 long 타입(64비트) 데이터를 byte* 으로 캐스팅한다음 c#에서 쉽게 사용하도록 byte 배열에 복사한다.