너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
그리고 이 라이브러리는 서버에서 사용하는 것을 생각했기 때문에 서버-클라이언트 연결과 서버-서버 연결을 가정하여 클라이언트 접속을 위한 Port와 서버 접속을 위한 Port 두 개를 생성합니다.
서버-서버 :: 서버 접속을 위한 Port
UINT16 ServerPort = configReader.GetValue<UINT16>(ACE_TEXT("server_port");
서버-클라이언트 :: 클라이언트 접속을 위한 Port
UINT16 ClientPort = configReader.GetValue<UINT16>(ACE_TEXT("client_port");
ACE는 정말 좋은 네트웍 프레임웍이지만 너무 범용적이라서 고도로
추상화 되었고, 기능이 다양하여 분석하기가 쉽지 않아서 사용에 어려움이 있습니다.
HalfNetwork는 javawork님이 다년간 온라인 게임 서버를
만든 경험을 바탕으로 서버 프로그램을 만들 때 필수적이고 자주 사용하는 기능을 쉽게 사용할 수 있도록 만든 것으로 서버와 클라이언트 용 둘 다
지원합니다. 그래서 ACE의 장점이 높은 성능과 다양한 기능, 멀티 플랫폼 지원을 이어 받으면서 아주 쉽게 서버 프로그램을 만들 수 있습니다(즉 ACE를 몰라도 괜찮습니다).
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
C++/CLI를 가장 자주 사용하는 경우가 아마 C++로 만든 코드를 닷넷에서 사용하고 싶을 때라고 생각합니다. 게임
개발에서는 보통 3D 툴을 만들 때 C++/CLI를 유용하게 사용합니다.
C++/CLI를 툴 개발에서 사용할 때는 보통 두 가지로 사용하는데
첫 번째는 C++로 만든 3D 엔진 코어를 DLL로 만들어서 C#을 이용하여 툴을 만들던가, 두 번째는 C++/CLI에서 관리코드와 비관리코드를 같이 사용할 때입니다.
제가 앞서 C++/CLI에 대해서 설명한 것들은 C++/CLI로만 프로그래밍 하는 것보다는 관리코드와 비관리코드를 같이 사용할 때를 생각하여 이때 필요로 하는
부분을 중심으로 했습니다. 그래서 아직 C++/CLI에 대한 많은
부분들이 빠져있습니다. 빠진 부분에 관심이 있는 분들은 C++/CLI
책이나 MSDN을 통해서 공부하시기 바랍니다.
앞으로 C++로 만든 코드를 C++/CLI를 사용하여 닷넷용 클래스 라이브러리를 만든 후 이것을 C#에서
사용하여 프로그램을 만든다는 경우로 간단한 프로그램을 만들면서 C++/CLI을 어떻게 사용하는지 몇 회에 걸쳐서 설명하겠습니다.
만들 프로그램은 네트웍 서버 애플리케이션입니다(제가 회사에서
맡은 직무가 온라인 게임 서버 개발입니다). ^^
네트웍 라이브러리(또는 프레임웍)
네트웍 프로그래밍은 일반적인 프로그래밍은 아니고 시스템 프로그래밍입니다. 조금은 특수한 분야라고 할 수 있습니다.
그러나 한국의 게임업계는 거의 대부분이 온라인
게임이라서 네트웍 프로그래밍이라는 것이 특수한 부분은 아닙니다.
보통 게임 프로그래밍이라는 것은 그래픽스 프로그래밍을 쉽게 떠올리고, 시중에
관련 책이나 학원들이 있습니다. 그러나 네트웍 프로그래밍에 관한 책이나 학원은 별로 없습니다(특히 전문 학원은 없는 것으로 알고 있습니다).
온라인 게임에서의
네트웍 프로그래밍을 한다라는 것은 대용량 네트웍 프로그램을 만들어야 한다는 것입니다. 그래서 온라인
게임 서버 프로그램을 만들려면 시스템 프로그래밍에 관한 지식과 네트웍 프로그래밍에 대한 경험이 필요합니다.
이런 이유 때문에 서버 프로그램을 만든다는 것은 쉽지 않습니다. 그러나
서버 프로그램을 쉽게 만들 수 있도록 도와주는 라이브러리를 사용할 수 있다면 어려움은 한층 작아질 것입니다.
위 코드 중 #pragma unmanaged 지시어 이하는 컴파일러에서
비관리코드로 취급합니다. 그리고 #pragma managed 이하는
관리코드로 취급합니다. 내용이 간단하고 어려운 부분이 없기 때문에 따로 자세한 설명은 생략하겠습니다.
C++/CLI는 단순하게 닷넷 플랫폼에서 사용할 수 있는 C++ 언어라기 보다는 C++ 언어의 부족한 부분을 진화 시킨 언어라고도
생각할 수 있는 부분이 꽤 있습니다. 그러나 C++/CLI는 C++과 C#의 중간의 애매한
위치에 있어서 양쪽 프로그래머 모두에게 별로 호응을 받지 못하는 것 같습니다. 그래서 C++/CLI 관련 글을 제가 처음에 생각했던 것보다는 조금 일찍 끝낼려고 합니다.
C++/CLI를 사용하는 대부분의 프로그래머들은 아마 기존의
비관리 코드를 관리코드에서 사용하고 싶을 때라고 생각합니다. C++/CLI의 기능 소개는 이번으로 일단 끝내고 앞으로는 비관리코드와의 연계에 대해서 실제 사례 보여주면서 설명하려고 합니다.
사례는 오픈 소스 네트워크 라이브러리인 HalfNetwork를
C++/CLI를 사용하여 관리코드에서 사용할 수 있도록 wrapping한
후 이것을 관리코드에서 사용할 예정입니다.
HalfNetwork는 온라인 게임 서버 프로그래머인 임영기님이
만든 것으로 ACE 라는 오픈 소스 네트워크 라이브러리를 사용하기 편하게 만든 라이브러리입니다.
C++/CLI은 네이티브
C++과 다르게 자료구조 배열을 사용하기 위해서는 array 컨테이너를 사용합니다.
array 컨테이너는 기본적으로
non-CLI 오브젝트는 사용할 수가 없습니다. 그러나non-CLI 오브젝트가 포인터라면 사용할 수 있습니다.
아래는 array 컨테이너에 non-CLI 오브젝트를 사용한 경우입니다.
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative>^
arr = gcnew array<CNative>(2);
return 0;
}
빌드하면 위에 이야기 했듯이 아래와 같은 빌드 에러가 발생합니다.
그럼 이번에는 non-CLI 오브젝트의 포인터를 사용해 보겠습니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative*>^
arr = gcnew array<CNative*>(2);
for(int
i=0; i<arr->Length; i++)
{
arr[i]
= new CNative();
}
getchar();
return 0;
}
이번에는 빌드에 문제가 없어서 아래와 같이 실행결과가 나옵니다.
그러나 위의 실행 결과를 보면 이상한 점을 발견하실 수 있을 것입니다. 그것은 CNative 오브젝트의 생성자는 호출하지만 파괴자는 호출되지 않은 것입니다. 이것은 array 컨테이너는 CLI 객체이므로 GC에서 관리하지만 non-CLI 오브젝트를 포인터 타입으로 사용한 것은 GC에서 관리하지 않으므로 만약 array가 GC에서
소멸 되기 전에 array에 담겨있는 non-CLI 오브젝트를
메모리(new로 할당한)를 해제하지 않으면 메모리 해제가 되지 않아서 메모리 릭이 발생합니다.
그래서 아래와 같이 array가 GC에서 소멸되기 전에 메모리를 해제하도록 해야합니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative*>^
arr = gcnew array<CNative*>(2);
for(int
i=0; i<arr->Length; i++)
{
arr[i]
= new CNative();
}
for(int
i=0; i<arr->Length; i++)
{
delete
arr[i];
}
getchar();
return 0;
}
실행 결과
이번에는 CNative의 파괴자가 제대로 호출되고 있습니다.
출처
도서 "C++/CLI In Action"
C++/CLI를 공부하시는 분들은 "C++/CLI In Action" 책을 꼭 한번 보시기를 추천합니다.
static 생성자는 클래스의 생성자에서 static 멤버를
초기화 하고 싶을 때 사용합니다.
ref
class, value class, interface에서 사용할
수 있습니다.
#include
"stdafx.h"
#include
<iostream>
using
namespace System;
ref class
A {
public:
static int a_;
static A()
{
a_ += 10;
}
};
ref class
B {
public:
static int b_;
static B()
{
//a_ += 10; // error
b_ += 10;
}
};
ref class
C {
public:
static int c_ = 100;
static C()
{
c_ = 10;
}
};
int
main()
{
Console::WriteLine(A::a_);
A::A();
Console::WriteLine(A::a_);
Console::WriteLine(B::b_);
Console::WriteLine(C::c_);
getchar();
return 0;
}
< 결과 >
static 생성자는 런타임에서 호출하기 때문에 클래스 A의
멤버 a_는 이미 10으로 설정되어 있습니다. 그리고 이미 런타임에서 호출하였기 때문에 명시적으로 A::A()를
호출해도 실제로는 호출되지 않습니다.
클래스
B의 경우 static 생성자에서 비 static 멤버를
호출하면 에러가 발생합니다.
클래스
C의 경우 static 멤버 c_를 선언과 동시에
초기화 했지만 런타임에서 static 생성자를 호출하여 값이 10으로
설정되었습니다.
initonly
initonly로 선언된 멤버는 생성자에서만 값을 설정할 수 있습니다. 그리고 initonly static로 선언된 멤버는 static 생성자에서만 값을 설정할 수 있습니다.
ref class C
{
public:
initonly staticint x;
initonly staticint y;
initonly int z;
static C()
{
x =1;
y =2;
// z = 3; // Error
}
C()
{
// A = 2; // Error
z =3;
}
void sfunc()
{
// x = 5; // Error
// z = 5; // Error
}
};
int main()
{
System::Console::WriteLine(C::x);
System::Console::WriteLine(C::y);
C c;
System::Console::WriteLine(c.z);
return0;
}
literal
literal로 선언된 멤버는 선언과 동시에 값을 설정하고 이후 쓰기는 불가능합니다. 오직 읽기만 가능합니다.
usingnamespaceSystem;
ref class C
{
public:
literal String^ S ="Hello";
literal int I =100;
};
int main()
{
Console::WriteLine(C::S);
Console::WriteLine(C::I);
return0;
}
참고 http://cppcli.shacknet.nu/cli:static%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF http://cppcli.shacknet.nu/cli:initonly http://cppcli.shacknet.nu/cli:literal
위의 예의 델리게이트들은 모두 void를 반환하고 파라미터가 없는 것인데 당연하듯이 반환 값이나 파라미터를 가질 수 있습니다.
델리게이트의 비동기 실행
델리게이트는 비동기 실행을 지원합니다. 비동기 실행은 처리를
요청한 후 종료를 기다리지 않은 호출한 곳으로 제어를 넘겨줍니다. 델리게이트 함수가 긴 시간을 필요로
하는 작업인 경우 비동기 실행을 이용하면 프로그램의 응답성을 높일 수 있습니다.
델리게이트의 비동기 실행은 스레드를 사용합니다. 이런 경우 비동기
실행을 할 때마다 스레드의 생성과 소멸에 부담을 느낄 수도 있지만 델리게이트는 닷넷의 기능을 잘 활용하여 스레드를 생성/삭제하지 않고 스레드 풀에 있는 스레드를 사용하므로 스레드 사용에 대한 부담이 작습니다.
비동기 실행을 할 때는 주의해야 할 점이 있습니다. 비동기 실행을
하는 경우 델리게이트에는 꼭 하나의 함수만 등록해야 합니다. 만약 2개
이상 등록하였다면 예외가 발생합니다.
비동기 실행은 BeginInvoke()를 사용하고, 만약 종료를 기다리고 싶다면 EndInvoke()를 사용합니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
delegate void MyDele(void);
void myfunc(void)
{
System::Threading::Thread::Sleep(3000);
}
int main(array<System::String ^>
^args)
{
MyDele^
dele = gcnew MyDele(&myfunc);
Console::WriteLine(L"1");
IAsyncResult^
result = dele->BeginInvoke(nullptr,nullptr);
Console::WriteLine(L"2");
dele->EndInvoke(result);
Console::WriteLine(L"3");
getchar();
return 0;
}
위 코드를 실행하면 '2'가 찍힌 이후 3초가 지난 이후에 3이 찍힙니다.
참고
http://cppcli.shacknet.nu/cli:delegate
http://cppcli.shacknet.nu/cli:delegate%E3%81%9D%E3%81%AE2
C++/CLI 공부를 해보면 좀 애매한면이 있지만 요즘 시대에 맞는 기능이 꽤 많아서 MS의 새로운 C++ 표준이라는 생각이 들기도 합니다. 아마 MS가 단독으로 새로운 C++ 표준을 만든다면 지금의 C++/CLI와 비슷한 모습이 될 것 같습니다(참고로 C++/CLI는 꼭 닷넷을 위해 만들어진 언어가 아닙니다).
첫 번째 방법에서 std::string을 사용한 이유는 다름이 아니고 메모리 확보 때문입니다.
마샬링을 통해서 char*와 wchar_t*에 메모리 주소를 저장합니다. 문자열 그 자체를 복사하는 것이 아닙니다. 그래서 변환한 문자열을
저장할 메모리 주소를 확보하고 사용 후에는 해제를 해야 합니다. 메모리 확보와 해제를 위해서 marshal_context를 사용합니다.
marshal_context는 변환에 필요한 메모리를 확보하고, 스코프를 벗어날
때 메모리를 해제합니다.
const char*
s2;
const
wchar_t* s3;
{
marshal_context ctx;
s2 = ctx.marshal_as<const
char*>(s0);
s3 = ctx.marshal_as<const
wchar_t*>(s0);
}
String^을 C/C++ 문자열로 변환할 때는 std::string + marshal_as 나 marshal_context 둘
중 하나를 선택하여 사용합니다.
내용중에
marshal_context ctx;로만 할 경우 해당 행에서 Reentrance관련 오류가 발생합니다. MDA를 사용하지 않음으로 했더니, AccesViolation 오류가 발생하구요.
결국 marshal_context^ ctx = gcnew marshal_cotext();로 하여 진행 하였습니다.
Choyee // 파라미터가 char[32]로 되어 있는 경우는 마셜링 유틸함수에서는 지원하지 않으니 수작업을 하셔야 될 것 같습니다. 그리고 MDA 사용하지 않음이 뭔지 저는 잘 모르겠네요^^;;. 참고로 C++/CLI는 지역 변수의 경우는
marshal_context ctx; 코딩하면 컴파일러가 자동으로
marshal_context^ ctx = gcnew marshal_cotext(); 만들어주기 때문에 gcnew를 하지 않아도 됩니다(정확하게는 컴파일러가 코드를 만들어주기 때문에 사용하지 않는건 아니죠)
setlocale로 국가를 설정하고(직접 나라를 지정할 수도 있고, 아니면 위처럼 시스템 설정에 따라가도록 할 수도 있습니다), ‘cout’ 대신
‘wcout’를 사용합니다.
관리코드 문자열과 비관리코드 문자열간의 변환에
따른 성능
C++로 만드는 프로그램은 보통 고성능을 원하는 프로그램이므로 보통 C++ 프로그래머는 성능에 민감합니다. 마샬링은 공짜가 아닙니다만
많은 양을 아주 빈번하게 마샬링 하는 것이 아니면 성능에 너무 신경 쓰지 않아도 됩니다. 다만 기본적으로
관리코드의 문자열은 유니코드입니다. 그래서 비관리코드의 문자열이
ANSI 코드라면 유니코드를 사용했을 때 보다 더 많은 시간이 걸립니다(정확한 수치는 잘
모르지만 ANSI가 유니코드보다 3배정도 더 걸린다고도 합니다). 그래서 관리코드와 비관리코드를 같이 사용할 때는 가능한 유니코드를 사용하는 것이 훨씬 좋습니다.
댓글을 달아 주세요
좋은 자료 감사합니다. ^^
감사합니다. 흥배님 덕분에 많이 배웠습니다.
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
새로운 아이디어와 혁신에 마음을 여십시오. 자신의 단어 겁쟁이 말하는 찾아내는 경우에, 기회는 열려 딱딱한 아니라입니다. 아이디어는 돈이 많은 친구 또는 직원이 당신이 그들의 아이디어를 받아들이려고하지 않는 생각을 염두에두고 있습니다 인쇄할 수 있습니다.
마케팅 조사는 매출과 이익에 영향을 미치지 시장 동향의 식별을 포함합니다. 마케팅 조사는 일반적인 지식보다 훨씬 더 많은 정보를 제공합니다.
C++/CLI가 필요해서 보기만 하다가 감사하다는 말은 해야할거 같아서 글을 썼어요...