2008년 12월 21일
WM_COPYDATA (2)
지난 WM_COPYDATA(1) 포스팅 된 글 중에서 다음 struct BookInfo 구조체의 m_szName 멤버 변수의 경우 사이즈가 정해져 있습니다. 그런데 만약 m_szName을 가변적으로 즉, 동적으로 메모리를 할당해서 WM_COPYDATA 메시지를 이용해서 다른 프로세스에게 데이터를 전송하기 위해서는 어떻게 해야할까요? 일감으로는 그냥 동적 할당한 후 WM_COPYDATA(1) 포스팅 내용과 동일하게 전송할 수 있을 것 같습니다. 하지만 같은 방법으로는 이 문제를 해결 할 수 없습니다.


//
// 해당 프로토콜에 대해 전달 될 데이터 형
//
struct BookInfo
{
    int m_nID;
    TCHAR m_szName[100];
};


그럼, 왜 안되는 걸까요?? 그건 WM_COPYDATA 메시지가 데이터를 전송해주는 방식에 있습니다. WM_COPYDATA는 COPYDATASTRUCT의 lpData 멤버변수가 가리키고 있는 메모리 주소에서 부터 cbData 멤버변수에 저장된 사이즈만큼 바이트를 읽어서 이를 복사하여 상대방 프로세스에게 데이터를 전송합니다.

우선 WM_COPYDATA(1) 에서의 경우 아래 그림과 같은 메모리가 복사되어 상대방 프로세스로 전달되기 때문에 데이터가 정상적으로 전달 되었습니다.

WM_COPYDATA(1) 에서 사용한 BookInfo 구조체를 다음과 같이 바꾸어 주고 동적으로 메모리를 할당하게 되면 아래와 같은 그림과 같은 메모리 구조를 같게 됩니다.



//
// 해당 프로토콜에 대해 전달 될 데이터 형
//
struct BookInfo
{
    int m_nID;
    LPTSTR m_szName;
};



이렇게 되면 WM_COPYDATA(1) 에서와 같은 방법으로 데이터를 전송하게 되면 m_szName이 가리키고 있는 메모리의 주소값(0x00385FE8)만 전달되게 되고 "Keep going"이라는 문자열은 전달되지 않게 됩니다. MSDN에서 WM_COPYDATA에 대한 설명을 보면 아래와 같은 문구가 있는데요. 위에서 시도한 방법은 이에 해당하는 내용입니다.

The data being passed must not contain pointers or other references toobjects not accessible to the application receiving the data.


그렇다면 이 문제를 해결하기 위해서는 어떻게 해야 할까요?? 여러가지 방법이 있겠지만 저 같은 경우는 Byte-Serializing이라는 방법을 사용해 보았습니다.

Byte-Serializing은 위 구조체의 멤버를 동적으로 할당 된 메모리 공간(이하 Byte-Array)에 복사를 하는 방법으로 구조체의 멤버를 복사한 Byte-Array를 WM_COPYDATA 메시지를 이용해서 상대방 프로세스에 전달하고, 상대방 프로세는 전달 받은 Byte_Array를 De-Byte-Serializing하여 구조체의 멤버에 값을 할당해 주는 방식입니다.

아래는 WM_COPYDATA(1) 에서 사용했던 BookInfo 구조체를 확장하여 바이트 시리얼라이징과 역 바이트 시리얼라이징 기능을 추가한 클래스입니다.


//
// 해당 프로토콜에 대해 전달 될 데이터 형
//
class CBookInfo
{
public:
    //
    //    ByteSerializing 시 사용할 생성자 ..
    //
    CBookInfo(int arg_nID, LPTSTR arg_szName)
        : m_nID(arg_nID)
        , m_pByteData(NULL)
        , m_bByteSerializing(true)
    {
        m_nName = _tcslen(arg_szName) + 1;
        m_szName = new TCHAR[m_nName];
        ZeroMemory(m_szName, m_nName);

        _tcsncpy(m_szName, arg_szName, m_nName);

        m_nByteData = sizeof(int) + m_nName;
    }
    //
    //    DeByteSerializing 시 사용할 생성자 ..
    //
    CBookInfo(char* arg_pByteData, int arg_nByteData)
        : m_nID(0)
        , m_nName(0)
        , m_nByteData(arg_nByteData)
        , m_szName(NULL)
        , m_pByteData(arg_pByteData)
        , m_bByteSerializing(false)
    {
       
    }
    ~CBookInfo()
    {
        if(m_szName) delete[] m_szName; m_szName = NULL;
        //
        // DeByteSerializing을 하기위해 생성자에서 받은 m_pByteData는 소멸자에서 제거해서는 안된다.
        //
        if(true == m_bByteSerializing && m_pByteData) delete[] m_pByteData; m_pByteData = NULL;
    }
    char* ByteSerializing()
    {
        m_pByteData = new char[m_nByteData];
        ASSERT(NULL != m_pByteData);

        memcpy(m_pByteData, &m_nID, sizeof(int));
        memcpy(m_pByteData+sizeof(int), m_szName, m_nName);

        return m_pByteData;
    }
    void DeByteSerializing()
    {
        m_nName = m_nByteData - sizeof(int);
        m_szName = new TCHAR[m_nName];
        ASSERT(m_szName);

        memcpy(&m_nID, m_pByteData, sizeof(int));
        memcpy(m_szName, m_pByteData+sizeof(int), m_nName);
    }

    size_t GetByteDataLen() { return m_nByteData; };
    int GetID() { return m_nID; }
    LPTSTR GetName() { return m_szName; }


private:
    int m_nID;
    LPTSTR m_szName;

