PIDL에서는 정수형이나 문자열 등 기본적인 변수 타입 외의 다양한 타입을 사용할 수 있습니다.
참고: C# 언어의 마샬링은 C# 언어에서 사용자 정의 타입을 마샬링하기에 설명되어 있습니다.
2.1RMI에 사용자 정의 클래스 타입을 사용하기
RMI 호출을 메시지로 변환하거나 메시지로부터 RMI를 호출하기 위한 값을 추출하는 것을 마샬링(marshaling)이라고 칭합니다.
ProudNet에서는 int나 float 등 기본적인 타입에 대한 마샬링 기능을 제공하고 있습니다.
물론, 이러한 기본적 타입 뿐만 아니라 사용자가 정의한 클래스 타입을 사용할 수도 있습니다. 그러기 위해서는 다음 함수들을 오버로딩으로 구현해야 합니다.
// 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를 메시지로 바꾸거나 메시지로부터 파라메터를 읽어올 때 사용하는 메시지 데이터를 갖고 있으며, 스트림 객체로서 사용됩니다.
marshaling을 하는 함수에 스트림을 구현하는 예는 다음과 같습니다.
// MyType.cpp 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"
이것이 실제로 구현된 예제는 11. 커스텀 타입 객체의 마샬링 예제 프로그램 및 <Sample/CasualGame/GCServer/FarmCommon.h> 를 참고하십시오.
더불어, 직접 구현하신 마샬링 기능이 잘 작동함을 확인하시려면 Proud.TestMarshal() 을 사용하시는 것도 좋습니다.
2.2조건에 따라 마샬링 방법을 달리 하기
예를 들어, 캐릭터의 타입간 각 필드가 유효하거나 무효 여부가 서로 다른 캐릭터의 정보를 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; } }
2.3Bit 단위의 데이터를 마샬링하기
를 할 때, 메시지에 저장되는 데이터의 용량을 줄이기 위해 비트 단위로 데이터를 마샬링할 수 있습니다.
Proud.CMessage는 bit 단위로 데이터를 저장하는 다음 메서드들이 있습니다.
•bit 단위 읽기: Proud.CMessage.ReadBits
•bit 단위 쓰기: Proud.CMessage.WriteBits
아래는 사용 예입니다.
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의 값이 누락됩니다. 따라서 비트 단위 읽기/쓰기를 할 때에는 이 점을 주의해야 합니다.
2.4enum 타입을 마샬링하기
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)
2.5콜렉션(배열 등)을 마샬링하기
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에서 기본 정의된 콜렉션 타입 외의 마샬링이 필요한 경우에는 필요로 하는 콜렉션에 대응하는 오버라이드를 구현해야 합니다. 이를 위해 2.1 RMI에 사용자 정의 클래스 타입을 사용하기 를 참고하십시오. 이미 구현된 선례를 marshaler.h에서 참고하시는 것도 권장합니다.
다음은 std.vector를 마샬링하는 예입니다. ProudNet에서 아직 지원하지 않는 콜렉션 타입에 대해서 마샬링을 하고자 할 때는 다음 루틴을 참고해서 마샬링하는 루틴을 작성하시면 됩니다.
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>"; } }