[kgc2014] 두 마리 토끼를 잡기 위한 c++ - c# 혼합 멀티플랫폼 게임 아키텍처...
DESCRIPTION
이미 많은 개발자들이 C#의 장점을 누리고 있으나, 본 PT에서는 높은 성능과 생산성을 동시에 달성하기 위해 C/C++로 개발된 native 게임 코드에 스크립트 언어로서 C#을 통합 할 수 있는 방법을 제시한다. 이를 위해 오픈소스 .Net 프레임웍인 Mono의 사용방법과 모바일 플랫폼에서의 특이사항들을 자세히 설명한다. 또한, C/C++언어에 C#을 비롯한 다양한 스크립트 언어를 효율적으로 혼합하여 게임을 구현할 수 있는 아키텍처를 제시한다. clang과 reflection을 이용하여 서로 다른 언어 간 인터페이스 노출을 자동화하고, 게임 내 오브젝트의 생명주기를 자동으로 관리할 수 있는 기법에 대해 설명한다.TRANSCRIPT
두마리토끼를잡기위한C++ - C# 혼합멀티플랫폼게임아키텍처설계
김성균
㈜이노스파크
Technical Director
목차
1. 개요
2. C# (.NET) 실행환경
3. 언어간인터페이스결합자동화
4. 언어간개체결합
5. 결론
6. 부록
동기
빠른성능
구현의자유성높은이식성
− 코딩의실수는치명적− 낮은생산성
높은생산성 (문법, OOP …)
적절한성능 C++와유사한문법
− 저수준제어어려움− 다른환경과연동어려움
목표
• 성능이중요한기능• 저수준제어• 외부라이브러리연동
• 빠른구현과잦은변경이필요한상위수준의기능들
• 성능이중요하지않은코드
언어간코드연동을위한작업량최소화코드를다른언어로손쉽게변환
Native Code
Script
Lib
구성요소
C# 런타임
언어간인터페이스자동화
언어간개체결합
C# (.NET) 실행환경
Mono™ = Microsoft .NET의포팅
≈오픈소스, 공짜아님
각플랫폼용 Mono 빌드
소스코드
Runtime
(.a .so .lib .dll)
make
make
configure
Compiler
mono
App
Embedded Mono
Native Runtime
.NET Assembly로드 & JIT 컴파일
컴파일
JIT / AOT Binary
Mono Runtime
실행
.NET Compiler
Objective-C
RuntimeJava VM
임베딩을위한 C# 코드처리
.NET Compiler
monolinker
mono
Reduced Assembly
.NET Assembly
Stripped Assembly
mov …
push …
call …
Assembly Code
.NET Libraries
AOT 컴파일
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 사용추천
언어간인터페이스결합자동화
목표: 언어간혼용의자유도
class Character
{
void SetPosition( float );
float position;
};
class Character
{
void Touched() …
void UpdateUI() …
int health;
}
목표: 언어간혼용의자유도
void Character::Touched()
{
SetPosition( position + 1 );
}
class Character
{
void SetPosition( float );
float position;
};
목표: 언어간혼용의자유도
void Character::SetPosition( float position )
{
…
health = …
UpdateUI();
}
class Character
{
void Touched() …
void UpdateUI() …
int health;
}
목표: 언어간혼용의자유도
• 구현의특성에가장적합한언어에서코딩
• C# 모듈을 C++ 처럼, C++ 모듈을 C# 처럼사용
• 인터페이스용코드필요– 각타입에대한메타정보등록코드
– 호출언어를위해정의된선언(declaration) 코드
• 언어간인터페이스를자동화한다면?!
언어간인터페이스자동생성
C++ 소스코드
(.h)
C# 소스코드
C++ 인터페이스C# 코드
C# 인터페이스C++ 코드
2
3
4
1
CppSharp
CXXI
…
C#을위한 C++ 인터페이스자동생성
1. C++ 헤더파일들을파싱(!)하여인터페이스들분석
2. (C++ 인터페이스를 Mono에노출하는 C++ 코드)와이를호출할수있는 C# 코드생성
3. C# 코드에서인터페이스코드를호출
4. C# 코드컴파일시인터페이스코드를함께컴파일
+ Clang
CppSharp
CXXI
C++ 파싱 - Clang
LLVM 컴파일러의 C/C++ 파서
상용제품수준
Visual C++ 구문들도인식
불완전한구문에의해파싱이실패하지않음
libclang을이용하여스크립트언어에서도사용가능,
Python 모듈기본제공 – AST 생성
− libclang은 clang의극히일부의기능만을노출하므로커스터마이제이션필요
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 ); }
}
C++ 인터페이스생성예제 (Embedded)
// C++ 소스코드#define EXPORT __attribute__((annotate(“ExportToMono”)))
class EXPORT Foo
{
int variable;
int DoSomething ( int arg1, std::string arg2 );
};
// 자동생성된클래스등록코드RegisterNativeClass<Foo>();
RegisterNativeClassVariable<Foo,int>( “variable”, offsetof(Foo, variable) );
RegisterNativeClassMethod<Foo,int,int,std::string>( “DoSomething”, &Foo::DoSomething );
+ Clang
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 );
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 ); }
}
C++를위한 C# 인터페이스자동생성
1. .NET reflection을사용하여 DLL 안의인터페이스들추적
2. DLL 인터페이스를호출할수있는 C++ 코드생성
3. C++ 게임코드에서인터페이스코드를통해 DLL의코드를 C++ 처럼호출
4. C++ 게임코드컴파일시인터페이스코드를함께컴파일
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에의해등록된함수포인터
C# 인터페이스생성예제 (Embedded)
// C# 소스코드[Export]
class Foo
{
int variable;
int DoSomething( int arg1, string arg2 ) { … }
}
// 자동생성된 C++용인터페이스코드class Foo
{
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;
}
};
언어간개체결합
언어간결합의깊이
C++ Class
C++ variables
C++ methods
C# Class
C# variables
C# methods
Hybrid Class
C++ variables
C# variables
C++ methods
C# methods
오브젝트참조클래스형변환
C++ 기능모듈
C# 기능모듈
표준호출단순타입
모듈간결합 다른객체간결합 객체수준결합
개체생명주기관리
객체수준결합
단일메모리의구역분할
• C# 클래스선언에서 C++ 클래스의영역미리선언필요
• 개체의수명은 C#에의해서만관리되어야함
• C++ new/delete 사용불가
객체수준결합
개체분할, 논리적결합
• 독립적인 C++개체와 C#개체• 필요할때에만상대개체생성가능
• 자동생성된소스코드빌드에유리(partial class)
• 개체수명관리가핵심
Garbage Collection vs new/delete
mono_gchandle_new
mono_gchandle_free
C++ 개체가소유자
new / delete
C# 개체가소유자 상호참조
gchandle refcount
상대개체동적생성예제
// 자동생성된 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);
}
};
성능주의
3 1
Mono GC에의해제거가능
Mono GC에의해제거방지
GC handle
Mono GC
Native GC
0
Mono GC에의해제거됨
삭제됨
참조카운트
살아있는쌍 GC 활성화 Native 삭제
삭제 - Garbage Collector 연동
GC handle 삭제
최종형태
Data Engine
부분최적화
과거의게임코드
결론
Roslyn
.NET Native, IL2CPP
CppSharp, Script#
거의모든플랫폼에서 .NET과 C# 사용가능
언어간혼합의자동화 → 더욱높은생산성과이식성
.NET과 C#은여전히진화중
Q&A
감사합니다
부록
참고자료
.NET– Reflection: http://msdn.microsoft.com/en-us/library/f7ykdhsy(v=vs.110).aspx
– Garbage Collection: http://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx
Mono– http://www.mono-project.com
– Compiling Mono: http://www.mono-project.com/docs/compiling-mono/compiling-from-git/
– Embedding Mono: http://www.mono-project.com/docs/advanced/embedding/
– InterOp with Native: http://www.mono-project.com/docs/advanced/pinvoke/
– CppSharp: https://github.com/mono/CppSharp
– CXXI: http://tirania.org/blog/archive/2011/Dec-19.html
– Mono for Unreal Engine: http://mono-ue.github.io/
Clang– http://clang.llvm.org
– Python with Clang: http://eli.thegreenplace.net/2011/07/03/parsing-c-in-python-with-clang
iOS 시뮬레이터용 Mono 런타임 configure
• --build=i386-apple-darwin13.0.0
• CC, CXX=<Xcodepath>/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin 안의 gcc와 g++
• CFLAGS, CXXFLAGS– -arch i386 –miphoneos-version-min=<최소지원버전>
– -isysroot=<Xcodepath>/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator<버전>.sdk
• LD, AS, AR, LIBTOOL, STRIP, RANLIB=<iPhoneSimulator SDK>/usr/bin 안에있는툴들사용
configure
iOS 기기용 Mono 런타임 configure
• --host=arm-apple-darwin10
• --target=arm-apple-darwin10
• CC, CXX=<Xcodepath>/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin 안의 clang과 clang++
• CFLAGS, CXXFLAGS
– -arch armv7
– -isysroot=<Xcodepath>/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS???.sdk
• LD, AS, AR, LIBTOOL, STRIP, RANLIB=<Xcodepath>/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin 안에있는툴들사용
configureMono 3.2의경우 XCode 4.x를사용해야함
Android 기기용 Mono 런타임 configure
• --host=arm-linux-androideabi
• --target=arm-linux-androideabi
• CC, CXX=<Xcodepath>/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin 안의 clang과 clang++
• CFLAGS, CXXFLAGS– -march=armv7-a
– -mfloat-abi=softfp
– -mfpu=neon
– --sysroot=<NDK_ROOT>/platforms/android-<version>/arch-arm
• LD, AS, AR, LIBTOOL, STRIP, RANLIB 설정불필요• --libdir <NDK_ROOT>/platforms/android-<version>/arch-
arm/usr/lib
configure
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로 구현가능
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 );
}
};