PIDL 활용법

Custom Build Rules 사용하기

Display Name : Custom Build Rules 에서 보여질 이름 입니다. ex) PIDL Rule File Name : 룰 파일명을 작성합니다. ex) PIDL_Custom_Build_Rule Directory : 룰 파일이 저장될 위치를 지정합니다. ex) C:\XXX\YYY

설정의 각 세부 내용은 다음과 같습니다.

Additional Dependencies : ..\..\..\util\PIDL.exe
Batching Separator :
Command Line : ..\..\..\util\PIDL.exe "$(InputPath)" -outdir .\
Display Name : PIDL
Execution Description : Compiling $(InputName).pidl ...
File Extensions : *.pidl
Name : PIDL Custom Build Rule
Outputs : $(InputDir)\$(InputName)_common.cpp;$(InputDir)\$(InputName)_common.h;$(InputDir)\$(InputName)_proxy.cpp;$(InputDir)\$(InputName)_proxy.h;$(InputDir)\$(InputName)_stub.cpp;$(InputDir)\$(InputName)_stub.h

해당 설정을 마치시면 Custom Build Rules 에 다음과 같이 PIDL Build Rule 이 추가됩니다.

새로 만들어진 룰 파일을 체크해 주시고 해당 프로젝트에서 파일을 만드시면 Build Tool이 PIDL로 자동 선택된 것을 확인하실 수 있습니다.

명령 프롬프트(Command Prompt, cmd.exe) 사용하기

PIDL AddOn 혹은 Custom Build Rules를 사용할 수 없을 경우 Windows OS에서 명령 프롬프트를 통해 컴파일할 수 있습니다.

cs의 경우 PIDL -cs 명령어를 붙여서 컴파일 해주시면 됩니다.

  • PIDL.exe 는 ProudNet 설치 경로의 util 폴더에 있습니다.

  • Debug, Release 모두 동일하게 설정해야 합니다.

  • PIDL.exe의 경로로 예제를 따라 적지 마시고, ProudNet이 설치된 경로의 PIDL로 설정해 주세요.

  • 해당 예시에서는 Common - PIDL 폴더를 생성 후 PIDL 컴파일 경로(outdir)로 지정했습니다.

  • 해당 include 경로를 따라 하지 마시고 사용하시는 프로젝트의 필요한 경로를 지정하셔서 작성하시기 바랍니다.

생성된 소스 파일들을 확인 후 Visual Studio 프로젝트 속성 창에서 포함하신 후 빌드 하시면 사용 가능합니다.

Customizations 사용하기

Visual Stuido 2005, 2008 버전

  • Custom Build Rules 를 사용하여 룰 파일 생성 가능

  • 확장자가 .rules 인 1개의 룰 파일

Visual Studio 2010 이후 버전

  • Custom Build Rules 를 사용하여 사용 가능 / 작성 불가

  • .props, .targets, .xml 3개의 파일 필요. 이전 버전에서 만든 .rules 파일 그대로 사용 불가

customizations 사용 방법 2가지

1. Visual Studio 2005 또는 2008 에서 프로젝트 생성 후 .rules 파일을 만들고 사용하도록 셋팅 후, 해당 프로젝트를 2010 이후 버전으로 변환하는 방법 : 프로젝트를 변환 하실 경우 .rules 파일이 .props, .targets, .xml 으로 자동으로 변환되어 생성됩니다.

2. .props, .targets, .xml 파일 생성 후 xml 코드를 직접 작성하여 사용하는 방법

Visual Studio 2005, 2008 과 Visual Studio 2010 이후 버전은 정의된 메크로가 다릅니다. 해당 룰이 Visual Studio 2010 이후 버전에서 사용되는 메크로로 작성되어 있지 않은 경우 변환을 하시기 전에 Visual Studio 2010 이후 버전에서 사용하는 메크로로 변환을 해주시거나 변환 후 .props, .targets, .xml을 각각 수정해 주셔야 합니다.

PIDL 내용에 include 또는 import 사용하기