    size_t m_nName;
    size_t m_nByteData;
    char* m_pByteData;
    //
    //    DeByteSerializing을 하기위해 생성자에서 받은 m_pByteData는 소멸자에서 제거해서는 안된다.
    //  이를 방지하기 위한 플래그 ..
    //
    bool m_bByteSerializing;
};

클래스 소스를 보시면 멤버 변수 중에 bool m_bByteSerializing; 이 보입니다. 소스를 보시면 아시겠지만 바이트 시리얼라이징을 할때와 역 바이트 시리얼라이징을 할 때 사용되는 생성자가 다릅니다. 역 바이트 시리얼 라이지을 할 때 생성자를 보시면 arg_pByteData 파라메터는 바이트 시리얼라이징 되어 전달 된 메시지 입니다. MSDN에서 WM_COPYDATA에 대한 설명을 보시면 아래와 같은 문구가 있습니다.

The receiving application should not free the memory referenced by lParam. <-- lParam이 arg_pByteData로 전달 되는 값 ..

 즉, arg_pByteData는 역 바이트 시리얼라이징을 이용하는 프로세스에서는 메모리 해제를 해주어서는 안된다는 것입니다. 그래서 m_bByteSerializing을 통해서 소멸자에서 arg_pByteData로 전달 된 값을 바이트 시리얼라이징 할 때만 메모리 해제 할 수 있도록 제어하는 방법을 사용하였습니다.

아래는 나머지 구현 코드입니다.

ProcessA ..


void CProcessADlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here

    //
    //    데이터를 전송 할 프로세스의 핸들을 이용하기 위해 윈도우 객체를 얻어 옵니다.
    //
    CWnd* pWnd = FindWindow(NULL, "ProcessB");
    ASSERT(pWnd);
    //
    //    전송할 데이터를 셋팅합니다.
    //
    CBookInfo bookInfo(1000, _T("Keep going .."));
    //
    //    전송할 데이터를 COPYDATASTURCT에 셋팅하고 전송합니다.
    //
    COPYDATASTRUCT cds;

    cds.dwData = Protocol::PROTOCOL_BOOKINFO;
    cds.cbData = (DWORD)bookInfo.GetByteDataLen();
    cds.lpData = (LPVOID)bookInfo.ByteSerializing();

    ::SendMessage(pWnd->m_hWnd, WM_COPYDATA, (WPARAM)GetSafeHwnd(), (LPARAM)&cds);
}


LRESULT CProcessADlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // TODO: Add your specialized code here and/or call the base class

    switch(message)
    {
    case WM_COPYDATA:
        {
            PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT) lParam;
            //
            //    프로토콜에 따른 처리를 위해서 ..
            //
            switch(pcds->dwData)
            {
            case Protocol::PROTOCOL_BOOKINFO:
                {
                    //
                    //    전송받은 데이터(COPYDATASTRUCT 구조체)를 풀어서 BookInfo에 셋팅합니다.
                    //
                    CBookInfo bookInfo((char*)(pcds->lpData), pcds->cbData);
                    bookInfo.DeByteSerializing();
                   
                    //
                    //    전송받은 데이터를 메시지 박스를 출력합니다.
                    //
                    CString strReceiveMsg;
                    strReceiveMsg.Format(_T("ID : %d\tName : %s"), bookInfo.GetID(), bookInfo.GetName());

                    AfxMessageBox(strReceiveMsg);           

                    break;
                }
            case Protocol::PROTOCOL_COOKINFO:
                {
                    // Do something ..
                    break;
                }
            }
        }
    }
    return CDialog::WindowProc(message, wParam, lParam);
}

ProcessB ..


LRESULT CProcessBDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // TODO: Add your specialized code here and/or call the base class

    switch(message)
    {
    case WM_COPYDATA:
        {
            PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT) lParam;
            //
            //    프로토콜에 따른 처리를 위해서 ..
            //
            switch(pcds->dwData)
            {
            case Protocol::PROTOCOL_BOOKINFO:
                {
                    //
                    //    전송받은 데이터(COPYDATASTRUCT 구조체)를 풀어서 BookInfo에 셋팅합니다.
                    //
                    CBookInfo bookInfo((char*)(pcds->lpData), pcds->cbData);
                    bookInfo.DeByteSerializing();

                    //
                    //    전송받은 데이터를 메시지 박스를 출력합니다.
                    //
                    CString strReceiveMsg;
                    strReceiveMsg.Format(_T("ID : %d\tName : %s"), bookInfo.GetID(), bookInfo.GetName());

                    AfxMessageBox(strReceiveMsg);           
                    //
                    //    wParam으로 들어 온 윈도우 핸들을 이용해서 데이터를 전송한 프로세스에게 데이터를 에코합니다.
                    //   
                    HWND hWnd = (HWND) wParam;
                    ::SendMessage(hWnd, WM_COPYDATA, 0, (LPARAM) pcds);

                    break;
                }
            case Protocol::PROTOCOL_COOKINFO:
                {
                    // Do something ..
                    break;
                }
            }
        }
    }
    return CDialog::WindowProc(message, wParam, lParam);
}


실행 결과는 WM_COPYDATA(1) 와 같습니다.


* 참고 문헌

Windows API 정복 (가남사, 김상형 저) - 제33장 IPC 라. WM_COPYDATA

* 예제 프로젝트

WM_COPYDATA_2.alz, WM_COPYDATA_2.a00, WM_COPYDATA_2.a01

by greenfrog | 2008/12/21 21:47 | C++ / WIN32 / MFC | 트랙백 | 덧글(1)
트랙백 주소 : http://greenfrog7.egloos.com/tb/1259515
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 이준배 at 2009/06/04 09:42
좋은 내용이네요 많은 도움이 되었습니다.

Protocol::PROTOCOL_BOOKINFO: 이게 뭔지가 궁금하네요

:         :

:

비공개 덧글



<< 이전 페이지 | 다음 페이지 >>