.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"
// for C++ language. semicolon is mandatory!
import com.mycompany.mygame; // for Java
마샬링
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를 메시지로 바꾸거나 메시지로부터 파라미터를 읽어올 때 사용하는 메시지 데이터를 갖고 있으며 스트림 객체로서 사용됩니다.
실제 구현된 예제는 <설치 폴더>/sample/CustomTypeMarshal 또는 <Sample/CasualGame/GCServer/FarmCommon.h> 를 참고하세요. 직접 구현하신 마샬링 기능 작동 여부를 확인하시려면Proud.TestMarshal()을 사용하시기 바랍니다.
C++ 쪽에서의 CFastArray를 serializing해서 보내면 C# 쪽에서 이를 받아 deserializing하여 사용하는 방법으로 5단계를진행됩힙다.
본 예제는 C# 클라이언트에서 Ping 이라는 RMI를 호출하면, 그것을 받은 C++ 서버는 StudentList RMI를 호출하여 C++의 CFastArray를 C# 쪽으로 전달하게 되고, C# 쪽은 그것을 List로 전달받습니다.
namespace CsClient
{
class Student
{
public string Name;
public int ID;
public int Kor;
public int Eng;
public int Mat;
public override string ToString()
{
return string.Format("Name: {0}({1}) K: {2}, E: {3}, M: {4}", Name, ID, Kor, Eng, Mat);
}
}
}
(2) C++ CStudent 클래스 작성
class CStudent
{
public:
Proud::String Name;
int ID;
int Kor;
int Eng;
int Mat;
};
C#의 마샬링은 Nettention.Proud.Marshaler을 상속받아 구현한 뒤, 대체하는 방식으로 개발되며 직접 하실 필요없이 PIDL에 선언한 marshaler(cs)=CsClient.MyMarshaler 키워드로 동작합니다.
namespace CsClient
{
class MyMarshaler : Nettention.Proud.Marshaler
{
public static bool Read(Message msg, out List<Student> students)
{
students = null;
if (!msg.ReadScalar(out var size))
{
return false;
}
students = new List<Student>();
for (int i = 0; i < size; ++i)
{
Student s = new Student();
if (!msg.Read(out s.Name)) { return false;
}
if (!msg.Read(out s.ID))
{
return false;
}
if (!msg.Read(out s.Kor))
{
return false;
}
if (!msg.Read(out s.Eng))
{
return false;
}
if (!msg.Read(out s.Mat))
{
return false;
}
students.Add(s);
}
return true;
}
public static void Write(Message msg, List<Student> students)
{
msg.WriteScalar(students.Count);
for (int i = 0; i < students.Count; ++i)
{
msg.Write(students[i].Name);
msg.Write(students[i].ID);
msg.Write(students[i].Kor);
msg.Write(students[i].Eng);
msg.Write(students[i].Mat);
}
}
}
}
g_S2CStub.StudentList = (remote, rmiContext, students) =>
{
lock(g_lock)
{
// List<Student> 로 넘어오는 students를 사용하시면 됩니다.
foreach (var s in students)
{
Console.WriteLine(s.ToString());
}
}
return true;
};
- 조건에 따른 마샬링 방법
캐릭터의 타입 간 각 필드가 유효하거나 무효 여부가 서로 다른 캐릭터의 정보를 RMI 파라미터에서 마샬링하는 방법이 있습니다. switch/case 문이나 객체의 다형성을 이용해서 다양한 마샬링을 구현할 수 있습니다.
- Bit 단위의 데이터를 마샬링하기
메시지에 저장되는 데이터의 용량을 줄이기 위해 비트 단위로 데이터를 마샬링 할 수 있습니다. Proud.CMessage 는 bit 단위로 데이터를 저장하는 다음 메서드들이 있습니다.
메서드
설명
Proud.CMessage.ReadBits
bit 단위 읽기
Proud.CMessage.WriteBits
bit 단위 쓰기
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에서 아직 지원하지 않는 콜렉션 타입에 대해서 마샬링을 하고자 할 때는 다음 루틴을 참고하여 작성하세요.
키워드 목록
키워드 명
설명
사용 예시
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 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;
}
}
예시
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;
}
}
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>";
}
}