프로그램 개발을 하다 보면 .pidl 파일에 include 혹은 import 구문을 넣고자 할 때가 있습니다.

#include "a/b/c.h"
 
class MyStub // PIDL 컴파일 결과물
{
    ...
}

이를 위해 PIDL 내용 안에서 다음과 같이 사용하세요.

#include "a/b/c.h" 
// for C++ language. semicolon is mandatory!

마샬링

RMI에 사용자 정의 클래스 타입을 사용하기

// MyType.h
 
namespace Proud
{
    // 호출한 RMI 함수의 내용을 문자열로 만들어 출력한다.
    // 로그를 만들 때 유용하다.
    void AppendTextOut(String &a,const MyType &b);
 
    // 메시지 버퍼로부터 커스텀 타입의 내용을 읽어온다.
    CMessage& operator>>(CMessage &a, MyType &b);
 
    // 메시지 버퍼로 커스텀 타입의 내용을 넣는다.
    CMessage& operator<<(CMessage &a, const MyType &b);
}

ProudNet의 RMI 기능 내부에서는 Proud.CMessage를 사용하고 있습니다. 그리고 위 함수들의 오버로딩을 통해 RMI의 파라미터가 마샬링됩니다. 여기서 Proud.CMessage가 사용되고 있습니다. Proud.CMessage는 ProudNet에서 RMI를 메시지로 바꾸거나 메시지로부터 파라미터를 읽어올 때 사용하는 메시지 데이터를 갖고 있으며 스트림 객체로서 사용됩니다.

마샬링을 하는 함수에 스트림을 구현하는 예는 다음과 같습니다.

namespace Proud
{
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        a>>b.x,b.y>>b.z>>b.w;
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        // a.UseInternalBuffer()를 사용하지 말 것!
        a<<b.x,b.y<<b.z<<b.w;
        return a;
    }
    void AppendTextOut(String &a,const MyType &b)
    {
        String f;
        f.Format(L"{x=%f,y=%f,z=%f,w=%f}",b.x,b.y,b.z,b.w);
        a+=f;
    }
}

마지막으로, PIDL compiler에서 만들어진 proxy, stub 파일들을 include를 하기 전에 위 오버로딩된 메서드들이 선언된 헤더 파일이 먼저 include 되어야 합니다.

// 예1
#include "MyType.h"
#include "MyPIDL_proxy.h"
 
// 예2
#include "MyType.h"
#include "MyPIDL_stub.h"

실제 구현된 예제는 <설치 폴더>/sample/CustomTypeMarshal 또는 <Sample/CasualGame/GCServer/FarmCommon.h> 를 참고하세요. 직접 구현하신 마샬링 기능 작동 여부를 확인하시려면 Proud.TestMarshal()을 사용하시기 바랍니다.

- 조건에 따른 마샬링 방법

캐릭터의 타입 간 각 필드가 유효하거나 무효 여부가 서로 다른 캐릭터의 정보를 RMI 파라미터에서 마샬링하는 방법이 있습니다. switch/case 문이나 객체의 다형성을 이용해서 다양한 마샬링을 구현할 수 있습니다.

➡️ swich/case를 쓰는 예시
namespace Proud
{
    enum UnitType
    {
        Zergling,     // 스타크래프트의 저글링(지상형 통상 공격 유닛)
        Queen,        // 스타크래프트의 퀸(비행형 특수 기술 사용 유닛)
        Broodling    // 퀸이 브루들링 스킬을 써서 생성된, 생존 시간이 짧은 공격 유닛
    };
 
    struct Unit
    {
        UnitType m_type;          // 유닛의 타입
        Vector2D m_position;    // 유닛의 위치
        int m_energy;            // 유닛의 보유 에너지(혹은 마나) -> 퀸만 유효
        float m_lifeTime;        // 유닛의 생존 시간 시한 -> 브루들링만 유효
        int m_attackPower;        // 유닛의 공격력->저글링만 유효
    };
 
