이미 많은 개발자들이 C#의 장점을 누리고 있으나, 본 PT에서는 높은 성능과 생산성을 동시에 달성하기 위해 C/C++로 개발된 native 게임 코드에 스크립트 언어로서 C#을 통합 할 수 있는 방법을 제시한다. 이를 위해 오픈소스 .Net 프레임웍인 Mono의 사용방법과 모바일 플랫폼에서의 특이사항들을 자세히 설명한다.
또한, C/C++언어에 C#을 비롯한 다양한 스크립트 언어를 효율적으로 혼합하여 게임을 구현할 수 있는 아키텍처를 제시한다. clang과 reflection을 이용하여 서로 다른 언어 간 인터페이스 노출을 자동화하고, 게임 내 오브젝트의 생명주기를 자동으로 관리할 수 있는 기법에 대해 설명한다.
13. 각 플랫폼용 Mono 빌드
소스코드
mono
Runtime
(.a .so .lib .dll)
make
make
configure
Compiler
14. App
Embedded Mono
Native Runtime
.NET Assembly
로드 & JIT 컴파일
컴
파
일
JIT / AOT Binary
Mono Runtime
실행
.NET Compiler
Objective-C
Runtime
Java VM
15. 임베딩을 위한 C# 코드 처리
.NET Compiler
monolinker
mono
Reduced Assembly
.NET Assembly
Stripped Assembly
mov …
push …
call …
Assembly Code
.NET Libraries
AOT 컴파일
16. Embedded Mono 유의사항
Ahead-of-Time 컴파일
– Just-in-Time 실행을 지원하지 않는 OS를 위함
– 실행될 CPU/OS 마다 별도의 Mono 컴파일러와 런타임 필요
– 실행파일 크기 문제
– 리소스 업데이트로 C# 코드 업데이트 불가능
디버깅
– Custom Command Soft Debugger 사용 필요
– 디버깅 대상이 client, MonoDevelop이 server로 작동
– 쓰레드에서 Mono 코드를 실행하지 않으면 중단점 작동 안됨
sgen GC 사용시 MonoObject* 대신 gc_handle 에 의존
mono_trace_set_level_string 사용 추천
18. 목표: 언어간 혼용의 자유도
class Character
{
void SetPosition( float );
float position;
};
class Character
{
void Touched() …
void UpdateUI() …
int health;
}
19. 목표: 언어간 혼용의 자유도
void Character::Touched()
{
SetPosition( position + 1 );
}
class Character
{
void SetPosition( float );
float position;
};
20. 목표: 언어간 혼용의 자유도
void Character::SetPosition( float position )
{
…
health = …
UpdateUI();
}
class Character
{
void Touched() …
void UpdateUI() …
int health;
}
21. 목표: 언어간 혼용의 자유도
• 구현의 특성에 가장 적합한 언어에서 코딩
• C# 모듈을 C++ 처럼, C++ 모듈을 C# 처럼 사용
• 인터페이스용 코드 필요
– 각 타입에 대한 메타정보 등록 코드
– 호출 언어를 위해 정의된 선언(declaration) 코드
• 언어 간 인터페이스를 자동화 한다면?!
22. 언어간 인터페이스 자동생성
C++ 소
스코드
(.h)
C# 소스
코드
C++ 인
터페이스
C# 코드
C# 인터
페이스
C++ 코
드
2
3
4
1
CppSharp
CXXI
…
23. C#을 위한 C++ 인터페이스 자동생성
CppSharp
CXXI
+ Clang
1. C++ 헤더파일들을 파싱(!)하여 인터페이스들 분석
2. (C++ 인터페이스를 Mono에 노출하는 C++ 코드)와
이를 호출할 수 있는 C# 코드 생성
3. C# 코드에서 인터페이스 코드를 호출
4. C# 코드 컴파일시 인터페이스 코드를 함께 컴파일
24. C++ 파싱 - Clang
LLVM 컴파일러의 C/C++ 파서
상용 제품 수준
Visual C++ 구문들도 인식
불완전한 구문에 의해 파싱이 실패하지 않음
libclang을 이용하여 스크립트 언어에서도 사용 가능,
Python 모듈 기본 제공 – AST 생성
− libclang은 clang의 극히 일부의 기능만을 노출하므
로 커스터마이제이션 필요
25. C++ 인터페이스 생성 예제 (CppSharp)
// C++ 소스코드
class Foo
{
int variable;
int DoSomething ( int arg1, std::string arg2 );
};
// 자동 생성된 C#용 인터페이스 코드
class Foo
{
// C++의 메모리 레이아웃 선언
struct Internal { … }
// 사용자를 위한 편리한 인터페이스
int variable {
get { return _Instance.ToPointer()->variable; }
set { _Instance.ToPointer()->variable = value; }
}
int DoSomething( int arg1, string arg2 )
{ return Internal.DoSomething( _Instance, arg1, arg2 ); }
}
26. C++ 인터페이스 생성 예제 (Embedded)
// C++ 소스코드
#define EXPORT __attribute__((annotate(“ExportToMono”)))
class EXPORT Foo
{
int variable;
int DoSomething ( int arg1, std::string arg2 );
};
+ Clang
// 자동 생성된 클래스 등록 코드
RegisterNativeClass<Foo>();
RegisterNativeClassVariable<Foo,int>( “variable”, offsetof(Foo, variable) );
RegisterNativeClassMethod<Foo,int,int,std::string>( “DoSomething”, &Foo::DoSomething );
27. C++ 인터페이스 생성 예제 (Embedded)
// 자동 생성된 클래스 등록 코드에 의한 실제 내부 작동
RegisterNativeClass<Foo>();
// Foo 타입에 대한 RTTI등 각종 기본 정보들 등록
RegisterNativeClassVariable<Foo,int>( “variable”, offsetof(Foo, variable) );
// C#에서 호출할 accessor 메소드 등록
int NativeGetInt( void* nativeObject, int offset );
mono_add_internal_call( “NativeGetInt”, &NativeGetInt );
void NativeSetInt( void* nativeObject, int offset, int value );
mono_add_internal_call( “NativeSetInt”, &NativeSetInt );
RegisterNativeClassMethod<Foo,int,int,std::string>( “DoSomething”, &Foo::DoSomething );
// C#에서 호출할 메소드 등록
static int Foo_DoSomething( Foo* nativeObject, int arg1, std::string arg2 )
{ nativeObject->DoSomething( arg1, arg2 ); }
mono_add_internal_call( “Foo.NativeCall_DoSomething”, &Foo_DoSomething );
28. C++ 인터페이스 생성 예제 (Embedded)
// 자동 생성된 C#용 인터페이스 코드
class Foo
{
// C++의 메소드들 등록
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern int NativeCall_DoSomething( IntPtr nativeObject, int arg1, string arg2 );
// 사용자를 위한 편리한 인터페이스
int variable {
get { return NativeGetInt( nativeObject, 4 ); }
set { NativeSetInt( nativeObject, 4 ); }
}
int DoSomething( int arg1, string arg2 )
{ return NativeCall_DoSomething( nativeObject, arg1, arg2 ); }
}
29. C++를 위한 C# 인터페이스 자동생성
1. .NET reflection을 사용하여 DLL 안의 인터페이스
들 추적
2. DLL 인터페이스를 호출할 수 있는 C++ 코드 생성
3. C++ 게임코드에서 인터페이스 코드를 통해 DLL의
코드를 C++ 처럼 호출
4. C++ 게임코드 컴파일시 인터페이스 코드를 함께
컴파일
30. C# 인터페이스 생성 예제 (Delegate)
// 자동 생성된 C# 인터페이스 코드
partial class Foo
{
void ExposeToNative() {
RegisterMonoMethod( 0, () => { return variable; } ); // Get_variable
RegisterMonoMethod( 1, (int value) => { variable = value; } ); // Set_variable
RegisterMonoMethod( 2, DoSomething ); // DoSomething
}
}
// 자동 생성된 C++ 인터페이스 코드
class Foo
{
int Get_variable() { monoMethods[0](); }
void Set_variable( int value ) { return monoMethods[1]( value ); }
int DoSomething( int arg1, std::string arg2 )
{ return monoMethods[2]( arg1, arg2 ); }
};
delegate를 native 함수 포인터로 전달
RegisterMonoMethod에 의해 등록된 함수 포인터
31. C# 인터페이스 생성 예제 (Embedded)
// C# 소스코드
[Export]
class Foo
{
// 자동 생성된 C++용 인터페이스 코드
class Foo
{
int variable;
int DoSomething( int arg1, string arg2 ) { … }
}
int Get_variable() {
void* ret = mono_runtime_invoke( monoClass, monoObject, “get_variable” );
return *(int*)ret;
}
void Set_variable( int value ) {
mono_runtime_invoke( monoClass, monoObject, “set_variable”, [&value] );
}
int DoSomething( int arg1, std::string arg2 ) {
void* ret = mono_runtime_invoke( monoClass, monoObject, “DoSomething”,
[&arg1, mono_string_new_wrapper(arg2)] );
return *(int*)ret;
}
};
33. 언어 간 결합의 깊이
모듈 간 결합 다른 객체 간 결합 객체 수준 결합
C++ Class
C++ variables
C++ methods
C# Class
C# variables
C# methods
Hybrid Class
C++ variables
C# variables
C++ methods
C# methods
오브젝트 참조
클래스 형 변환
C++ 기능 모듈
표준 호출
단순 타입
C# 기능 모듈
개체 생명주기 관리
34. 객체 수준 결합
단일 메모리의 구역 분할
• C# 클래스 선언에서 C++ 클래스의
영역 미리 선언 필요
• 개체의 수명은 C#에 의해서만 관
리되어야 함
• C++ new/delete 사용 불가
35. 객체 수준 결합
개체 분할, 논리적 결합
• 독립적인 C++개체와 C#개체
• 필요할 때에만 상대개체 생성 가
능
• 자동생성된 소스코드 빌드에 유리
(partial class)
• 개체 수명관리가 핵심
36. Garbage Collection vs new/delete
C++ 개체가 소유자
mono_gchandle_new
mono_gchandle_free
C# 개체가 소유자 상호 참조
new / delete
gchandle refcount
37. 상대 개체 동적생성 예제
// 자동생성된 C++용 인터페이스 코드
class FooProxy
{
FooProxy( Foo* nativeObject )
{ // C#측의 Foo 개체가 없으면 생성 }
int DoInCS( int arg )
{ // C#측의 Foo.DoInCS 호출 }
};
// C# 소스코드
class Foo : public NativeBound
{
int DoInCS( int arg ) { … }
}
// C++ 소스코드
class Foo : public MonoBound
{
int DoInCPP( int arg )
{
return FooProxy(this).DoInCS(arg);
}
};
성능 주의
38. 삭제 - Garbage Collector 연동
살아있는 쌍 GC 활성화 Native 삭제
GC handle
3 1
Mono GC에 의
해 제거 가능
Mono GC에 의
해 제거 방지
Mono
GC
Native
GC
0
Mono GC에 의해
제거됨
삭제됨
참조 카운트
GC handle 삭제
47. C++ → Lua 예제
// 자동 생성된 클래스 등록 코드에 의한 실제 내부 작동
RegisterNativeClass<Foo>();
// 클래스를 lua table로 생성
lua_createtable( “Foo” );
RegisterNativeClassVariable<Foo,int>( “variable”, offsetof(Foo, variable) );
// lua table에 getter/setter 멤버 함수들 등록
lua_pushcclosure( “Get_variable”, &NativeGetInt );
lua_pushcclosure( “Set_variable”, &NativeSetInt );
RegisterNativeClassMethod<Foo,int,int,std::string>( “DoSomething”, &Foo::DoSomething );
// lua table에 멤버 함수 호출자 등록
lua_pushcclosure( “DoSomething”, &Foo_DoSomething );
// 자동 생성된 lua용 인터페이스 코드 - 주석 또는 Lua Checker 용도 외에는 필요 없음
Foo = {
Get_variable = function(),
Set_variable = function( int_value ),
DoSomething = function( int_arg1, string_arg2 )
}
명시적 accessor 대신 __index,
__newindex로 구현 가능
48. Lua → C++ 예제
// lua 소스코드
Foo = {
variable = 0,
int_DoSomething = function( int_arg1, string_arg2 )
…
end
// 자동 생성된 C++용 인터페이스 코드
}
class Foo
{
int Get_variable() { return lua_tointeger( “variable” ); }
void Set_variable( int value ) { lua_pushinteger( “variable”, value ); }
int DoSomething( int arg1, std::string arg2 )
{
lua_pushinteger( arg1 );
lua_pushstring( arg2 );
lua_pcall( “DoSomething” );
return lua_tointeger( STACK_TOP );
}
};