H3 동영상이 공개가 되었다..
참 열악한 상황해서 개발했는데.. 아둥바둥 노력해서 모양세가 나오는것 보니
참 눈물이 앞을 가린다.
지금은 자리를 옮겼지만 한때 나의 노력이 녹아있기 때문에 잘되었으면 하는 바램이다.
H3 동영상이 공개가 되었다..
참 열악한 상황해서 개발했는데.. 아둥바둥 노력해서 모양세가 나오는것 보니
참 눈물이 앞을 가린다.
지금은 자리를 옮겼지만 한때 나의 노력이 녹아있기 때문에 잘되었으면 하는 바램이다.
펌) http://kslive.tistory.com/23
◎ 동기화의 필요성
다수의 스레드가 하나의 데이터를 동시에 엑세스를 하는 경우는 우리가 스레드 프로그래밍을 하는 동안에는 피할 수 없는 현상이다. 다수의 스레드가 하나의 데이터를 읽기만 한다면야 이 데이터에대한 동기화의 필요성은 필요하지 않을 것이다. 왜냐하면 읽기만 한다는건 데이터가 변하지 않는다는것을 말하기때문이다. 그러므로 어느 스레드에서든 해당 데이터의 값은 동일할 것이다. 하지만 다수의 스레드가 하나의 데이터에 대한 읽기와 쓰기의 작업을 동시에 한다면 스레드의 동기화를 사용하지 않는다면 이 데이터에 대한 무결성을 입증할 방법이 존재할 수 있을까? 답은 없다이다. CPU는 한줄의 대입 연산을 실행하는 경우에도 그에 해당하는 여러줄의 기계어 코드를 만들기때문에 원자적 접근이 되지않기 때문이다.
◎ 원자적 접근
스레드 동기화는 원자적 접근을 보장해주는 일이다. 원자접근은 같은 시간에 같은 리소스에 다른 스레드가 접근하지 않도록 하면서 해당 스레드가 리소스에 접근하도록 하는 일일 말한다.
◎ Interlocked 계열의 함수
Interlocked 계열의 함수는 하나의 long형 변수의 값을 변경할때 원자접근을 수행하는 함수를 말한다. Interlocked 계열의 함수는 다음과 같이 존재한다. (현재는 더 존재할 수 있다.)
. LONG InterlockedIncrement (LONG volatile* Addend) : Addend 에 저장되어있는 long형 변수를 1 증가 시킨다. Rv는 증가된 후의 값이다.
. LONG InterlockedDecrement(LONG volatile* Addend) : Addend 에 저장되어있는 long형 변수를 1 감소 시킨다. Rv는 감소된 후의 값이다
. LONG InterlockedExchage(LONG volatile* Target, LONG Value) : Target에 저장되어있는값을 Value값으로 변경한다. Rv는 최초 Target에 저장되어있는 값이다.
.PVOID InterlockedExchangePointer(PVOID volatile* Target, PVOID Value) : Target에 저장되어있는 값을 Value에 저장되었는 값으로 변경한다. Rv는 최초 Target에 저장되어있는 값이 있는 번지를 리턴한다.
.LONG InterlockedExchangeAdd(LONG volatile* Target, LONG Increment) : Target에 저장되어있는값에 Increment값을 더한다. Rv는 최초 Target에 저장되어있는 값이다.
.LONG InterlockedCompareExchange(LONG volatile* Dest, LONG Exchange, LONG Compare) : Dest에 저장되어있는값이 Compare와 동일하면 Dest의 값을 Exchange으로 변경한다. Rv는 최초 Dest에 저장되어있는 값이다.
.PVOID InterlockedCompareExchangePointer(PVOID* pDest, PVOID pExch, PVOID pCompare) : pDdest에 저장되어있는 값이 pCompare와 동일하면 pDest의값은 pExch값으로 변경된다. Rv는 최초 pDest의 값이다.
◎ CRITICAL_SECTION
CRITICAL_SECTION 은 윈도우에서 제공하는 동기화 객체 중의 하나로서 동기화 객체 중 유일하게 유저모드에서 실행된다.(커널모드로의 전환이 일어나면 CPU사이클을 많이 잡아먹는다. 한마디로 느리다는것이다.)
CRITICAL_SECTION의 사용법은 다음과 같다.
1. CRITICAL_SECTION 구조체를 초기화 한다.
void InitializeCriticalSection(LPCRITICAL_SECTION pCs);
사용할 CRITICAL_SECTION 객체의 주소를 넘겨준다.
2. 임계영역에 진입한다.
VOID EnterCriticalSection(LPCRITICAL_SECTION pCs);
3. 해당작업을 한다. (원자성을 보장한다.)
4. 임계영역을 탈출한다
void LeaveCriticalSection(LPCRITICAL_SECTION pCs);
※ 인자로 들어가는 CRITICAL_SECTION 구조체의 주소가 동일해야한다.
※ 3번에서 원자성을 보장한다는 얘기는 해당작업을 하는 부분에서 사용하는 공통된 데이터에 접근 하는 모든 스레드에서 EnterCriticalSection와 LeaveCriticalSection를 사용해야 하며, 인자로 넘어가는 CRITICAL_SECTION 의 구조체의 주소는 모두 동일해야 한다.
위의 순서에서 사용된 함수들에 대한 설명은 다음과 같다.
void InitializeCriticalSection(LPCRITICAL_SECTION pCs)
: CRITICAL_SECTION 구조체를 초기화한다. 이 함수는 단지 멤버변수를 설정하기 때문에 실패하지 않는다. 이 함수는 EnterCriticalSection을 호출하기전에 반드시 호출되어야한다. 초기화되지않은 CRITICAL_SECTION 에 진입을 시도할시 결과는 정의되지 않는다고 SDK문서에 명시되어있다.
※ 이 함수가 실패는 하지 않지만 예외상황을 발생시킬수있다. 위 함수는 디버깅 정보가 있는 메모리 블럭을 할당하기 때문에 메모리 블럭 할당에 실패하였을 경우 STATUS_NO_MEMORY 예외를 던진다. SEH를 이용하여 예외를 Catch할수있다.
.void DeleteCriticalSection(LPCRITICAL_SECTION pCs)
: DeleteCriticalSection은 구조체 내의 멤버 변수를 재설정한다(제거). 어떤 스레드가 여전히 이 구조체를 사용하고있다면 자연히 임계영역은 삭제할 수 없다. SDK 문서에서는 이렇게 할때 결과는 정의되지 않는다고 나와있다.
.VOID EnterCriticalSection(LPCRITICAL_SECTION pCs)
: 임계영역에 진입을 시도한다.
※ 한 CRITICAL_SECTION 에 대해 두번의 EnterCriticalSection은 시도할경우 최초 진입 성공시 두번째 EnterCriticalSection시에는 CRITICAL_SECTION의 접근 스레드를 표시하는 변수를 업데이트하고 바로 리턴한다. (Enter가 두번이면 Leave도 두번호출해야한다.)
BOOL TryEnterCriticalSection(LPCRITICAL_SECTION pCs)
: 임계영역에 진입을 시도한다. 이 함수는 이함수를 호출하는 스레드가 절대 대기상태로 진입을 하지 않는다. 이 함수가 나타내는 Rv값은 다른 스레드에서 해당 임계영역에 진입을 하고있는 상태면 FALSE, 아니면 TRUE이다. 이 함수는 해당 임계영역에 진입이 가능하면 진입을 시도하고 아닐경우 바로 리턴하여(FALSE) 다른 일을 한다.
※ Windows 2000 이상에서만 지원한다.
.void LeaveCriticalSection(LPCRITICAL_SECTION pCs)
: 이 함수는 호출한 CRITICAL_SECTION에 접근하고있는 스레드의 개수를 하나 감소시킨다. 접근스레드의 개수가 1이상이면 감소 후 바로 리턴하고.개수가 0개이면EnterCriticalSection을 호출한 다른 스레드가 대기 상태에 있는지 검사 후 존재하면 해당 스레드에게 맞게 CRITICAL_SECTION 구조체를 업데이트하고 바로 해당스레드를 스케쥴한다.(여러개가 존재할경우 공정하게 선택한단다.-_-;;) 대기하는 스레드가 없을 경우 멤버함수를 업데이트하여 접근하는 스레드가 없음을 나타낸다.
◎ 스핀록
스레드가 EnterCriticalSection을 호출했을 시 해당 임계영역을 다른 스레드가 소유하고있으면 호출한 스레드는 바로 대기모드로 전환한다. 이는 유저모드에서 커널모드로의 전환을 의미한다.(CPU사이클을 많이 소모한다.) 이 전환은 소모가 매우 크다. 그래서 MS는 이 비용을 줄이기 위해 임계 영역안에 스핀록을 만들었다. EnterCriticalSection을 호출했을시 EnterCriticalSection은 리소스를 몇번 동안 요청을 시도하기위해 시핀록을 사용해 루프를 돈다. 오직 모든 시도가 실패하게 될때 스레드는 커널모드로(대기모드) 전환된다.
스핀록을 사용하기위해서는 다음과 같은 함수를 사용한다.
BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION pCs, DWORD dwSpinCount);
: InitliazeCriticalSection을 사용하지 말고 위 함수를 사용하여 CRITICAL_SECTION을 초기화 해야한다. dwSpincount는 리소스 재요청 횟수이다. 위 횟수는 0~ 0x00FFFFFF 사이의 어떤값도 될수 있다. InitliazeCriticalSection 은 항상 성공하며 가끔 예외(?)를 던지지만, 이 함수는 메모리 할당에 실패하면 FALSE를 리턴한다. 훨씬 좋다. 사용하자.
※ 프로세서가 하나인 머신에서는 dwSpinCount무시되고 항상 0 으로 설정된다.
DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION pCs, DWORD dwSpinCount)
: 위 함수는 해당 CRITICAL_SECTION객체의 스핀횟수를 변경한다.
※ 역시 프로세서가 하나인 머신에서는 위의 dwSpinCount값은 무시되고 0으로 설정된다.
※ 임계영역에서 스핀록을 사용하면 손해보지는 않는다. 지원을 하면 항상 사용하자.
◎ 임계영역 & 에러 핸들링
내부적으로 임계 영역은 두개 이상의 스레드가 동시에 임계 영역을 가지고 경쟁할 경우 이벤트 커널 오브젝트를 사용한다. 이런 경쟁이 드물면 시스템은 이벤트 커널 오브젝트를 생성하지 않는다. 메모리가 적은 상황에서 임계 영역에대한 경쟁 상황이 발생할 수 있고 시스템은 요청된 이벤트 커널 오브젝트의 생성에 실패하게 되면 EnterCriticalSection함수는 EXCEPTION_INVALID_HANDLE 예외를 발생한다. 이런 예외에 대한 처리는 대부분 하지 않기 때문에 치명적인 상황이 발생할 수 있다.
위의 상황을 방지하기 위해서는 SEH로 예외를 핸들링하는방법과(비추이다.)
InitializeCriticalSectionAndSpinCount을 사용해서 임계영역을 생성하는것이다. (dwSpincount를 높게 설정해서) dwSpinCount가 높게 설정되면 이 함수는 이벤트 커널 오브젝트를 생성하여 이것을 임계영역과 연결한다. 만약 높은 비트로 설정이 되었는데 이벤트 커널 오브젝트가 생성되지 않으면 이 함수는 FALSE를 리턴한다. 성공적으로 이벤트 커널 오브젝트가 생성되면 EnterCriticalSection은 잘동작하고 절대 예외를 발생하지 않는다.
※ EnterCriticalSection 실행 시 다른 스레드가 해당 임계영역을 소유하고있다면 무기한 대기를 하는것이 아니고 레지스트리에 등록된 시간만큼만 대기하고 하나의 예외를 발생한다.
위치는 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manger 에 위치한 CriticalSectionTimeout 값이다. 단위는 초이고 기본값으로 2592000초이다. 대략 30일이다.;; (한마디로 무한대기구먼.ㅡ_ㅡ;;)
/// 버전 리소스 사이즈 얻기
DWORD dwHandle = 0;
DWORD dwSize = ::GetFileVersionInfoSize(szFilePath, &dwHandle);
/// 버전 리소스 정보 얻기
LPBYTE lpBuffer = new BYTE[dwSize];
::ZeroMemoty(lpBuffer, dwSize);
::GetFileVersionInfo(szFilePath, 0, dwSize, lpBuffer);
/// 리소스 핸들 얻기
HANDLE hResource = ::BeginUpdateResource(szFilePath, FALSE);
/// 리소스 정보 갱신
::UpdateResource(hResource, RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), wTanslationL/*LCID*/, lpBuffer, dwSize);
::EndUpdateResource(hResource, FALSE);
/// 버전 스트링 정보 읽기
::VerQueryValue(lpVSVersionInfo, szPath, &lpInfo/*out*/, &dwInfoLen/*out*/)
CString strData = (TCHAR*)lpInfo;
/// Translate code 구성
LCID, Code Page
lcid 와 code page 를 이용해 스트링 정보의 주소를 추출한다.
정보를 읽을때는 API 를 이용하여 읽기가 가능하지만 버전 정보의 추가 삭제 변경을 위해서는
데이터의 앞에 자신이 가진 Value의 길이 값이 들어가기 때문에 버퍼 클래스가 필요하다.
버퍼클래스는 아래의 코드에서 vs_version.h 부분 을 참고 하면 된다.
펌) http://www.jiniya.net/lecture/techbox/strsafe.html
C언어 표준에 포함된 문자열 함수들 중에 일부는 매우 위험하다. 대표적인 함수가 strcpy와 sprintf함수다. 이 두 함수의 경우 출력 값으로 문자열 포인터를 전송한다. 하지만 출력 문자열 포인터의 크기를 입력 받지 않기 때문에 버퍼 오버런의 위험을 가지고 있다. 버퍼 오버런의 경우 보안상 취약점이 될 수 있다. 따라서 견고한 프로그램을 작성하기 위해서는 되도록 이 함수들을 사용하지 않는 것이 좋다.
버퍼 오버런이란 프로그램 내부에서 사용하는 메모리 공간을 프로그래머가 기존에 의도했던 것을 넘어서서 덮어 쓰는 것을 말한다. 스택 공간을 침범하는 것을 스택 오버런, 힙 공간을 침범하는 것을 힙 오버런이라 한다. 아래 코드는 어떻게 스택 오버런이 발생하는 지 보여준다.
위의 코드를 살펴보면 dest는 최대 4글자를 저장할 수 있다. 왜냐하면 C언어의 경우 끝을 알리기 위해서 NULL 종료 문자를 사용하기 때문이다. 하지만 실제로 복사를 하고자 하는 소스 문자열은 4글자보다 큰 문자다. 따라서 프로그래머가 잡아둔 메모리 공간을 침범해서 덮어쓰게 된다. 이렇게 될 경우 스택이 깨지고 코드가 엉뚱한 곳으로 리턴되는 결과를 만들 수 있다.
Windows에서는 이러한 보안상 취약한 함수들을 대체할 새로운 안전한 함수들을 작성해서 Platform SDK를 통해서 배포하고 있다. 이 함수들은 strsafe.h에 들어있다. 우선 간략하게 어떠한 함수들이 들어 있는지 살펴보도록 하자.
기존함수 | 대체 함수 |
strcpy | StringCbCopy, StringCbCopyEx StringCchCopy, StringCchCopyEx |
strncpy | StringCbCopyN, StringCbCopyNEx StringCchCopyN, StringCchCopyNEx |
strcat | StringCbCat, StringCbCatEx StringCchCat, StringCchCatEx |
strncat | StringCbCatN, StringCbCatNEx StringCchCatN, StringCchCatNEx |
sprintf | StringCbPrintf, StringCbPrintfEx StringCchPrintf, StringCchPrintfEx |
vsprintf | StringCbVPrintf, StringCbVPrintfEx StringCchVPrintf, StringCchVPrintfEx |
gets | StringCbGets, StringCbGetsEx StringCchGets, StringCchGetsEx |
strlen | StringCbLength StringCchLength |
함수 이름이 규칙적으로 지어진 덕분에 함수의 종류를 한눈에 파악할 수 있다. 전체적으로 을 네 가지 종류의 함수가 있다. Cb, Cch계열과 일반 함수와 Ex 함수가 그것이다. Cb계열의 함수는 버퍼 크기를 인자로 받는다. 즉, 버퍼가 몇 바이트 크기를 가지느냐 하는 것을 기준으로 삼는다. 반면에 Cch계열 함수들은 버퍼의 길이를 인자로 받는다. 몇 글자를 저장할 수 있느냐 하는 것을 기준으로 삼는다. Ex 함수는 일반 함수의 기능에 버퍼의 잘림과 패딩을 다루는 추가적인 기능을 가진 함수들이다.
일반 함수의 경우 표준 함수와 동일한 인자를 받도록 되어 있다. 단지 추가적으로 버퍼의 크기를 하나 더 받는다. 따라서 여기서는 StringCbCopy와 StringCchPrintf의 사용법만 살펴보도록 하겠다. 다른 함수들의 자세한 사용방법을 알고 싶다면 MSDN을 참고하도록 하자.
StringCbCopy 함수의 원형이다. 이 함수는 strcpy와 동일한 기능을 한다. pszDest에는 복사될 버퍼 포인터를, cbDest에는 pszDest의 크기를, 그리고 pszSrc에는 복사할 문자열 포인터를 넣어주면 된다. cbDest를 제외하면 strcpy와 동일한 의미의 인자가 순서대로 입력된다는 것을 알 수 있다. 결과 값은 함수의 성공 여부다. 성공한 경우 S_OK를 리턴 한다. 위에 나열된 모든 String계열 함수의 리턴 값은 HRESULT다. COM에 사용되는 것과 동일한 타입이기 때문에 FAILED, SUCCEEDED매크로를 사용하면 손쉽게 에러 여부를 체크할 수 있다. StringCbCopy함수를 사용해 간단한 문자열을 복사하는 과정은 아래와 같다.
위의 코드를 실행해 보면 왜 StringCbCopy가 안전한지를 알 수 있다. 위 프로그램을 실행하면 dest값으로 Hello가 출력된다. 왜냐하면 dest의 크기인 6이 StringCbCopy함수 내부로 들어갔기 때문에 거기까지만 복사가 진행된 것이다. 더 이상 복사할 경우 버퍼 오버런이 발생하기 때문이다.
StringCbPrintf 함수의 원형이다. 이 함수는 sprintf와 동일한 기능을 한다. pszDest에는 출력될 버퍼를, cchDest에는 pszDest에 저장할 수 있는 글자 수를, 끝으로 pszFormat에는 포맷 문자열을 넣으면 된다. 아래와 같이 사용할 수 있다.
문자열을 다루는 일은 프로그래밍 과정에서 광범위 하게 사용된다. 일부 프로그램은 문자열 처리 과정이 프로그램의 전부이기도 하다. 이처럼 문자열 처리 작업은 많이 사용되는 만큼 가장 많은 버그와 보안 허점이 나오는 곳이기도 하다. 이러한 문제를 해결하는 가장 좋은 방법은 기존의 불완전한 함수들을 사용하지 않는 것이다. 이런 이유 때문에 strsafe.h를 프로젝트에 포함시키게 되면 표준 문자열 함수를 사용하는 부분에서는 deprecated 경고가 발생한다. 하지만 deprecated 경고를 강제로 무시하고 싶은 상황도 있다. 어쩔 수 없이 써야 하는 라이브러리 코드 등에서 표준 문자열 함수를 사용한 경우가 대표적이다. 이럴 때 경고를 강제로 끄기 위해서는 strsafe.h를 포함시키는 부분 앞에 STRSAFE_DEPRECATE를 정의해주면 된다. 아래와 같이 include를 시키면 표준 문자열 함수에 대한 경고가 발생하지 않는다.
String 계열의 함수가 안전하고 좋은 것임은 사실이다. 하지만 기존의 ANSI C/C++ 의 표준 문자열 함수들로 작성된 프로젝트를 String 계열의 함수로 교체하는 작업은 신중하게 결정해야 한다. 언뜻 보기에는 함수명을 바꾸는 간단한 작업처럼 보이지만 실상은 그렇지 않다. 기존 라이브러리 함수들을 사용하는 대부분의 코드의 경우 함수로 출력 버퍼의 크기를 전송하지 않기 때문에 호출하는 쪽과 함수 코드를 전체적으로 수정해야 한다. 이런 이유로 대부분의 경우 String계열로 코드를 고침으로써 얻는 보안 효과보다 더 많은 버그가 수정 도중에 발생한다. 따라서 기존의 프로젝트 코드를 변경하는 일은 신중히 검토한 후 결정하도록 하자.
말은 쓰는 사람의 혼을 담는 그릇이라고 한다. 이와 마찬가지로 코드는 프로그래머의 혼을 담는 그릇이 될 수 있다. 앞으로 새롭게 작성하는 프로젝트에는 안전한 문자열 함수를 쓰고 문자열 포인터가 전달되는 곳으로는 항상 크기를 같이 전달하도록 하자. 이보다 좀 더 좋은 방법은 되도록 직접적인 문자열 포인터의 사용을 줄이고 string이나 CString등의 C++ 클래스를 사용하는 것이다.
Operator ~ 는 자료형의 최대값을 기준으로
값을 반전 시켜준다.
예를 들어
USHORT wSource = 0xFFFF;
wSource = ~wSource ;
이렇게 하였을때 Operator~ 를 적용하기 전인 wSource 의 값이 최대값 65535이기 때문에
현재 wSource 의 값은 0 이다.
다시 이야기 하면 wSource + ~wSource 의 값은 자료형의 최대값이다.
BOOL FileExist(LPCTSTR lpszFileName)
{
HANDLE hFile = CreateFile(lpszFileName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
펌) http://psmon.x-y.net/maniwiki/doku.php
기타 파일 버전 관련 소스) http://www.codeproject.com/KB/install/VerPatch.aspx
MSDN)
http://msdn.microsoft.com/en-us/library/ms647003.aspx :: GetFileVersionInfo
http://msdn.microsoft.com/en-us/library/ms647005(VS.85).aspx :: GetFileVersionInfoSize
http://msdn.microsoft.com/en-us/library/ms647464(VS.85).aspx :: VerQueryValue
http://msdn.microsoft.com/en-us/library/ms646997.aspx :: VS_FIXEDFILEINFO
http://msdn.microsoft.com/en-us/library/ms647001(VS.85).aspx :: VS_VERSIONINFO
#pragma once
#include <Version.h>
#pragma comment(lib,"version")
struct VS_VERSIONINFO
{
WORD wLength;
WORD wValueLength;
WORD wType;
WCHAR szKey[1];
WORD wPadding1[1];
VS_FIXEDFILEINFO Value;
WORD wPadding2[1];
WORD wChildren[1];
};
struct
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
struct Vs_info
{
DWORD high;
DWORD low;
};
void CreschangerDlg::SetVersionInfo(char *cfilename,char *cfileversion,char *cproductversion,char *ccomment)
{
UpdateData(TRUE);
PS_PRJINFO edit_one;
edit_one.exefullname = cfilename;
edit_one.fileversion=cfileversion;
edit_one.productversion=cproductversion;
edit_one.comment=ccomment;
edit_one.filename=m_strFileName.GetBuffer(0);
m_prj.EditPrj(&edit_one);
//버젼 유효성검사
if(false == validversiondata(PVER_FILEVERSION,cfileversion) )
{
MessageBox(m_verLastError);
return;
}
if(false == validversiondata(PVER_PRODUCTVERSION,cproductversion) )
{
MessageBox(m_verLastError);
return;
}
if(false == validversiondata(PVER_COMMENT,ccomment) )
{
MessageBox(m_verLastError);
return;
}
VS_VERSIONINFO *pVerInfo;
LPBYTE pOffsetBytes;
VS_FIXEDFILEINFO *pFixedInfo;
LPCTSTR lpszFile = _T(cfilename);
DWORD dwHandle, dwSize, dwResult = 0 ;
LPBYTE lpBuffer;
// determine the size of the resource information
dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle);
if (0 < dwSize)
{
lpBuffer = new BYTE[dwSize];
memset(lpBuffer,0,dwSize);
if (GetFileVersionInfo(lpszFile, 0, dwSize, lpBuffer) != FALSE)
{
// these macros help to align on r-byte boundaries (thanks Ted Peck)
#define roundoffs(a,b,r) (((BYTE *) (b) - (BYTE *) (a) + ((r) - 1)) & ~((r) - 1))
#define roundpos(a,b,r) (((BYTE *) (a)) + roundoffs(a,b,r))
// 'point to' the start of the version information block
pVerInfo = (VS_VERSIONINFO *) lpBuffer;
// the fixed section starts right after the 'VS_VERSION_INFO' string
pOffsetBytes = (BYTE *) &pVerInfo->szKey[16];
//int x = _tcslen((char*)pVerInfo->szKey);
pFixedInfo = (VS_FIXEDFILEINFO *) roundpos(pVerInfo, pOffsetBytes, 4);
// increment the numbers!
int left = 0;
int right = 0;
left = GetDigtFromVerString(cfileversion,0);
right = GetDigtFromVerString(cfileversion,1);
pFixedInfo->dwFileVersionMS = (0x00010000 * left) + (0x00000001 * right);
left = GetDigtFromVerString(cfileversion,2);
right = GetDigtFromVerString(cfileversion,3);
pFixedInfo->dwFileVersionLS = (0x00010000 * left) + (0x00000001 * right);
left = GetDigtFromVerString(cproductversion,0);
right = GetDigtFromVerString(cproductversion,1);
pFixedInfo->dwProductVersionMS = (0x00010000 * left) + (0x00000001 * right);
left = GetDigtFromVerString(cproductversion,2);
right = GetDigtFromVerString(cproductversion,3);
pFixedInfo->dwProductVersionLS = (0x00010000 * left) + (0x00000001 * right);
LPVOID lpInfo;
UINT unInfoLen;
if (VerQueryValue(lpBuffer, _T("\\"), &lpInfo, &unInfoLen))
{
//ASSERT(unInfoLen == sizeof(m_FileInfo));
//if (unInfoLen == sizeof(m_FileInfo))
// memcpy(&m_FileInfo, lpInfo, unInfoLen);
}
// find best matching language and codepage
VerQueryValue(lpBuffer, _T("\\VarFileInfo\\Translation"), &lpInfo, &unInfoLen);
DWORD dwLangCode = 0;
if (GetTranslationId(lpInfo, unInfoLen, MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL), dwLangCode, TRUE))
dwLangCode = *((DWORD*)lpInfo);
if(dwLangCode < 1 )
{
MessageBox("영문버젼정보가 있질않습니다. 리소스의 국가설정을 확인하세요!");
delete [] lpBuffer;
return;
}
CString m_strProductName;
CString strSubBlock;
HANDLE hResource = BeginUpdateResource(lpszFile, FALSE);
if (NULL != hResource)
{
UINT uTemp;
wchar_t tt[128];
char xx[128];
char* w_version;
w_version= new char[dwSize];
for(int i=0;i<dwSize;i++)
{
w_version[i] = lpBuffer[i];
}
bool is_lenok = true;
LPVOID p_fileVer;
memset(&tt[0],0,sizeof(wchar_t) * 128 );
p_fileVer = SearchText(w_version,"FileVersion",dwSize);
strcpy(xx,cfileversion);
for(int i=0;i<strlen(xx);i++)
{
tt[i]=std::wcout.widen(xx[i]);
}
strSubBlock.Format(_T("\\StringFileInfo\\%04X%04X\\FileVersion"), dwLangCode&0x0000FFFF, (dwLangCode&0xFFFF0000)>>16);
if (VerQueryValue(lpBuffer, (LPTSTR)(LPCTSTR)(strSubBlock), (LPVOID *) &pValueBuffer, &unInfoLen))
{
m_strProductName = CString((LPCTSTR)pValueBuffer);
if(strlen(xx) <= m_strProductName.GetLength())
{
ZeroMemory(p_fileVer, m_strProductName.GetLength() * sizeof(wchar_t));
wcscpy((wchar_t*)p_fileVer, &tt[0] );
}
else
{
is_lenok =false;
}
}
else
{
is_lenok =false;
}
memset(&tt[0],0,sizeof(wchar_t) * 128 );
p_fileVer = SearchText(w_version,"ProductVersion",dwSize);
strcpy(xx,cproductversion);
for(int i=0;i<strlen(xx);i++)
{
tt[i]=std::wcout.widen(xx[i]);
}
strSubBlock.Format(_T("\\StringFileInfo\\%04X%04X\\ProductVersion"), dwLangCode&0x0000FFFF, (dwLangCode&0xFFFF0000)>>16);
if (VerQueryValue(lpBuffer, (LPTSTR)(LPCTSTR)(strSubBlock), (LPVOID *) &pValueBuffer, &unInfoLen))
{
m_strProductName = CString((LPCTSTR)pValueBuffer);
if(strlen(xx) <= m_strProductName.GetLength())
{
ZeroMemory(p_fileVer, m_strProductName.GetLength() * sizeof(wchar_t));
wcscpy((wchar_t*)p_fileVer, &tt[0] );
}
else
{
is_lenok =false;
}
}
else
{
is_lenok =false;
}
memset(&tt[0],0,sizeof(wchar_t) * 128 );
p_fileVer = SearchText(w_version,"Comments",dwSize);
strcpy(xx,ccomment);
for(int i=0;i<strlen(xx);i++)
{
tt[i]=std::wcout.widen(xx[i]);
}
strSubBlock.Format(_T("\\StringFileInfo\\%04X%04X\\Comments"), dwLangCode&0x0000FFFF, (dwLangCode&0xFFFF0000)>>16);
if (VerQueryValue(lpBuffer, (LPTSTR)(LPCTSTR)(strSubBlock), (LPVOID *) &pValueBuffer, &unInfoLen))
{
m_strProductName = CString((LPCTSTR)pValueBuffer);
if(strlen(xx) <= m_strProductName.GetLength())
{
ZeroMemory(p_fileVer, m_strProductName.GetLength() * sizeof(wchar_t));
wcscpy((wchar_t*)p_fileVer, &tt[0] );
}
else
{
is_lenok =false;
}
}
else
{
is_lenok =false;
}
memcpy(lpBuffer,w_version,dwSize);
delete [] w_version;
if(!is_lenok)
{
delete [] lpBuffer;
MessageBox("바꾸려는 버젼정보문자열이, Original버젼의 문자열길이를 초과하거나 없습니다..");
return;
}
// could probably just use LANG_NEUTRAL/SUBLANG_NEUTRAL
if (UpdateResource(hResource, RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), dwLangCode&0x0000FFFF, lpBuffer, dwSize) != FALSE)
{
// 언어한글 강제..업데이트
//UpdateResource(hResource, RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), 1042, lpBuffer, dwSize);
if (EndUpdateResource(hResource, FALSE) == FALSE)
dwResult = GetLastError();
}
else
dwResult = GetLastError();
}
else
dwResult = GetLastError();
delete [] lpBuffer;
}
else
{
dwResult = GetLastError();
delete [] lpBuffer;
}
}
else
dwResult = GetLastError();
if(dwResult != 0)
{
sprintf(m_verLastError,"VerChanger Failed Reason:%d",dwResult);
MessageBox(m_verLastError);
}
else
{
MessageBox("버젼업이 성공했습니다.");
}
}
BOOL CreschangerDlg::GetTranslationId(LPVOID lpData, UINT unBlockSize, WORD wLangId, DWORD &dwId, BOOL bPrimaryEnough/*= FALSE*/)
{
for (LPWORD lpwData = (LPWORD)lpData; (LPBYTE)lpwData < ((LPBYTE)lpData)+unBlockSize; lpwData+=2)
{
if (*lpwData == wLangId)
{
dwId = *((DWORD*)lpwData);
return TRUE;
}
}
if (!bPrimaryEnough)
return FALSE;
for (lpwData = (LPWORD)lpData; (LPBYTE)lpwData < ((LPBYTE)lpData)+unBlockSize; lpwData+=2)
{
if (((*lpwData)&0x00FF) == (wLangId&0x00FF))
{
dwId = *((DWORD*)lpwData);
return TRUE;
}
}
return FALSE;
}
LPVOID CreschangerDlg::SearchText(char* Search,char* findstring,int maxlen)
{
int comlen = strlen(findstring);
bool b_ser=false;
int comcount=0;
for(int i=0;i<maxlen ; i++)
{
comcount=0;
for(int x=0;x<comlen;x++)
{
char cur = Search[i];
if(cur == findstring[x])
{
comcount++;
i = i + 2;
}
else
{
break;
}
if(comcount == comlen)
{
int xxx=999;
if(0 ==strcmp("ProductVersion",findstring))
{
return (LPVOID)(&Search[i+2]);
}
if(0 == strcmp("Comments",findstring))
{
return (LPVOID)(&Search[i+2]);
}
return (LPVOID)(&Search[i+4]);
}
}
}
return NULL;
}
bool CreschangerDlg::validversiondata(int ntype,char* verstring)
{
int i_verlen = strlen(verstring);
if( (COMMENT_MAX < i_verlen) && (i_verlen < 1) )
{
strcpy(m_verLastError,"Comment의 Rev이 지정된 길이를 초과했거나, 1보다 작습니다.");
return false;
}
if(ntype == PVER_COMMENT)
{
strcpy(m_verLastError,"Comment의 유효성 통과");
return true;
}
if( VERSION_MAX < i_verlen)
{
strcpy(m_verLastError,"Version정보가 제한된 길이를 초과했습니다.");
return false;
}
int comCount = 0;
for(int i=0 ;i<i_verlen;i++)
{
// ,의 개수를 카운팅한다.
if(verstring[i] == ',' || verstring[i] == '.')
{
if(i > 0)
{
if(verstring[i-1] == ',' || verstring[i-1] == '.')
{
strcpy(m_verLastError,"버젼 구분자사이에 값이 없습니다.");
return false;
}
}
comCount++;
continue;
}
//버젼정보에 해당하는 문자열값이 숫자인지 체크
if( (NUM_START < verstring[i]) && (NUM_END > verstring[i]) )
{
}
else
{
strcpy(m_verLastError,"Version정보에 ,를 제외한 문자가 들어올수 없습니다.");
return false;
}
}
if(COMA_COUNT != comCount )
{
strcpy(m_verLastError,"Version정보에 ,의 카운트는 3이어야합니다.");
return false;
}
strcpy(m_verLastError,"Version정보 유효성 통과");
return true;
}
Activate_RunDLL
AppCompat_RunDLLW
CDefFolderMenu_Create
CDefFolderMenu_Create2
CallCPLEntry16
CheckEscapesA
CheckEscapesW
CommandLineToArgvW
Control_FillCache_RunDLL
Control_FillCache_RunDLLA
Control_FillCache_RunDLLW
Control_RunDLL
Control_RunDLLA
Control_RunDLLAsUserW
Control_RunDLLW
DAD_AutoScroll
DAD_DragEnterEx
DAD_DragEnterEx2
DAD_DragLeave
DAD_DragMove
DAD_SetDragImage
DAD_ShowDragImage
DllCanUnloadNow
DllGetClassObject
DllGetVersion
DllInstall
DllRegisterServer
DllUnregisterServer
DoEnvironmentSubstA
DoEnvironmentSubstW
DragAcceptFiles
DragFinish
DragQueryFile
DragQueryFileA
DragQueryFileAorW
DragQueryFileW
DragQueryPoint
DriveType
DuplicateIcon
ExtractAssociatedIconA
ExtractAssociatedIconExA
ExtractAssociatedIconExW
ExtractAssociatedIconW
ExtractIconA
ExtractIconEx
ExtractIconExA
ExtractIconExW
ExtractIconResInfoA
ExtractIconResInfoW
ExtractIconW
ExtractVersionResource16W
FindExeDlgProc
FindExecutableA
FindExecutableW
FreeIconList
GetFileNameFromBrowse
ILAppendID
ILClone
ILCloneFirst
ILCombine
ILCreateFromPath
ILCreateFromPathA
ILCreateFromPathW
ILFindChild
ILFindLastID
ILFree
ILGetNext
ILGetSize
ILIsEqual
ILIsParent
ILLoadFromStream
ILRemoveLastID
ILSaveToStream
InternalExtractIconListA
InternalExtractIconListW
IsLFNDrive
IsLFNDriveA
IsLFNDriveW
IsNetDrive
IsUserAnAdmin
OpenAs_RunDLL
OpenAs_RunDLLA
OpenAs_RunDLLW
OpenRegStream
Options_RunDLL
Options_RunDLLA
Options_RunDLLW
PathCleanupSpec
PathGetShortPath
PathIsExe
PathIsSlowA
PathIsSlowW
PathMakeUniqueName
PathProcessCommand
PathQualify
PathResolve
PathYetAnotherMakeUniqueName
PickIconDlg
PifMgr_CloseProperties
PifMgr_GetProperties
PifMgr_OpenProperties
PifMgr_SetProperties
PrintersGetCommand_RunDLL
PrintersGetCommand_RunDLLA
PrintersGetCommand_RunDLLW
ReadCabinetState
RealDriveType
RealShellExecuteA
RealShellExecuteExA
RealShellExecuteExW
RealShellExecuteW
RegenerateUserEnvironment
RestartDialog
RestartDialogEx
SHAddFromPropSheetExtArray
SHAddToRecentDocs
SHAlloc
SHAllocShared (forwarded to shlwapi.SHAllocShared)
SHAppBarMessage
SHBindToParent
SHBrowseForFolder
SHBrowseForFolderA
SHBrowseForFolderW
SHCLSIDFromString
SHChangeNotification_Lock
SHChangeNotification_Unlock
SHChangeNotify
SHChangeNotifyDeregister
SHChangeNotifyRegister
SHChangeNotifySuspendResume
SHCloneSpecialIDList
SHCoCreateInstance
SHCreateDirectory
SHCreateDirectoryExA
SHCreateDirectoryExW
SHCreateFileExtractIconW
SHCreateLocalServerRunDll
SHCreateProcessAsUserW
SHCreatePropSheetExtArray
SHCreateQueryCancelAutoPlayMoniker
SHCreateShellFolderView
SHCreateShellFolderViewEx
SHCreateShellItem
SHCreateStdEnumFmtEtc
SHDefExtractIconA
SHDefExtractIconW
SHDestroyPropSheetExtArray
SHDoDragDrop
SHEmptyRecycleBinA
SHEmptyRecycleBinW
SHEnableServiceObject
SHEnumerateUnreadMailAccountsW
SHExtractIconsW
SHFileOperation
SHFileOperationA
SHFileOperationW
SHFindFiles
SHFind_InitMenuPopup
SHFlushClipboard
SHFlushSFCache
SHFormatDrive
SHFree
SHFreeNameMappings
SHFreeShared (forwarded to shlwapi.SHFreeShared)
SHGetAttributesFromDataObject
SHGetDataFromIDListA
SHGetDataFromIDListW
SHGetDesktopFolder
SHGetDiskFreeSpaceA
SHGetDiskFreeSpaceExA
SHGetDiskFreeSpaceExW
SHGetFileInfo
SHGetFileInfoA
SHGetFileInfoW
SHGetFolderLocation
SHGetFolderPathA
SHGetFolderPathAndSubDirA
SHGetFolderPathAndSubDirW
SHGetFolderPathW
SHGetIconOverlayIndexA
SHGetIconOverlayIndexW
SHGetImageList
SHGetInstanceExplorer
SHGetMalloc
SHGetNewLinkInfo
SHGetNewLinkInfoA
SHGetNewLinkInfoW
SHGetPathFromIDList
SHGetPathFromIDListA
SHGetPathFromIDListW
SHGetRealIDL
SHGetSetFolderCustomSettingsW
SHGetSetSettings
SHGetSettings
SHGetShellStyleHInstance
SHGetSpecialFolderLocation
SHGetSpecialFolderPathA
SHGetSpecialFolderPathW
SHGetUnreadMailCountW
SHHandleUpdateImage
SHHelpShortcuts_RunDLL
SHHelpShortcuts_RunDLLA
SHHelpShortcuts_RunDLLW
SHILCreateFromPath
SHInvokePrinterCommandA
SHInvokePrinterCommandW
SHIsFileAvailableOffline
SHLimitInputEdit
SHLoadInProc
SHLoadNonloadedIconOverlayIdentifiers
SHLoadOLE
SHLockShared (forwarded to shlwapi.SHLockShared)
SHMapIDListToImageListIndexAsync
SHMapPIDLToSystemImageListIndex
SHMultiFileProperties
SHObjectProperties
SHOpenFolderAndSelectItems
SHOpenPropSheetW
SHParseDisplayName
SHPathPrepareForWriteA
SHPathPrepareForWriteW
SHPropStgCreate
SHPropStgReadMultiple
SHPropStgWriteMultiple
SHQueryRecycleBinA
SHQueryRecycleBinW
SHReplaceFromPropSheetExtArray
SHRestricted
SHRunControlPanel
SHSetInstanceExplorer
SHSetLocalizedName
SHSetUnreadMailCountW
SHShellFolderView_Message
SHSimpleIDListFromPath
SHStartNetConnectionDialogW
SHTestTokenMembership
SHUnlockShared (forwarded to shlwapi.SHUnlockShared)
SHUpdateImageA
SHUpdateImageW
SHUpdateRecycleBinIcon
SHValidateUNC
SheChangeDirA
SheChangeDirExA
SheChangeDirExW
SheChangeDirW
SheConvertPathW
SheFullPathA
SheFullPathW
SheGetCurDrive
SheGetDirA
SheGetDirExW
SheGetDirW
SheGetPathOffsetW
SheRemoveQuotesA
SheRemoveQuotesW
SheSetCurDrive
SheShortenPathA
SheShortenPathW
ShellAboutA
ShellAboutW
ShellExec_RunDLL
ShellExec_RunDLLA
ShellExec_RunDLLW
ShellExecuteA
ShellExecuteEx
ShellExecuteExA
ShellExecuteExW
ShellExecuteW
ShellHookProc
ShellMessageBoxA
ShellMessageBoxW
Shell_GetCachedImageIndex
Shell_GetImageLists
Shell_MergeMenus
Shell_NotifyIcon
Shell_NotifyIconA
Shell_NotifyIconW
SignalFileOpen
StrChrA
StrChrIA
StrChrIW
StrChrW
StrCmpNA
StrCmpNIA
StrCmpNIW
StrCmpNW
StrCpyNA
StrCpyNW
StrNCmpA
StrNCmpIA
StrNCmpIW
StrNCmpW
StrNCpyA
StrNCpyW
StrRChrA
StrRChrIA
StrRChrIW
StrRChrW
StrRStrA
StrRStrIA
StrRStrIW
StrRStrW
StrStrA
StrStrIA
StrStrIW
StrStrW
WOWShellExecute
Win32DeleteFile
WriteCabinetState
[NONAME] (forwarded to shlwapi.PathBuildRootW)
[NONAME] (forwarded to shlwapi.PathCombineW)
[NONAME] (forwarded to shlwapi.PathIsUNCW)
[NONAME] (forwarded to shlwapi.PathIsRelativeW)
[NONAME] (forwarded to shlwapi.PathGetDriveNumberW)
StreamReader file = File.OpenText(@"D:\11.txt");
byte[] b = file.CurrentEncoding.GetPreamble();
위와 같이 오픈한 해당 파일의
"byte-order mark" 를 가져온 뒤
byte배열에 들어있는 값을
아래표와 비교하시면 됨
=========
Encoding | Representation (hexadecimal) | Representation (decimal) |
---|---|---|
UTF-8 | EF BB BF † |
239 187 191 |
UTF-16 (BE) | FE FF |
254 255 |
UTF-16 (LE) | FF FE |
255 254 |
UTF-32 (BE) | 00 00 FE FF |
0 0 254 255 |
UTF-32 (LE) | FF FE 00 00 |
255 254 0 0 |
UTF-7 | 2B 2F 76 , and one of the following bytes: [ 38 | 39 | 2B | 2F ] † |
43 47 and one of the following bytes: [ 56 | 57 | 43 | 47 ] |
UTF-1 | F7 64 4C |
247 100 76 |
UTF-EBCDIC | DD 73 66 73 |
221 115 102 115 |
SCSU | 0E FE FF † |
14 254 255 |
BOCU-1 | FB EE 28 optionally followed by FF † |
251 238 40 optionally followed by 255 |
출처 : http://maluchi.cafe24.com/xe/?mid=MyProgrammingTips&page=6&listStyle=list&document_srl=15989
[ 트리컨트롤(Tree Control) ]
1. 대화상자에 트리컨트롤을 붙이고 옵션을 다음과 같이 수정하자.
Edit labels: 트리컨트롤에서 에디트 기능을 사용할때.
Show selection always: 선택된 아이템을 표시할때.
2. 맴버 변수를 m_ctrTree라고 만들자(Control형 하나밖에 없다).
3. 아이템 추가하기
TVINSERTSTRUCT TI;
TI.hParent = TVI_ROOT; // TVI_ROOT, NULL
// HTREEITEM값을 사용하면 해당하는 아이템의 자식으로 아이템이 추가된다.
TI.hInsertAfter = TVI_LAST; // TVI_FIRST, TVI_LAST, TVI_SORT
TI.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TI.item.iImage = 0; // Tree가 선택안되었을때 표시될 아이콘
TI.item.iSelectedImage = 1; // Tree가 선택되었을때 표시될 아이콘
TI.item.pszText = "root";
HTREEITEM hTreeItem = m_ctrTree.InsertItem(&TI); // 추가된 아이템의 HTREEITEM이 리턴된다.
4. 아이템 확장하기.
m_ctrTree.Expand(hTreeItem, TVE_EXPAND);
5. 아이템 선택시 선택된 아이템 알아보기
+ TVN_SELCHANGED메시지를 사용한다.
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
HTREEITEM hTreeItem = pNMTreeView->itemNew.hItem; // 이 값이 선택된 아이템의 핸들이다.
6. 아이템 문자열 가져오기
CString str = m_ctrTree.GetItemText(hTreeItem);
7. 아이템 개수 알아내기
int nCount = m_ctrTree.GetCount();
8. 아이템 제거하기
m_ctrTree.DeleteItem(hTreeItem); // 핸들 아래단의 아이템들도 모두 제거된다.
9. 현재 선택된 아이템 알아내기
HTREEITEM hTreeItem = m_ctrTree.GetSelectedItem();
10. 위치로 아이템 찾기
CPoint p;
GetCursorPos(&p);
::ScreenToClient(m_ctrTree.m_hWnd, &p);
HTREEITEM hItem = m_ctrTree.HitTest(p);
11. 아이템 확장 축소 감지
+ TVN_ITEMEXPANDED메시지를 사용한다.
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
TVITEM item;
item.mask = TVIF_HANDLE;
item.hItem = pNMTreeView->itemNew.hItem;
m_ctrTree.GetItem(&item); // 아이템 정보를 알아낸다.
if(item.state & TVIS_EXPANDED)
{
// 확장
}
else
{
// 축소
}
12. 아이템 아이콘 설정 변경
m_ctrTree.SetItemImage(hTreeItem, 0, 1);
13. 아이템 에디트 입력중 포커스가 나갈때 입력중인 값 아이템에 적용하기
+ TVN_ENDLABELEDIT메시지를 사용한다.
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
CEdit *pEdit = m_ctrTree.GetEditControl();
if(pEdit)
{
CString str;
pEdit->GetWindowText(str);
if(str.GetLength() > 0)
{
m_ctrTree.SetItemText(pTVDispInfo->item.hItem, str);
}
}
14. 이미지 리스트 설정
+ CImageList m_Image; // 32 x 16 아이콘 BITMAP 16 x 16 2개 짜리
m_Image.m_hImageList = ImageList_LoadImage(
(HINSTANCE) GetWindowLong(m_hWnd, GWL_HINSTANCE),
MAKEINTRESOURCE(IDB_BITMAP_SMALL), 16, 2,
RGB(255,255,255), IMAGE_BITMAP, LR_CREATEDIBSECTION);
m_ctrTree.SetImageList(&m_Image, TVSIL_NORMAL);
- 드래그 앤 드롭 사용하기
1. 드래그 시작
- 트리컨트롤의 TVN_BEGINDRAG메시지 사용
CImageList *m_pTreeDragImage = NULL; // 드래그시 생성된 이미지 사용
HTREEITEM m_hDragItem = NULL; // 드래그시 처음 선택된 아이템 핸들 기억용
void CDlg::OnBegindragTree(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
// 드래그 이미지 생성
if(m_pTreeDragImage) m_pTreeDragImage->DeleteImageList();
m_pTreeDragImage = m_ctrTree.CreateDragImage(pNMTreeView->itemNew.hItem);
// 드래그시 사용할 이미지 크기 계산
RECT rc;
m_ctrTree.GetItemRect(pNMTreeView->itemNew.hItem, &rc, TRUE); // 아이콘을 포함하는 크기
// 드래그를 시작
m_pTreeDragImage->BeginDrag(0, CPoint(pNMTreeView->ptDrag.x-rc.left+16,
pNMTreeView->ptDrag.y-rc.top));
// 드래그 이미지 표시
m_pTreeDragImage->DragEnter(&m_ctrTree, pNMTreeView->ptDrag);
// 마우스 메시지를 잡아두고
SetCapture();
// 현재 선택된 아이템 핸들을 기억
m_hDragItem = pNMTreeView->itemNew.hItem;
*pResult = 0;
}
2. 이동
- WM_MOUSEMOVE메시지 사용
void CDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
// 드래그 중이라면
if(m_pTreeDragImage)
{
// 트리컨트롤 기준으로 마우스 좌표 계산
CPoint p = point;
ClientToScreen(&p);
::ScreenToClient(m_ctrTree.m_hWnd, &p);
// 마우스가 위치한 아이템을 검사한다.항목이 트리 뷰 항목위에 있는지 확인하고 그렇다면 항목이 밝게 표시되도록한다.
HTREEITEM hItem = m_ctrTree.HitTest(p);
// 밝게 표시된 부분과 현재 선택된 아이템이 틀리다면
if(hItem != m_ctrTree.GetDropHilightItem())
{
// 드래그 이미지 그리기 중지
m_pTreeDragImage->DragLeave(&m_ctrTree);
// 새로운 항목을 밝게 표시한다.
m_ctrTree.SelectDropTarget(hItem);
// 드래그 이미지를 다시 보여준다.
m_pTreeDragImage->DragEnter(&m_ctrTree, p);
}
else
{
m_pTreeDragImage->DragMove(p);
}
}
CDialog::OnMouseMove(nFlags, point);
}
3. 드롭
- WM_LBUTTONUP메시지 사용
void CDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
// 드래그 중이 었다면
if(m_pTreeDragImage)
{
// 마우스 메시지 캡쳐 기능을 제거한다.
ReleaseCapture();
// 드래그 과정을 중단한다.
m_pTreeDragImage->DragLeave(&m_ctrTree);
m_pTreeDragImage->EndDrag();
m_pTreeDragImage->DeleteImageList();
m_pTreeDragImage = NULL;
// 일단 마지막으로 밝게 표시되었던 항목을 찾는다.
HTREEITEM hTargetItem = m_ctrTree.GetDropHilightItem();
// 밝게 표시된 드롭 항목의 선택을 취소한다.
m_ctrTree.SelectDropTarget(NULL);
// 선택된 항목(아이템)이 있다면
if(hTargetItem)
{
// 선택된 아이템과 이동될 곳의 아이템이 같다면 이동할 필요가 없다.
if(m_hDragItem != hTargetItem)
{
// 현재 자식의 부모 아이템 핸들을 구한다.
HTREEITEM hParentItem = m_ctrTree.GetNextItem(m_hDragItem,
TVGN_PARENT);
// 이동하려는 곳이 자신이 직접속한 항목 이라면 이동할 필요가 없다.
if(hParentItem != hTargetItem)
{
// 트리의 내용을 이동하자.
MoveTreeItem(&m_ctrTree, m_hDragItem, hTargetItem);
// 이동된 곳의 트리를 확장하자.
m_ctrTree.Expand(hTargetItem, TVE_EXPAND);
// 이미지도 확장한걸로 바꾸자
m_ctrTree.SetItemImage(hTargetItem, 1, 1);
// 원본 트리의 모든 아이템이 사라졌다면 이미지 그림을 기본으로 바꾸자.
HTREEITEM hItem = m_ctrTree.GetChildItem(hParentItem);
if(!hItem)
{
m_ctrTree.SetItemImage(hParentItem, 0, 0);
}
}
}
}
m_hDragItem = NULL;
}
CDialog::OnLButtonUp(nFlags, point);
}
4. 트리 항목(아이템) 이동 함수
// 아이템 데이터 이동
BOOL MoveTreeItem(CTreeCtrl *pTree, HTREEITEM hSrcItem, HTREEITEM hDestItem)
{
// 이동할 아이템의 정보를 알아내자.
TVITEM TV;
char str[256];
ZeroMemory(str, sizeof(str));
TV.hItem = hSrcItem;
TV.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TV.pszText = str;
TV.cchTextMax = sizeof(str);
m_ctrTree.GetItem(&TV);
DWORD dwData = pTree->GetItemData(hSrcItem);
// 아이템을 추가 하자.
TVINSERTSTRUCT TI;
TI.hParent = hDestItem;
TI.hInsertAfter = TVI_LAST;
TI.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TI.item.iImage = TV.iImage;
TI.item.iSelectedImage = TV.iSelectedImage;
TI.item.pszText = TV.pszText;
HTREEITEM hItem = pTree->InsertItem(&TI);
pTree->SetItemData(hItem, dwData);
// 현재 아이템에 자식 아이템이 있다면
HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);
if(hChildItem)
{
// 자식 아이템이 있다면 같이 이동하자.
MoveChildTreeItem(pTree, hChildItem, hItem);
}
// 확장 여부를 알아서 똑같이 하자.
TVITEM item;
item.mask = TVIF_HANDLE;
item.hItem = hSrcItem;
pTree->GetItem(&item);
if(item.state & TVIS_EXPANDED)
{
pTree->Expand(hItem, TVE_EXPAND);
}
// 아이템을 선택하자.
pTree->SelectItem(hItem);
// 기존 아이템을 제거한다.
pTree->DeleteItem(hSrcItem);
return TRUE;
}
// 현재 트리의 모든 아이템 데이터 이동
BOOL MoveChildTreeItem(CTreeCtrl *pTree, HTREEITEM hChildItem,
HTREEITEM hDestItem)
{
HTREEITEM hSrcItem = hChildItem;
while(hSrcItem)
{
// 이동할 아이템의 정보를 알아내자.
TVITEM TV;
char str[256];
ZeroMemory(str, sizeof(str));
TV.hItem = hSrcItem;
TV.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TV.pszText = str;
TV.cchTextMax = sizeof(str);
m_ctrTree.GetItem(&TV);
DWORD dwData = pTree->GetItemData(hSrcItem);
// 아이템을 추가 하자.
TVINSERTSTRUCT TI;
TI.hParent = hDestItem;
TI.hInsertAfter = TVI_LAST;
TI.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TI.item.iImage = TV.iImage;
TI.item.iSelectedImage = TV.iSelectedImage;
TI.item.pszText = TV.pszText;
HTREEITEM hItem = pTree->InsertItem(&TI);
pTree->SetItemData(hItem, dwData);
// 현재 아이템에 자식 아이템이 있다면
HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);
// pTree->GetNextItem(hSrcItem, TVGN_CHILD);
if(hChildItem)
{
MoveChildTreeItem(pTree, hChildItem, hItem);
}
// 확장 여부를 알아서 똑같이 하자.
TVITEM item;
item.mask = TVIF_HANDLE;
item.hItem = hSrcItem;
pTree->GetItem(&item);
if(item.state & TVIS_EXPANDED)
{
pTree->Expand(hItem, TVE_EXPAND);
}
// 다음 아이템을 알아보자.
hSrcItem = pTree->GetNextItem(hSrcItem, TVGN_NEXT);
}
// 기존 아이템을 제거한다.
pTree->DeleteItem(hChildItem);
return TRUE;
}
© Copyright 2000, Detlef Vollmann
Over the last two years I've held several tutorials on meta-classes and reflection in C++. I use the term reflection here in its original sense, "looking back to oneself", nowadays sometimes called introspection. The more general process to allow modifications at class level at run-time is the old task to provide a meta-level, but is today sometimes (mis-)called "behavioural reflection" or "structural reflection".
In some sense this article presents work in progress, though it's going on now for nearly ten years. It's not the definitive meta-object protocol for C++, but more a presentation of lesser-known C++ techniques to solve some specific design problems.
If you have comments on these techniques, or proposals how the problems could be solved completely differently, or if you find errors in this article, I really appreciate your feedback to dv@vollmann.ch.
This document can be found on the web at http://www.vollmann.com/en/pubs/meta/index.html.
Some source code to illustrate the implementation of the ideas of this article can be found at http://www.vollmann.com/download/mop/index.html.
The code in this article is not completely identical with that source code (due to typographical reasons and compiler restrictions). So, it could well be that some errors crept into the code here; if you find them, please mail me as well.
Book
class:class Book { public: Book(const string & author_, const string & title_, const string & publisher_, double price_, double weight_); string getName() { string name; name = author + ": " + title; return name.substr(0, 40); } double getPrice(); double getWeight(); private: string author, title, publisher; double price, weight; };Your solution works, Susan is happy, and all is fine for a while...
Product
class (Fig. 3): A simple interface for ShoppingCart
providing getName()
, getPrice()
, and getWeight()
. This is what you already have. Then you need a different interface for a general search machine2, which must provide information like:someBook.author
, is information on the object level. But information about the properties of the object itself, about its attributes, its structure, etc. is meta information.Type
.Type
is to distinguish different kind of Attribute
s. For this, a simple enum
would suffice. But the idea of types is to have different kind of Values
for different Type
s, so the Type
should create new Value
s. So we put the enum
inside the Type
class, provide the newValue()
method, and get the interface shown in Fig. 4.Value
for now, if we have different kind of values we probably need some base class for them. Let's call it "BaseValue
", and newValue()
can just return a pointer to BaseValue
.static vector
3.class Type { public: enum TypeT {stringT, intT, doubleT, unknownT}; explicit Type(TypeT typeId_) : typeId(typeId_) {} BaseValue * newValue() const { return prototypes[typeId]->clone(); } TypeT getType() const { return typeId; } static void init(); { prototypes[stringT] = new Value<string>(""); prototypes[intT] = new Value<int>(0); prototypes[doubleT] = new Value<double>(0); } private: TypeT typeId; static vector<BaseValue *> prototypes; }; vector<BaseValue *> Type::prototypes(Type::unknownT);
Value
s through Type
, Attribute
contains only a name and type, so here is its implementation:class Attribute { public: Attribute(const string & name_, Type::TypeT typeId) : name(name_), type_(typeId) {} const string & getName() const { return name; } Type getType() const { return type_; } private: string name; Type type_; };
Class
itself. As multiple inheritance is probably not an issue for our purposes, we have just one pointer to a base class (which can be 0). The more important question is the attribute list: Should it hold only the attributes defined for this class or should it include all inherited attributes? While for the actual object value access a complete list is more useful (and much faster), for class maintenance it might be important to know which attribute was defined in which class. So we just keep both. For the complete list, the order might be significant: should the own attributes come first or the inherited ones? In most illustrations the inherited attributes come first, so we keep this order as well.findAttribute()
must do a reverse search. What shall we return from findAttribute()
? The STL way would be to return an iterator, but for applications with GUIs (to create new objects and assign values to its attributes based on selection lists) an index-based access to the attributes will be more appropriate. So findAttribute()
returns an index and getAttribute()
takes an index and returns an Attribute
. So the Attribute
lists need to be an indexed containers, so we choose vector
s for them.Class
is to create Object
s from it, so it has a method newObject()
which returns a pointer to an Object
. Do we need to keep a repository with references to all created objects? For a full reflection interface we should do this. But for actual applications this is nearly never useful, as objects of the same class are created for completely different purposes. But do we need the repository for internal use? It depends on what we want to do with objects after they were created. This leads directly to another important decision: What do we do with already existing objects if we add a new attribute to a class? One option is to add this attribute to all existing objects and assign it a default value. The other option is to leave these existing objects and add the new attribute only to new objects. This leads to differently structured objects of the same class at the same time, and then we must add some version information to the objects. But there is a third option: To forbid the modification of a class definition once an instance of that class was created. This is the easiest option, so we adopt it for our MOP and add a flag definitionFix
. With that flag, we can skip the object repository.addAttribute()
.class ClassDef { //typedefs Container, Iterator for attributes public: ClassDef(ClassDef const * base, const string & name_) : baseClass(base), name(name_), definitionFix(false) { baseInit(); effectiveAttributes.insert(effectiveAttributes.end(), ownAttributes.begin(), ownAttributes.end()); } template <typename iterator> ClassDef(ClassDef const * base, const string & name_, iterator attribBegin, iterator attribEnd) : baseClass(base), name(name_), ownAttributes(attribBegin, attribEnd), definitionFix(false) { baseInit(); effectiveAttributes.insert(effectiveAttributes.end(), ownAttributes.begin(), ownAttributes.end()); } string getName() const; Object * newObject() const { definitionFix = true; return new Object(this); } AttrIterator attribBegin() const; AttrIterator attribEnd() const; Attribute const & getAttribute(size_t idx) const; void addAttribute(const Attribute &); size_t getAttributeCount() const; size_t findAttribute(string const & name) const { // this does a reverse search to find the most derived AttributeContainer::const_reverse_iterator i; for (i = effectiveAttributes.rbegin(); i != effectiveAttributes.rend(); ++i) { if (i->getName() == name) { return distance(i, effectiveAttributes.rend()) - 1; } } return getAttributeCount(); } private: void baseInit() { if (baseClass) { baseClass->definitionFix = true; copy(baseClass->attribBegin(), baseClass->attribEnd(), back_inserter<AttributeContainer>(effectiveAttributes)); } } ClassDef const * const baseClass; string name; AttributeContainer ownAttributes, effectiveAttributes; mutable bool definitionFix; };
Object
, we have to think about Value
. We need a common interface to manage them, which we already called BaseValue
. But what interface do we need? The whole idea of Value
is to store values, so we need a set()
function. What parameter? The only thing we have is BaseValue
, so that's the parameter type. Pass by value, by reference, or by pointer? Definitely not by value, as BaseValue
is only an interface. On the other hand, what you pass is a value, so the parameter passing should be by value to let you pass temporaries. So one option would be to pass by const reference. But though this helps for the problem at hand, it doesn't cure the fundamental problem: you should have a value, but all you have is a polymorphic interface. The real solution here is the pimpl idiom, also known as Cheshire Cat, Envelope/Letter, or more generally Handle/Body. So we add a handle class, name it Value
, and look at it later again. For now we're still at BaseValue
.set(Value)
, so what about get()
? The return type of get()
would be Value
, and the implementation would look like:Value BaseValue::get() { return *this; // calls Value(BaseValue const &) }But that we can do directly, so
get()
doesn't make much sense.BaseValue
functions do we need? Values must be copied, so we add clone()
.asString()
for convenience5.class BaseValue { public: virtual ~BaseValue(){} virtual BaseValue * clone() const = 0; virtual string asString() const = 0; // fromString() virtual void set(Value const & v) = 0; // no get()! private: // Type info };
int
, double
, string
, .... And an int
value must hold an int
, a double
value a double
, etc. This looks like an opportunity for a template. So, let's define RealValue<T>
, derive it from BaseValue
, implement the inherited interface, and we're nearly done. But as RealValue<T>
is just a wrapper around T
with some additional functionality, but essentially still a T
, we should provide conversion in both directions, by providing a converting constructor and a conversion operator.template <typename PlainT> class RealValue : public BaseValue { public: RealValue(PlainT v) : val(v) {} RealValue * clone() const { return new RealValue(*this); } string asString() const { ostringstream os; os << val; return os.str(); } operator PlainT() const // conversion to plain type { return val; } RealValue<PlainT>::set(Value const & v) { val = v.get<PlainT>(); } private: PlainT val; };A note about
RealValue
: As we have conversion in both directions, we can use RealValue<T>
like T
:RealValue<int> i = 1; int j = i; RealValue<double> d = i + 5.2 / (i*2); cout << d << endl;Nearly: the following doesn't work:
RealValue<string> name, author = "Bjarne", title = "The C++ PL"; name = author + ": " + title; cout << name << endl;The reason is that the compiler only applies one user-defined conversion, but for string literals, you need two: from
char const *
to string
, and from string
to RealValue<string>
. If you want to work with RealValue<string>
outside the MOP, you should define a specialization:template <> class RealValue<string> : public BaseValue, public string { public: RealValue(string const & s) : string(s) {} RealValue(char const * s) : string(s) {} RealValue() {} RealValue * clone() const { return new RealValue(*this); } string asString() const { return static_cast<string>(*this); } // no operator string(), conversion to base automatically void set(Value const & v) { string::operator=(v.get<string>()); } };Note: Actually, its not really clean to derive
RealValue<string>
from std::string
, but as long as you don't delete a RealValue<string>
through a pointer to string
, it will work.Value
. As a handle class, it contains its body and cares for it. Its main job is to adopt/create and to delete its body. And it mirrors the interface of the body and forwards all messages. But it should also be a real value class, thus providing default and copy constructor and assignment. But how to implement the default constructor? As we don't know what type to create, we must create an empty handle without a body and check before forwarding if we actually have something to forward to. The assignment is essentially the set()
, so we skip the set()
.get()
. Of course, to return a Value
or BaseValue
doesn't make sense. But what about returning the RealValue
or even the wrapped underlying value? That would be really useful, but for that we have to tell get()
what we want as return type. So get()
becomes a member template and so can return whatever is inside the RealValue<>
.class Value // Value handle { public: Value(BaseValue const & bv) : v(bv.clone()) {} Value(Value const & rhs) : v(rhs.v ? rhs.v->clone() : 0) {} explicit Value(BaseValue * bv = 0) : v(bv) {} ~Value() { delete v; } Value & operator=(const Value & rhs) { // this is not a typical pimpl assignment, but a set() if (v) { if (rhs.v) { // fine, all v's exist v->set(rhs); } else { // the other v doesn't exist, so we must delete our own :-( BaseValue * old = v; v = 0; delete old; } } else { // we don't have a v, so just copy the other v = (rhs.v ? rhs.v->clone() : 0); } return *this; } template <typename PlainT> PlainT get() const { if (v) { RealValue<PlainT> const & rv = dynamic_cast<RealValue<PlainT> const &>(*v); return rv; // uses conversion operator } else { return PlainT(); } } std::string asString() const { if (v) { return v->asString(); } else { return string(); } } private: BaseValue * v; };
Object
. Now, as we have everything else, an Object
is mainly a container for its attribute values. To ease implementation, we will structurally mirror the attribute container in the class definition, so we use a vector
. As we have so much effort invested in our Value
handle, it would make sense to store that in the vector
. But for future extensions it will be easier to have the BaseValue
pointers directly available.ClassDef*
.class Object { public: explicit Object(ClassDef const * class_) : myClass(class_), values(class_->getAttributeCount()) { buildValueList(); } ClassDef const & instanceOf() const { return *myClass; } Value getValue(size_t attribIdx) const { return *values[attribIdx]; // calls Value(BaseValue &) } Value getValue(string const & attribName) const { size_t idx = instanceOf()->findAttribute(attribName); // should check for not found return getValue(idx); } void setValue(size_t idx, Value const & v) { values[idx]->set(v); } void setValue(string const & attribName, Value const &v) { size_t idx = instanceOf()->findAttribute(attribName); // should check for not found setValue(idx, v); } private: typedef vector<BaseValue *> ValueContainer; void buildValueList() { ClassDef::AttrIterator a; ValueContainer::iterator i = values.begin(); for (a = instanceOf()->attribBegin(); a != instanceOf()->attribEnd(); ++a, ++i) { *i = a->getType().newValue(); } } ClassDef const * const myClass; ValueContainer values; };Now the MOP is complete. Let's use it:
Product
class:ClassDef * product = new ClassDef(0, // no base class for Product "Product"); // name of classAdding attributes:
product->addAttribute(Attribute("Product Number", Type::intT)); product->addAttribute(Attribute("Name", Type::stringT)); product->addAttribute(Attribute("Price", Type::doubleT)); product->addAttribute(Attribute("Weight", Type::doubleT));Creating the
Book
class with an attribute list:list<Attribute> attrL; attrL.push_back(Attribute("Author", Type::stringT)); attrL.push_back(Attribute("Title", Type::stringT)); attrL.push_back(Attribute("ISBN", Type::intT)); ClassDef * book = new ClassDef(product, // base class "Book", attrL.begin(), attrL.end());Creating an object:
Object * bscpp(book->newObject());
ProductNo
):bscpp->setValue(0, RealValue<int>(12345));Same for a string value:
bscpp->setValue(4, RealValue<string>("Bjarne Stroustrup"));Better way: set value by name this gives the most derived attribute:
bscpp->setValue("Title", RealValue<string>("The C++ Programming Language")); bscpp->setValue("Weight", Value<double>(370));
ClassDef::AttrIterator a; size_t idx; for (a = book->attribBegin(), idx = 0; a != book->attribEnd(); ++a, ++idx) { cout << a->getName() << ": " << bscpp->getValue(idx).asString() << endl; }and we get:
Product Number: 12345 Name: Price: Weight: 370 Author: Bjarne Stroustrup Title: The C++ Programming Language ISBN:So, our MOP is complete. For our sample application, you have to add a class repository, some nice GUI to define classes and objects, creating the index for the search machine, provide an interface for
ShoppingCart
, but then you're done, and Susan is happy as she now can create her own new product categories at runtime.
ShoppingCart
in our example, you'll find that it isn't so easy. If all classes are dynamic classes, all access must go through the MOP:getName()
for Book
:string bookGetName(Object const * book) { if (book->instanceOf().getName() != "Book") { /* throw some exception */ } string name; // name = book->author + ": " + book->title; it was so easy... string author = book->getValue("Author").get<string>(); string title = book->getValue("Title").get<string>(); name = author + ": " + title; return name.substr(0, 40); }For a lot of applications, it would be useful to provide some classes of a hierarchy as C++ classes, e.g.
Product
, but still let the user add classes of the same hierarchy at runtime, e.g. TShirt
. So, let's look at this. If we want access through our MOP to C++ classes, we need a getValue()
to which we can give the attribute we want to access at runtime. So, here it is:Value getValue(Object *o, MemberPointer mp) { return o->*mp; }The magic lies in
'->*'
: This is the pointer-to-member selector of C++.class Product { // ... protected: RealValue<double> price; }; class Book : public Product { public: // ... private: RealValue<string> author, title; RealValue<double> weight; }; Book b, *bp;A pointer-to-member is a type that is derived from two other types: The type of the base object (
Book
in our example) and the type of the member (RealValue<>
). The type decorator for a pointer-to-member is '::*
', so let's define two variables with initialization:RealValue<string> Book::* bookStringMemPtr = &Book::author;
RealValue<double> Book::* bookDoubleMemPtr = &Book::weight;
The pointer-to-member selector comes in two variations: as '.*
' you can apply it to references of the class and as '->*
' it takes a pointer. It is a binary operand, as left operand it takes a reference (or pointer) to an object and as right operand a pointer-to-member. So, with the above definitions, you can do things likeb.*bookStringMemPtr = "Bjarne Stroustrup"; // assigns b.author bookStringMemPtr = &Book::title; bp->*bookStringMemPtr = "The C++ Programming Language"; // assigns b.authorOf course, as
title
is a private member of Book
, the assignment of the pointer-to-member must be at the scope of that class. But the pointer-to-members themselves can be used even if you don't have access privileges to the members (as long as you have access to the pointer-to-member).RealValue<double> Book::*
, RealValue<double> Product::*
, and BaseValue Product::*
are different types. But are there conversions? The C++ standard provides a conversion from a pointer-to-member of a base class to a pointer-to-member of a derived class. That makes sense: You can apply an offset to a member of a base class to the base address of a derived object as well, as the base is a part of the derived object7. So you can assignbookDoubleMemPtr = &Product::price;
as the price
is part of each Book
instance.RealValue<double> Product::* &Book::author;
as author
is not a member of each instance of type Product
.RealValue<double> Book::*
to BaseValue Book::*
, though it would be save: If the result type of the pointer-to-member selector is a reference to a derived class, it can be safely converted to a reference of a respective base class, so it would also be safe to let the compiler do the conversion automatically and therefore also convert the pointer-to-members themselves. As already mentioned, the standard doesn't provide (implicit) and even doesn't allow (explicit through static_cast
) that conversion, probably because the committee didn't see any use for pointer-to-data-members at all (see [5]), and for pointer-to-member-functions that conversion really doesn't make sense.reinterpret_cast
, but the only thing you can safely do with a reinterpret_cast
ed thing is to reinterpret_cast
it back, and for that you have to store the original type as well. So we use another option: we just define the conversion! But as C++ doesn't allow you to define your own conversions to compiler-provided types (and in this sense the pointer-to-members are compiler defined, though the involved single types like Book
or BaseValue
are user-defined), we have to define wrapper classes around them.template <typename BaseType, typename BaseTargetType> class MemPtrBase { public: virtual BaseTargetType & value(BaseType & obj) const = 0; virtual BaseTargetType const & value(BaseType const & obj) const = 0; protected: MemPtrBase() {} virtual ~MemPtrBase() {}; private: MemPtrBase(MemPtrBase const &); MemPtrBase & operator=(MemPtrBase const &); }; template <typename BaseType, typename BaseTargetType, typename TargetType> class TypedMemPtr : public MemPtrBase<BaseType, BaseTargetType> { public: TypedMemPtr(TargetType BaseType::* ptr) : p(ptr) {} BaseTargetType & value(BaseType & obj) const { return obj.*p; } BaseTargetType const & value(BaseType const & obj) const { return obj.*p; } private: TargetType BaseType::* p; }; template <typename BaseType, typename BaseTargetType> class MemPtr // this is a handle only { public: template <typename BaseType2, typename TargetType> explicit MemPtr(TargetType BaseType2::* ptr) : p(new TypedMemPtr<BaseType, BaseTargetType, TargetType>(static_cast<TargetType BaseType::*>(ptr))) {} ~MemPtr() { delete p; } BaseTargetType & value(BaseType & obj) const { return p->value(obj); } BaseTargetType const & value(BaseType const & obj) const { return p->value(obj); } private: MemPtrBase<BaseType, BaseTargetType> * p; };Some notes to the code:
BaseType
is used for the class to which a pointer to member is applied (e.g. Book
), TargetType
is the result type to which a pointer-to-member points (RealValue<double>
), and BaseTargetType
is the base class of TargetType
(BaseValue
). MemPtrBase<>
is the common base class as we need it (e.g. MemPtrBase<Book, BaseValue>
, which stands for BaseValue Book::*
), TypedMemPtr<>
hold an actual C++ pointer-to-member (TypedMemPtr<Book, RealValue<double> >
), and MemPtr<>
is a handle class around MemPtrBase<>
to store them in a container. Here, the actual access function is the value()
member function. If you want, you can add a global operator '->*
' (as template function), but you can't provide the operator by a member function (as the left operand is not the class instance), and you can't overload '.*
' (this is one of the few non-overloadable operators).MemPtr
constructor is a member template with two template parameters: a BaseType2
and the TargetType
. The second one is clear as it defines the actual TypedMemPtr
to be constructed, but the BaseType2
is not so obvious. If we omit the BaseType2
, so only havingtemplate <typename BaseType, typename BaseTargetType> class MemPtr // this is a handle only { public: template <typename TargetType> explicit MemPtr(TargetType BaseType::* ptr) : p(new TypedMemPtr<BaseType, BaseTargetType, TargetType>(ptr)) {} // ... }and then we try to create a
MemPtr<Book, BaseValue> mp2(&Product::price);some compilers give an error, as they cannot fiddle out the correct conversion. This would be to convert
RealValue<double> Product::*
to RealValue<double> Book::*
, which should be done automatically, and then to instantiate MemPtr
's constructor with RealValue<double>
as TargetType
.MemPtr<Book, BaseValue> mp2(static_cast<RealValue<double> Book::*>(&Product::price));but that's quite a lot to type. It's actually much easier to move that explicit conversion into the constructor itself and just provide an additional template parameter, as shown in the implementation above. The compiler checks the conversion anyway, so you will get a compile time error if that conversion is not allowed (e.g. if you try to convert a
RealValue<double> Book::*
to RealValue<double> Cd::*
.MemPtr
s allow you to access the attribute values of an ordinary C++ object. This helps for one part of the MOP. But what about the attributes themselves? The compiler has the necessary knowledge, but unfortunately there is no standard way to access that knowledge at runtime. So we must provide it and define an interface for it. To allow a smooth integration with our existing ClassDef
, we provide an Attribute
iterator as interface. For now, we provide the information about the attributes manually, but in a following article we'll explore the use of a pre-processor for that.Book
we provide the following functions:class Book : public Product { typedef MemPtr<Book, FinalValue> MemberPtr; public: // as before static size_t ownAttribCount() { return 5; } static Attribute * ownAttribBegin() { static Attribute a[] = {Attribute("Author", Type::stringT), Attribute("Title", Type::stringT), Attribute("Publisher", Type::stringT), Attribute("Price", Type::doubleT), Attribute("Weight", Type::doubleT) }; return a; } static Attribute * ownAttribEnd() { return ownAttribBegin() + ownAttribCount(); } static MemberPtr * memberBegin() { static MemberPtr m[] = {MemberPtr(&Book::productNo), MemberPtr(&Product::weight), MemberPtr(&Book::author), MemberPtr(&Book::title), MemberPtr(&Book::publisher), MemberPtr(&Book::price), MemberPtr(&Book::weight) }; return m; } static MemberPtr * memberEnd() { return memberBegin() + 7; } private: RealValue<string> author, title, publisher; RealValue<double> price, weight; };Please note the difference between
ownAttribBegin()
and memberBegin()
: the first provides information only about the own attributes, while the latter provides access also to base class members. This separation makes sense: while on object level all data members build together one object, on the class level the base class is a different entity and should be available as common base class for the meta object protocol as well. But this separation has consequences: we can't derive a C++ class from a MOP class (but this is definitely no real restriction) and the C++ base class must be also made known to the MOP (but that's useful anyway).baseClass
in ClassDef
must be made a ClassDef
instance as well, as noted above. The C++ base classes are not of much use for our application.ClassDef
from a C++ class; it's so easy that we can even provide a helper function for that:template <typename CppClass> ClassDef makeClass(ClassDef const * base, string const & name) { return ClassDef(base, name, CppClass::ownAttribBegin(), CppClass::ownAttribEnd()); }Now, we can build our
ClassDef
s for Product
and Book
and create instances from them:ClassDef base(makeClass<DynaProduct>(0, "Product")); ClassDef book(makeClass<Book>(base, "Book")); book.newObject();But stop -- though this works, it's not what we want: now, the instances are not genuine C++ objects, but MOP objects, and all access must still go through the MOP.
Object
interface. The OO way to do that is to derive our Product
from Object
, but though other OO languages like Smalltalk do that, I think there's a better, less intrusive option: we provide an adaptor.Object
and containing a Product
member or using multiple inheritance and derive the adaptor from Object
and Product
. For simplicity, we will use the first option, but real world applications often benefit from the second approach. So we provide a wrapper class CppObject
that is derived from Object
and that holds the original C++ object. It implements the interface of Object
(getValue()
and setValue()
) through our MemPtr
s:template <typename OrigClass> class CppObject : public Object { typedef MemPtr<OrigClass, BaseValue> MemberPtr; public: CppObject(ClassDef const * myClass) : Object(myClass), myObject(), members(OrigClass::memberBegin()) {} virtual Object * clone() const { return new CppObject(*this); } using Object::getValue; // importing getValue(name) using Object::setValue; // importing setValue(name) virtual Value getValue(size_t idx) const { return members[idx].value(myObject); } virtual void setValue(size_t idx, Value const & v) { BaseValue * p = &(members[idx].value(myObject)); p->set(v); } private: MemberPtr * members; OrigClass myObject; };A useful rule of OO design says that only leaf classes should be concrete, so let's define
Object
as abstract base class and create a new class DynaObject
that resembles our former Object
for real MOP class instances (Fig. 7).prod.newObject()
, we still get it wrong: we now get a DynaObject
, but we want a CppObject<Product>
. To solve that, we must provide the ClassDef
with a means to create the correct kind of object, and the simplest way to do that is a factory method: we provide a static creation function in CppObject<>
and DynaObject
, give a pointer to that function to the ClassDef
's constructor, store it and use that function in ClassDef::newObject()
:DynaObject
and CppObject
:Object * DynaObject::newObject(ClassDef const * myClass) { return new DynaObject(myClass); } template <typename OrigClass> Object * CppObject<OrigClass>::newObject(ClassDef const * myClass) { return new CppObject(myClass); }Changes to
ClassDef
: class ClassDef { public: typedef Object * (*CreateObjFunc)(ClassDef const *); template <typename Iterator> ClassDef(ClassDef const *, string const &, CreateObjFunc objFunc, Iterator, Iterator) : // ... createObj(objFunc) { // ... } ClassDef(ClassDef const *, string & const name_, CreateObjFunc objFunc) : // ... createObj(objFunc) { // ... } Object * newObject() const { definitionFix = true; return (*createObj)(this); } // ... as before private: const CreateObjFunc createObj; // ... as before };And a simple change to
makeClass
:template <typename CppClass> ClassDef makeClass(ClassDef const * base, string const & name) { return ClassDef(base, name, CppObject<CppClass>::newObject, CppClass::ownAttribBegin(), CppClass::ownAttribEnd()); }Now, everything works. Well -- nearly. If we now try to create a
ClassDef
for Product
with makeClass
the compiler complains about creating an abstract class: makeClass
gives the ClassDef
constructor a pointer to CppObject<Product>::newObject()
, and that creates a Product
instance as part of CppObject<Product>
. This is easily fixed: just call the ClassDef
constructor directly with a null-pointer for the creation function, thus prohibiting the creation of a Product instance through the MOP.ClassDef base(0, "Product", 0, Product::ownAttribBegin(), Product::ownAttribEnd()); ClassDef book(makeClass<Book>(&base, "Book"));You can create instances of them
book.newObject();
Product
ClassDef * tShirt = new ClassDef(&base, "T-Shirt", DynaObject::newObject); tShirt->addAttribute(Attribute("Size", Type::stringT)); tShirt->addAttribute(Attribute("Color", Type::stringT)); tShirt->addAttribute(Attribute("Name", Type::stringT)); tShirt->addAttribute(Attribute("Price", Type::doubleT)); classReg.registerClass(tShirt);and manipulate instances of existing classes and new classes through the MOP:
Object * ecpp(book.newObject()); ecpp->setValue(5, RealValue<double>(22.50)); ecpp->setValue(0, RealValue<int>(23456)); ecpp->setValue(2, RealValue<string>("Scott Meyers")); ecpp->setValue("Title", RealValue<string>("Effective C++")); ecpp->setValue(6, RealValue<double>(280)); size_t idx; cout << "ecpp:" << endl; for (a = book.attribBegin(), idx = 0; a != book.attribEnd(); ++a, ++idx) { cout << a->getName() << ": " << ecpp->getValue(idx).asString() << endl; } cout << ecpp->getValue("Author").asString() << endl;And a dynamic object:
Object * ts(tShirt.newObject()); ts->setValue(0, RealValue<int>(87654)); ts->setValue(2, RealValue<string>("XXL")); ts->setValue("Color", RealValue<string>("red")); ts->setValue("Price", RealValue<double>(25.95)); ts->setValue("Weight", RealValue<double>(387)); for (size_t idx = 0; idx != 4; ++idx) { cout << ts->getValue(idx).asString() << endl; }
Product
interface. For instances of C++ classes you can modify the CppObject<T>
to derive from T
as we have discussed before, or better, to provide a member function in CppObject
that returns a pointer to myObject
. And for instances of MOP classes you can define a wrapper around an Object
that implements the Product
interface:class DynaProduct : public Product { public: DynaProduct(Object const * o) : obj(o) {} virtual std::string getName() const { Value v = obj->getValue("Name"); return v.get<std::string>(); } virtual double getPrice() const { Value v = obj->getValue("Price"); return v.get<double>(); } virtual double getWeight() const { Value v = obj->getValue("Weight"); return v.get<double>(); } private: Object const * const obj; };And you can't access normal C++ instances of
Book
through the MOP; to solve this, you could add another constructor that adopts the C++ instance by copying (and consequently you should delete the original one to avoid an object that exists twice) or you could modify the wrapper to hold only a pointer to the C++ object and add a member function to return the controlled C++ object on request: Here, we use the first approach:template <typename OrigClass> CppObject<OrigClass>::CppObject(ClassDef const * myClass, OrigClass const & obj) : Object(myClass), myObject(obj), // calls the copy-ctor of OrigClass, which must be accessible members(OrigClass::memberBegin()) {}And now, you can do this:
Book b("Bjarne Stroustrup", "The C++ Programming Language", "Addison-Wesley", 27.50, 370); CppObject<Book> mb(*book, b); Object * ob = &mb; cout << "C++ object through MOP" << endl; for (a = ob->instanceOf()->attribBegin(), idx = 0; a != ob->instanceOf()->attribEnd(); ++a, ++idx) { cout << a->getName() << ": " << ob->getValue(idx).asString() << endl; }But in general, it's just important that you can define basic classes in C++ at programming time but allow the user to derive own classes from these base classes at runtime.
offsetOf
macro that is still in widespread use for that purpose.Could the same kind of reflection be achieved with get
/set
functions instead of pointers-to-members? Perhaps, yes. Some component mechanisms use that approach (e.g. Borland's VCL). In that case, a get()
and set()
function for each attribute and a specialized TypedMemPtr<>
for each attribute for each class is required. That's a lot of work, but with a respective pre-processor or compiler support that's not a point. But it's still much more intrusive to add all the getters and setters; and if they are public, they break encapsulation. Though pointers-to-members allow direct access to the data, that encapsulation leak can be much better controlled.
The second remark relates to RealValue<>
. Are they really necessary for true reflection purpose? Actually not. In that case, you could remove BaseTargetType
from all MemPtr
templates, return the TargetType
in TypedMemPtr
's getter function, and make the getter function of the MemPtr
handle a member template function analogous to the getter function of Value
. In the source code for this article ( http://www.vollmann.com/download/mop/index.html) you'll find a sample implementation for that.
Though this seems to be a major advantage for pure reflection applications like persistency libraries, in fact I found it in most cases quite useful to have a base class like DbValue
for all persistent attributes to provide additional functionality like dirty flags, type conversion specifics, etc.
[1] Gregor Kiczales, Jim des Rivières, Daniel G. Bobrow: "The Art of the Metaobject Protocol", MIT Press 1991, ISBN 0-262-61074-4
[2] James O. Coplien: "Advanced C++ Programming Styles And Idioms", Chapters 8-10, Addison-Wesley, 1992, ISBN 0-201-54855-0
[3] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Michael Stal: "Pattern-Oriented Software Architecture: A System of Patterns", Wiley 1996, ISBN 0-471-95869-7
[4] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: "Design Patterns", Addison-Wesley 1994, ISBN 0-201-63361-2
[5] Michael S. Ball, Stephen D. Clamage: "Pointers-to-Members", C++ Report 11(10):8-12, Nov/Dec 1999
class
is a reserved word in C++, and I don't like identifiers to differ only in case from others I have chosen ClassDef
here.fromString()
would also be useful, but we omit it here.