    CMessage& operator<<(CMessage& msg,const Unit& unit)
    {
        msg<<unit.m_type<<unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg<<unit.m_attackPower;
            break;
        case Queen:
            msg<<unit.m_energy;
            break;
        case Broodling:
            msg<<unit.m_lifeTime;
            break;
        }
        return msg;
    }
 
    CMessage& operator>>(CMessage& msg,Unit& unit)
    {
        msg>>unit.m_type>>unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg>>unit.m_attackPower;
            break;
        case Queen:
            msg>>unit.m_energy;
            break;
        case Broodling:
            msg>>unit.m_lifeTime;
            break;
        }
        return msg;
    }
}

- Bit 단위의 데이터를 마샬링하기

메시지에 저장되는 데이터의 용량을 줄이기 위해 비트 단위로 데이터를 마샬링 할 수 있습니다. Proud.CMessage 는 bit 단위로 데이터를 저장하는 다음 메서드들이 있습니다.

메서드설명

Proud.CMessage.ReadBits

bit 단위 읽기

Proud.CMessage.WriteBits

bit 단위 쓰기

➡️ 예시
namespace Proud
{
    struct MyType
    {
        int x,y,z;
    };
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        // x 값을 읽어들이는데 6비트 사용
        a.ReadBits(b.x,6);
        // y 값을 읽어들이는데 3비트 사용
        a.ReadBits(b.y,3);
        // z 값을 읽어들이는데 15비트 사용. 즉 도합 6+3+15=24bit(3 byte)사용
        a.ReadBits(b.z,15);
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        a.WriteBits(b.x,6); // x,y,z값을 비트 단위로 저장한다.
        a.WriteBits(b.y,3);
        a.WriteBits(b.z,15);
        return a;
    }
}

Bit 단위 데이터 마샬링의 주의 사항

기록하려는 비트 갯수가 기록하려는 실제 값의 범위를 벗어나서는 안됩니다. 예를 들어 int를 기록하려고 하는데 정작 int 안에 들어가 있는 값이 음수인 경우 첫 bit가 1입니다. 이럴 때 bit 양을 줄이기 위해 31비트 이하를 기록하려고 하는 경우 첫 bit의 값이 누락되므로 비트 단위 읽기/쓰기를 할 때에는 이 점을 주의해야 합니다.

- enum 타입을 마샬링하기

enum 타입을 마샬링하려면 아래와 같은 함수를 구현합니다.

enum type { ... } ;
 
namespace Proud
{
    inline CMessage& operator<<(CMessage& a,type b)
    {
        a<<(int)b;
        return a;
    }
    inline CMessage& operator>>(CMessage& a,type& b)
    {
        int x;
        a>>x;
        b=(type)x;
        return a;
    }
    inline void AppendTextOut(String &a,type b)
    {
        String txt;
        txt.Format(L"%d",(int)b);
        a+=txt;
    }
}

ProudNet에 이미 정의된 매크로 PROUDNET_SERIALIZE_ENUM 를 이용하면 위 구현을 아래와 같이 쉽게 할 수 있습니다.

PROUDNET_SERIALIZE_ENUM(type)

- 콜렉션(배열 등)을 마샬링하기

ProudNet에서는 몇 가지 기본 콜렉션(배열 등) 타입에 대한 마샬링을 바로 할 수 있도록 준비되어 있습니다. Proud.CFastArray, Proud.CFastMap, std.vector , CAtlArray 를 지원합니다.

marshaler.h에서 operator>>, operator<< 오버라이드 참고

// 아래는 배열 타입을 PIDL에서 선언한 예입니다.
Foo([in] Proud::CFastArray<MyType> a, [in] std::vector<MyType> b);

만약 marshaler.h 에서 기본 정의된 콜렉션 타입 외의 마샬링이 필요한 경우에는 필요로 하는 콜렉션에 대응하는 오버라이드를 구현해야 합니다. 이를 위해 RMI에 사용자 정의 클래스 타입 사용하기 또는 이미 구현된 예시가 있는 marshaler.h에서 참고하시기 바랍니다.

