프로퍼티에 대해서 열심히 찾아보다 보니 이해할 수 없는 코드로 가득하지만 동작은 하는 그런... 코드를 찾았다.
바로 이 사이트에서.
https://www.codeproject.com/Articles/118921/C-Properties
C++ Properties
C#-Style Properties in C++
www.codeproject.com
#define PROPERTY(t,n) __declspec( property (put = property__set_##n, get = property__get_##n)) t n; \
typedef t property__tmp_type_##n
#define READONLY_PROPERTY(t,n) __declspec( property (get = property__get_##n) ) t n;\
typedef t property__tmp_type_##n
#define WRITEONLY_PROPERTY(t,n) __declspec( property (put = property__set_##n) ) t n;\
typedef t property__tmp_type_##n
#define GET(n) property__tmp_type_##n property__get_##n()
#define SET(n) void property__set_##n(const property__tmp_type_##n& value)
class Something
{
private:
int _x;
public:
PROPERTY(int, x);
GET(x)
{
return _x;
}
SET(x)
{
_x = value;
}
};
int main()
{
Something someObject;
someObject.x = 50;
int x = someObject.x;
}
이런 식으로 매크로를 활용하여 C# 스타일로 만들어 내는 것인데, __decelspec이라던가 property, ##n 이런게 다 뭔지는 모르겠다...
어쨌든 Read / write를 구분할 수 있으며, 멤버에도 접근을 할 수 있게 되었다. 근데 복사는 안되고 포인터나 레퍼런스로 하면 잘 되는 것 같다. 예를 들면 이런 식으로 사용 가능하다.
struct Foo {
int member = 1;
};
class Something
{
private:
Foo* _f;
public:
PROPERTY(Foo*, f);
GET(f)
{
cout << "Get!!" << endl;
return f;
}
SET(f)
{
f = value;
}
};
int main()
{
Something someObject;
someObject.f->member = 3;
}
이렇게 포인터를 저장해두면 멤버에 접근해서 값을 수정해도 되지만, 다음과 같이 하면 불가능하다.
struct Foo {
int member = 1;
};
class Something
{
private:
Foo _f;
public:
PROPERTY(Foo, f);
GET(f)
{
cout << "Get!!" << endl;
return f;
}
SET(f)
{
f = value;
}
};
int main()
{
Something someObject;
someObject.f->member = 3; //빨간줄 쳐짐! -> 식이 수정할 수 있는 lvalue여야 합니다.
}
왜 이런 일이 발생하는지, 어떻게 하면 개선할 수 있을지 알려면 결국 매크로에 써진 의미를 파악해야 할 것 같다.
다음 사이트에서 개념을 조금 알 수 있었다. MS에서 만든 확장문법이고 C++ 표준이 아니라고 한다..
https://tmshdnqhem.wordpress.com/2018/04/26/declspec%EC%97%90-%EB%8C%80%ED%95%B4/
declspec에 대해
declspec에 대해 declspec에 대해 동기 계획 declspce란? 형식 정의 property 원형 C#의 Getter/Setter 프로퍼티 흉내내기 단점 참조한 포스트 dll의 Export와 Import dllimport의 작동원리 참조한 포스트
tmshdnqhem.wordpress.com
https://docs.microsoft.com/en-us/cpp/cpp/property-cpp?view=vs-2019
property (C++)
property (C++) In this article --> Microsoft Specific This attribute can be applied to non-static "virtual data members" in a class or structure definition. The compiler treats these "virtual data members" as data members by changing their references into
docs.microsoft.com
비표준이면 사용하지 않는게 좋을까? 고민된다. 한 10분정도 검색도 하면서 생각해봤는데, 결국 이 작업은 Getter, Setter 함수를 쓰기 싫어서 쓰는 기능이니까... 원래 어떻게 써야하는지 알고 있으니 괜찮지 않을까 하는 생각이 들었다.
그럼 이제 다음으로 넘어가자.
먼저 이미 생성된 윈도우에 다른 WndProc 함수를 등록하는 방법은 다음과 같다.
SetWindowLongPtr(handle, GWLP_WNDPROC, funcPtr);
Windows.h 에 정의된 함수로, 지정된 창(핸들handle)에 WndProc 함수를 funcPtr(LONGPTR)로 변경해주는 함수이다.
내가 넣고싶은 함수를 reinterpret_cast<LONGPTR>로 타입캐스팅해서 집어넣으면 된다.
이걸로 Admin에 있는 WndProc 함수를 넣어주었다. 하지만 Wndproc 함수는 반드시 전역으로 선언되어야 하는 함수라서 Input으로 메세지를 보내려면 Admin에서 리디렉팅을 해줘야 한다.
다음으로 한건 콘솔 창을 하나 더 띄워서 콘솔창에 로그를 표시할 수 있도록 하여 디버깅을 용이하게 하려고 했다.
콘솔창 띄우는 방법은
AllocConsole();
로 콘솔창을 생성후
GetStdHandle(STD_OUTPUT_HANDLE);
콘솔창의 handle값을 받아오는 식이다.
이 handle을 이용해 Writefile 함수로 콘솔창에 메세지를 보낼 수 있다.
이제 ProcessMessage 함수를 만들어서 받은 메세지를 Translate하고 Dispatch하도록 해야한다. 여러 개의 윈도우를 만들 것을 가정하고 개발했기 때문에 WM_CLOSE 메세지가 온 경우 해당하는 hwnd를 가진 Window 클래스도 같이 삭제해 주어야 한다.
여기서 내가 vector에 대한 사용법 숙지가 제대로 안되어 있음을 통감했다.
벡터에 있는 erase 함수를 for문에서 호출할 때 아주 큰 실수가 있어서 에러가 났다. 그래서 여기에 다시 정리해둔다.
for ( auto it : vector)
{
if(삭제 조건)
{
★★★매우 중요
it = vector.erase(it);
}
else
++it; ★★★중요!!
}
지우고 나서 받은 이터레이터를 다시 대입해주어야 한다는 것을 잊고 있었다.
막상 만들고 나니 DestroyWindow(hwnd); 를 호출하기 위해서는 HWND값이 필요해서 vector로 저장을 해버리면 순회하면서 탐색해서 원하는 Window 클래스를 가져와야 해서 그럴 바에 unordered_map(C#에서의 Dictionary)를 써야겠다고 생각했다.
pair<hwnd, Window*> 라고 보면 될 것 같다.
이 모든 조치를 다 취해주고 난 후 shared_ptr에 의해 window가 삭제되길 기대했건만... 또 실패했다. 이상하게 소멸자 함수가 실행이 안되더라.
이유는 드디어 찾았는데, 컨테이너를 다른 곳에 또 복사를 해버리는 바보짓을 해버렸기 때문이었다. 복사 연산이 일어나지 않게 제어를 잘 해야겠구나.
또 문제가 생겼다. WndProc 함수는 콜백 함수라서 콜백함수에서 컨테이너에 있는 Window 클래스를 지워버리면 ProcessMessages 함수에서 컨테이너 순회를 돌다가 갑자기 삭제된 것 때문에 에러가 발생했다. 아직 시작도 안했지만 멀티스레드 프로그래밍이 얼마나 힘들지 예상이 간다. 시점을 서로 동기화할 수 없어서 어쩔 수 없이 ProcessMessage 함수에서 컨테이너에 있는 Window클래스를 지우도록 만들었다.
bool Admin::ProcessMessages()
{
MSG msg = { 0 };
auto it = windows.container.begin();
for (; it != windows.container.end();)
{
it->second->ProcessMessages(msg);
if (msg.message == WM_CLOSE)
{
it = windows.container.erase(it);
}
else
++it;
}
return true;
}
대략 이런 식이다. WndProc에서 클래스를 삭제하는 건 위험하다는 걸 깨달았다...
돌고 돌아 드디어 Input 클래스를 다시 만들 수 있게 되었다. 다른 건 문제 없을 것 같은데 키의 지속적인 입력,
즉 유니티에서 Input::GetKey()함수를 어떤 식으로 구현해야 할지 찾아봐야겠다.