ProudNet에서 아직 지원하지 않는 콜렉션 타입에 대해서 마샬링을 하고자 할 때는 다음 루틴을 참고하여 작성하세요.

➡️ std.vector 마샬링 예시
namespace Proud
{
    // vector에서 사용할 수 있는 serialization functions
    // for output to stream
    template<typename elem>
    inline CMessage& operator>>(CMessage &a, std::vector<elem> &b)
    {
        // 크기를 얻는다.
        int size;
        a >> size;
 
        // 크기가 받아들일 수 없는 경우에는 예외를 발생시킨다.
        // 해킹된 경우 등을 의심할 수 있다.
        if (size<0 ||size >= CNetConfig::MessageMaxLength)
            ThrowExceptionOnReadArray(size);
 
        // 메모리 frag를 줄이기 위해
        b.reserve(size);
        b.resize(0);
 
        // 배열 항목 하나 하나를 읽는다.
        elem e;
        for (int i = 0;i < size;i++)
        {
            a >> e;
            b.push_back(e);
        }
        return a;
    }
 
    // vector, list등 unary item elem 등에서 
    // 사용할 수 있는 serialization functions
    // for input from stream
    template<typename elem>
    inline CMessage& operator<<(CMessage &a, const std::vector<elem> &b)
    {
        // 배열 크기를 기록한다.
        int size = (int)b.size();
        a << size;
        
        // 각 배열 인자를 기록한다.
        for (std::vector<elem>::const_iterator i = b.begin();i != b.end();i++)
        {
            a << (*i);
        }
        return a;
    }
 
    template<typename elem>
    inline void AppendTextOut(String &a, std::vector<elem> &b)
    {
        a += L"<vector>";
    }
}

키워드 목록

키워드 명설명사용 예시

access

[C#] namespace 안의 proxy, stub 클래스 접근 권한 설정

[access=권한이름]

byval

파라미터를 value로 전달

P2PChat([in] Proud::String a, [in, byval] int b);

global

네임 스페이스 접근 권한(현재 global만 지원)

global Simple 2000 { ... }

in

파라미터 입력

Chat([in] string txt);

include

[C#] 결과물에 include 삽입

#include "a/b/c.cpp"

marshaler

[C#] 사용자 정의 타입에 대해 마샬러

[marshaler(cs) = SimpleCSharp.CMyMarshaler]

mutable

파라미터를 변경 가능(:=reference)로 전달

Chat([in, mutable] string txt);

private

[C#] namespace 안의 proxy, stub 클래스 접근 권한 설정

[access=private]

protected

[C#] namespace 안의 proxy, stub 클래스 접근 권한 설정

[access=protected]

public

[C#] namespace 안의 proxy, stub 클래스 접근 권한 설정

[access=public]

rename

특정 환경에서 치환하여 컴파일

rename cs(Proud::String, System.String);

using

[C#] 결과물에 using 키워드 삽입

using(cs) System.XXX;

[C#] 다양하게 파라미터를 전달하고 싶을 때

global 네임스페이스명 2000 
{
    함수명([in] int 변수명);
}

위와 같이 PIDL을 작성했을 시, PIDL 컴파일러는 기본 타입에 대하여 아래와 같이 컴파일합니다.

namespace 네임스페이스명
{
    class Proxy : public ::Proud::IRmiProxy
    {
    public:
        virtual bool 함수명( ::Proud::HostID remote, ::Proud::RmiContext& rmiContext , const int& 변수명) PN_SEALED; 
        ...
    }
}

전달 방식을 변경하고 싶으실 땐 아래의 키워드를 사용합니다.

키워드 명예제(PIDL)예제(출력결과)

없음

Chat([in] int val);

...Chat(..., const int& val)...

byval

Chat([in, byval] int val);

...Chat(..., const int val)...

mutable

Chat([in, mutable] int val);

...Chat(..., int& val)...


⬅️ 뒤로

Last updated