728x90

http://blog.naver.com/zacronan?Redirect=Log&logNo=40018174412
http://airtight.yonsei.ac.kr/research/COM/thread_apartment.htm

1-2 스레딩 모델 

먼저 이 부분을 설명하기 위해서는 아파트먼트(Apartment)라는 것에 대해 설명이 필요하다. 아파트먼트는 하나의 프로세스 내에서 여러 개의 스레드를 가지고 각각의 스레드 동기화 문제를 해결하기 위해 나온 것으로 각 오브젝트를 가지고 있는 하나의 방이라는 개념으로 이해하면 된다. 각 아파트먼트당 하나의 스레드가 존재하며 오브젝트는 적당한 아파트먼트 안으로 들어가게 된다. 

이러한 스레딩 모델(Threading Model)은 레지스트리에서 CLSID의 스레딩 모델의 값을 보면 된다. 가능한 경우는 스레딩 모델이 아예 없거나 아파트먼트, 프리(Free), 보스(Both)가 되는 것이다. 아파트먼트는 STA 모델을 가리키며 프리는 MTA 모델을, 보스는 STA, MTA 둘 다 지원한다는 것을 말한다. 이 값이 아예 없다는 것은 main STA에서 동작한다는 것을 말하고 이 main STA는 프로세스가 초기에 주 스레드에서 호출한 CoInitializeEx 값에 의해 결정된다. 




① STA(Single Threaded Apartment) 

STA 내에서 서버는 각각의 아파트먼트 내에 각 오브젝트가 존재하며 하나의 프로세스 내에서 각 아파트먼트 내에 각 오브젝트가 들어가 있게 된다. 이 경우는 CoInitializeEx(NULL,COINIT_APARTMENTTHREADED)를 각각의 스레드당 호출하여 초기화 된다. 이러한 각각의 오브젝트 호출은 메시지에 의해 동기화 된다. 즉 이러한 메시지를 통해 동기화 된다는 것은 메시지 루프를 가져야 한다는 얘기이다. 

하나의 아파트먼트 내에서는 프록시의 개입 없이 직접적으로 호출할 수 있으나 다른 아파트먼트 내에서는 이렇게 될 수는 없다. 즉 하나의 메소드 호출은 클래스 네임이 OleMainThreadWndClass를 가지는 윈도우 클래스가 등록한 WndProc로 가서 적당한 Interface의 메소드를 호출하게 된다. 이러한 모델 내에서의 호출은 그림 5와 같다. 










그림 5 : STA에서의 메시지 큐 


그림 5에서 보는 것처럼 서버로의 모든 호출은 큐(Queue) 속에서 들어가며 GetMessage,DispatchMessage에 의해 해당 오브젝트로 전달된다. 




② .MTA(Multi Threaded Apartment) 

MTA 내에서 하나의 아파트먼트 내에 여러 개의 스레드와 오브젝트가 들어가게 되며 따라서 오직 한 번만 CoInitializeEx(NULL, COINIT_MULTITHREADED)를 호출하면 된다. 이 경우에는 여러 개의 스레드가 동시에 오브젝트의 메소드로 접근할 수 있기 때문에 Reentrant하도록 소스가 작성되거나 동기화 문제를 코드 안에서 해결해야 한다. 하지만 서버 안에서의 스레드간 인터페이스 전달에 프록시가 개입할 필요가 없다. 

또한 STA로 작성하는 것보다 빠른 속도를 보이는 멀티 스레드의 장점을 충분히 활용할 수 

가 있으며 하나의 프로세스 내에 오직 하나의 MTA만 만들어질 수 있다. 




③ 혼합 모델 

이 모델은 프로세스가 한 개의 MTA와 한 개 이상의 STA를 가진다는 것을 의미한다. 




④ STA와 MTA와 혼합 모델에서의 선택 

어떤 오브젝트의 모델을 선택하는 것은 각기 장단점이 존재하기 때문에 상황에 맞게 선택해야 하는데, STA에서는 프로그래머가 스레드간의 동기화를 신경쓸 필요가 없다. 그러나 여기서 다른 아파트먼트로 인터페이스를 전달하기 위해서는 프록시의 개입을 필요로 하므로 느려질 수 있으며, 또한 객체로의 메소드 호출이 메시지 루프(Loop)를 통해 한번에 하나씩 만 처리되기 때문에 멀티 스레드의 장점을 충분히 활용할 수가 없게 된다. 

그러나 MTA를 선택한 경우 서버 객체의 구현자는 여러 개의 스레드가 동시에 메소드 호출을 수행할 수가 있으므로 코드는 Reentrant하게 작성되거나 Mutex, Critical Section, Event를 통해 동기화 코드를 작성해야 한다. 따라서 작성이 어려워지게 되지만 이 경우라고 해도 수많은 사용자가 사용할 수 있는 오브젝트라면 한번 고려해볼 만하다.


출처 : http://broneri.tistory.com/372

728x90
728x90

struct _tagPoint{
     int x; 
     int y;
};

POINTS는 선언되지 않은 심볼이므로 struct _tagPoint POINTS;라고 하면,

POINTS라는 이름의 구조체 변수가 생성됩니다.

당연히 POINTS는 타입으로 선언된 것이 아니므로 POINTS s;라는 구문은 오류입니다.

struct의 임무는 뒤에 나오는 식별자 _tagPoint가

구조체 태그이름임을 나타내는 지시어입니다.

이는 C와의 하위 호환성을 위해 남겨둔 것으로,

C++에서는 선택적으로 사용해도 되고, 사용하지 않아도 상관 없습니다.

C에서 변수선언은 기본적으로

"변수 선언이 가능한 키워드로 시작"될 때만

변수 선언으로 인식하도록 되어 있는 구조입니다.

따라서 한 문장의 시작이 int, char, void 등의 타입을 나타내는

'키워드'여야만 유효한 선언으로 간주하는 구조로 설계되었습니다.

enum이나 union, struct등의 사용자 정의 타입은

그냥 선언한 이름만 쓰면 변수 선언으로 인식하지 못하는 구조이죠.

그래서 해당 타입이 무엇으로 선언되어 있는지를

컴파일러에게 알려주도록 C언어는 설계되었습니다.


 

하지만 C의 슈퍼셋으로 설계된 C++은

사용자 정의 타입 역시 기본 데이터 타입과 통합된 타입 시스템으로 관리하므로,

더이상 사용자 정의 타입의 변수를 생성할 때 그 종류를 알려줄 필요가 없지만,

C와의 하위 호환성을 위해 남겨둔 것 뿐입니다.

C에서 사용자 정의 데이터 타입의 변수를 선언하려면

사용자 정의 타입임을 나타내는 키워드(enum, struct, union 등)와

선언된 타입의 태그 이름을 같이 적어 주어야 했습니다.

하지만 매번 struct를 써주는 것도 번거로운 일이므로,

C에서는 사용자 정의 타입 이름도 typedef 로 재정의 가능하도록 허용했습니다.

그래서 C에서는 사용상의 편의를 위해서 구조체 선언과 동시에

구조체의 태그 이름으로 별 의미없는, 잘 안쓰는 이름을 붙이고

typedef 이름을 잘 쓰이고 읽기 쉬운 이름으로 붙이는 관습이 생겨난 것입니다.

C에서

struct POINTS
{
    int x, y;
};

POINTS s;

라고 변수를 정의하면, 컴파일 에러를 냅니다.

이는 POINTS가 타입이름으로 선언된 이름이 아니라,

사용자 정의 타입의 태그 이름으로만 선언된 이름이기 때문이죠.

태그이름과 타입이름은 엄연히 다른 것이니까요.

하지만 C++ 에서 위의 코드는 아무런 문제가 없습니다.

C++의 타입 시스템은 사용자정의 타입도 기본 데이터 타입과 마찬가지로 취급하므로

구조체 태그 이름을 선언하는 순간, 그 이름이 타입 시스템 내에서

유효한 타입이름으로 등록되어 사용할 수 있게 되므로, C++에서는 사실상

typedef로 타입을 선언하나, 그냥 struct만으로 선언하나 아무런 차이가 없습니다.

단, 여기서 주의할 점은, 다음과 같은 구문에서,

typedef struct
{
    int x, y;
} POINTS;

이와 같은 구문은 컴파일러는 이렇게 해석합니다.

a. 이름없는 구조체를 만들고,

b. 여기에 두 멤버변수 x,y를 추가하고

c. 이 이름없는 구조체의 타입이름을 POINTS로 재정의한다.

여기서 문제가 되는 것은 '이름없는 구조체'를 만드는 것이

컴파일러에 따라 지원될 수도 있고, 안될 수도 있다는 것이죠.

'이름없는 구조체'는 언어 표준에서 명시된 것이 아니므로,

이를 지원하는 컴파일러는 살짝 표준에서 빗겨나가 있긴 하지만

대부분의 컴파일러가 지원하는 기능이므로 대부분 저렇게 쓰기도 합니다.

하지만, 진짜 언어 표준만을 지원하는 컴파일러는

위와 같은 구문은 컴파일시 에러나 워닝을 냅니다.

그래서 그와 같은 컴파일러에서는 구조체를 typedef로 새 타입이름으로 선언하더라도,

임시로 사용할 태그 이름이 필요하게 됩니다.

따라서, 이와 같은 컴파일러에서는 다음과 같이 임시 태그 이름을 사용하여

typedef로 타입 이름을 선언해야 합니다. 

(물론, 번거롭긴 하지만 표준과 일치하는 방법입니다.)

typedef struct _tagPoint{
     int x; 
     int y;
}POINT;

이와 같은 구문은, 어떤 컴파일러를 쓰더라도 정확히 동일한 결과를 내므로

안전한 기법이라고 할 수 있겠습니다.

POINTS라는 이름이 끝에 온다면,

이는 typedef 타입 이름으로 간주되 POINTS라는 이름이 struct 뒤에 바로 온다면

구조체 태그 이름으로 간주된다는 차이가 있습니다.

즉, 첫번째 예제의 POINTS는 typedef 타입 이름이 되며,

두번째 예제의 POINTS는 구조체 태그 이름이 됩니다.

반면, 세번째 예제에서는 typedef구문인데,  POINTS는 구조체 태그 이름만 있고

typedef로 선언할 새 타입 이름이 없으므로 오류입니다.

typedef의 기본 구문은,

typedef (기존타입) (새타입이름);

과 같은 형식입니다.

typedef struct POINT{
     int x; 
     int y;
};

와 같이 쓴다면, struct POINT { ... } 이 부분은 (기존타입)에 해당하지만,

(새타입이름)에 해당하는 부분이 없으므로 컴파일러는 typedef 부분을 무시하고

그냥 POINT라틑 태그이름으로 구조체를 선언하게 되는 것입니다.


728x90
728x90

쉽게 말해 enum 은 열거형 변수 입니다.

"어떻게" 생각하면 struct와 다르냐? 라고 물을 수 있지만.

"전혀" 다릅니다.

 

보통 enum은 상수를 정의하는데에 많이 쓰입니다.

소스의 가독성을 높인다거나 상수에 의미를 부여해서 쓸 수 있습니다.

 

전처리기인 #define 을 사용하거나, 전역 변수를 const로 선언해서 상수처럼 사용이 가능하지만

enum을 상수로 생각해서 처리할때의 이점이 있습니다. (뒷부분에 설명하겠습니다.)

 

우선 이넘(enum)을 선언하는 방법은 다음과 같습니다.

 

enum {mon, tue, wed, thr, fri, sat, sun } day;

 

여기서 day를 열거형 변수라 하고 그 하위의 항목(원소) 들 ( mon, tue, wed, thr, fri, sat, sun)로 구성됩니다.

 

특별하게 값을 지정하지 않는다면 enum의 각 원소들은 순차적인 "정수" 값을 갖게 됩니다.

이때 갖게되는 값은 mon부터 0, 1, 2 ... 6 까지 각 각 가지게 됩니다.

 

특별하게 값을 지정해서 enum을 사용한다면

 

enum {mon=100, tue, wed=200, thr, fri=300, sat, sun } day;

 

이렇게 선언을 하게 되면, mon은 당연히 100일거고,, tue는... 150일까요?

tue는 101을 갖게 됩니다.

 

enum의 값을 정하는 정의는 "이전 원소 값의 + 1" 입니다.

 

 

그럼. "왜 이넘을 사용해야 하는가.." 입니다.

 

사실 enum은 필요없을 지도 모릅니다.

#define 이 있고,

const int 가 있습니다.

 

하지만 C++ 이 enum에게 준 몇가지의 특별한 이유 때문에 enum을 사용합니다.

 

① 기억이 용이하다.

enum대신 정수형 변수를 대신 사용할 수 도 있습니다. 하지만, 이렇게 될때 각 정수의 의미가 무엇인지를 만드는 사람이 기억하고 있어야 하죠. 값이 많아질 수록 혼란스러울겁니다. 보통, 사람은 숫자보다 문자를 더 잘 기억합니다. 그리고, enum의 원소들을에게 의미를 부여한 이름을 준다면 더 기억하기가 쉽겠죠..

 

② 소스의 가독성(Readability)이 높아진다.

enum의 원소만 봄으로써, 그 상수의 의미를 보다 쉽게 파악 할 수 있고, 소스 전체의 가독성을 높이는데 도움이 됩니다. 예를 들어 whatdays = 0; 이라고 표현한다면, 이게 월요일인지,, 일요일인지, 도무지 알 길이 없습니다. 하지만 whatdays = mon; 이라고 한다면, 두말할나위없이 이해할 수 있겠죠. 두명이상의 Project나 팀단위 작업을 할때에는 알고리즘을 아무리 잘 짜도 소스가 X같으면, 프로젝트 진행이 어려워집니다. 따라서 이런 팀단위 작업에는 가독성이 아주 중요시 됩니다.

 

③ 안전하다.

앞서 예를든 days에 대입될수 있는 값은 7개중 하나로 제한이 되기때문에 사용자의 실수를 컴파일러가 미리 예방해줄 수 있습니다. 따라서 변수형을 사용하는것보다 안전하죠.

 

 

필수는 아니지만 있으면 좋다. 이게 이넘(emum)입니다.

 

 

enum을 보다 효율적으로 사용하는 방법은

 

typedef를 사용하는겁니다.

 

바로 예부터 보자면...

 

typedef enum CDays {enum {mon, tue, wed, thr, fri, sat, sun };

 

이제 CDays는 하나의 타입으로 생성된겁니다.

 

CDays WhatDay(); 와 같이 오늘이 몇요일인지 리턴하는 함수를 작성 할 수도 있구요.

 

CDays Day = WhatDay();

 

와 같이 Enum을 효과적으로 처리할 변수를 만들어 사용할 수 있습니다.

[출처] enum 사용법|작성자 다람쥐


enum 사용 예제 입니다. 

  1. #include <stdio.h>  
  2. enum week {sun, mon, tue, wed, thu, fri, sat};  
  3. int main(void)  
  4. {  
  5.     enum week day1;  
  6.     day1=fri;  
  7.     printf("day1 : %d\n", day1);  
  8.     printf("fri : %d\n", fri);  
  9.     day1=mon;  
  10.     printf("day1 : %d\n", day1);  
  11.     printf("mon : %d\n", mon);  
  12.     return 0;  
  13. }  


// 실행 결과


출처 : http://kimgagakk.tistory.com/341

728x90
728x90

UNION

C에서는 공용체라 부르는 특수한 타입이 있다. 공용체의 멤버로 선언된 변수들은 메모리 어드레스를 공유하게 된다. 어찌보면 별 필요없는 기능이라고 생각될 수도 있지만 메시지를 만들어 데이터를 읽고 쓰는데는 아주 유용한 기능이다.

선언 및 사용

union의 선언 방법은 struct와 동일하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
union simple_message {
int a;
int b;
};
 
int main(int argc, char **argv)
{
union simple_message smessage;
smessage.a = 1;
 
printf("smessage.a: %dn", smessage.a);
printf("smessage.b: %dn", smessage.b);
}

출력 결과를 보면 공용체 smessage의 멤버 a의 값과 b의 값이 동일한 것을 볼 수 있다. 멤버 a의 값이 변경된 것이 b에도 적용된 것 처럼 보이지만 사실은 공용체 멤버 a와 b가 동일한 메모리 주소를 공유하고 있는 것이다.

Example


위와 같은 데이터를 읽고 쓴다고 하자. 4 bytes 데이터 이므로 int 값에 bit 연산으로 데이터를 채워 넣는 방법을 쓸 수도 있지만 가독성이 떨어지며 코드길이가 길어져 한참후에 코드를 들여다 봤을 때 이해하는 것이 불가능해 질 수도 있다. 하지만 union을 사용하면 데이터를 다루기도 쉽고 이해하기도 편리하다는 것을 깨닫게 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <linux/types.h>
#include <memory.h>
 
struct regdata {
 __u16 opcode:4;
 __u16 prtaddr:5;
 __u16 regaddr:5;
 __u16 ta:2;
 __u16 data;
} __attribute__((packed));
 
union data {
 struct regdata reg0;
 __u32 rdata;
};
 
int main(int argc, char** argv)
{
 union data message;
 
 memset(&message, 0x0, sizeof(union data));
 message.reg0.opcode = 0x4;
 message.reg0.regaddr = 0x3;
 message.reg0.data = 0xE8A3;
 
 printf("message: %08xn", message.rdata);
 
 return 0;
}

실행결과는 message: e8a30604 이다. intel 시스템일 경우 little endian을 사용함으로 big endian으로 읽어보면 제대로데이터가 들어간 것을 알 수 있다.


728x90
728x90

전처리문이란?

실질적인 컴파일 이전에 미리 처리되는 문장을 가리킨다. 선행처리기라고도 한다. 따라서 컴파일러는 사용자가 작성한 코드를 컴파일하기 전에 전처리문에서 정의해 놓은 작업들을 먼저 수행한다.

종류로는 #define, #if, #ifdef, #ifndef, #defined, #undef 등이 있다. 이것은 기존에 있는 방대한 소스 코드를 지우지 않고 활성화 비활성화 하는 데에 가장 많이 이용된다. 즉, 기존에 있는 소스 코드를 건드리지 않는 상태에서 부분적인 컴파일을 하는 것이다.
C의 전처리문이 오는 줄(Line)의 첫 문자는 항상 ‘#’으로 시작한다. ANSI 표준에 따른 C의 전처리문의 종류가 아래에 나와 있다.

  • 파일 처리를 위한 전처리문 : #include
  • 형태 정의를 위한 전처리문 : #define, #undef
  • 조건 처리를 위한 전처리문 : #if, #ifdef, #ifndef, #else, #elif, #endif
  • 에러 처리를 위한 전처리문 : #error
  • 디버깅을 위한 전처리문 : #line
  • 컴파일 옵션 처리를 위한 전처리문 : #pragma

 

조건 처리를 위한 전처리문은 어떤 조건에 대한 검사를 하고 그 결과를 참(0 이 아닌 값) 또는 거짓(0)으로 돌려준다.

#if : …이 참이라면
#ifdef : …이 정의되어 있다면
#else : #if나 #ifdef에 대응된다.
#elif : “else + if”의 의미
#endif : #if, #ifdef, #infdef 이 끝났음을 알린다.

 

#include

헤더 파일과 같은 외부 파일을 읽어서 포함시키고자 할 때 사용된다. 이때의 파일은 이진파일(Binary file)이 아닌 C의 소스파일과 같은 형태의 일반 문서파일을 말한다.

1
2
#include <stdio.h>    /* 이 위치에 stdio.h라는 파일을 포함시킨다. */
#include "text.h"   /* 이 위치에 text.h라는 파일을 포함시킨다. */

‘<…>’를 사용할 때와 ‘”…”‘를 사용할 때의 차이점은 ‘<…>’는 컴파일러의 표준 포함 파일 디렉토리(또는 사용자가 별도로 지정해 준)에서 파일을 찾는 것을 기본으로 한다. 그리고 “…”‘를 사용했을 때는 현재의 디렉토리를 기본으로 파일을 찾게 된다. 아예 디렉토리를 같이 지정할 수도 있다.

1
2
#include <C:\MYDIR\MYHEAD.H>
#include "C:\MYDIR\MYHEAD.H"

 

#define

상수값을 지정하기 위한 예약어로 매크로라고 부른다. 구문의 상수로 치환한다.
또한 #define은 함수 역활과 비슷하게 아래와 같이 쓰일 수 있다.

1
#define SUM(x) ((x) = (x) + (x))

동작원리는 함수와 같다. 말 그대로 main소스에서 SUM을 호출하면 옆에 있는 더하기 코드가 치환되는 것이다.

 

#define으로 정의할 수 있는 것은 숫자만이 아니다.

1
2
#define MYNAME "Young Hee"
printf(MYNAME);

라고 한다면 이는 전처리기에 의해

1
printf("Young Hee");

와 같이 전개되는 것이다.

이렇게 #define으로 정의된 것은 일반적인 변수와는 다르다. 그 차이는 명백한데, 예를 들어서

1
2
#define MYNAME "Turbo"
char my_name[] = "Turbo"

의 두가지 경우가 있다고 하자. “MYNAME”은 전처리문으로 “my_name”은 문자형 배열 변수로 정의되었다.

1
2
3
4
printf(MYNAME);
printf(MYNAME);
printf(my_name);
printf(my_name);

이것을 전처리한 상태는 다음과 같이 될 것이다.

1
2
3
4
printf("Turbo");
printf("Turbo");
printf(my_name);
printf(my_name);

이런 결과에서 우리가 유추해 볼 수 있는 것은 전처리 명령을 사용했을 경우 “Turbo”라는 동일한 동작에 대해서 두개의 똑같은 문자열이 사용됐고, 변수를 사용했을 경우에는 하나의 문자열을 가지고 두번을 사용하고 있다는 것이다. 결과적으로 이런 경우에는 전처리문을 사용했을 경우 메모리 낭비를 가져 온다는 것을 알 수 있다.

 

#undef

#define으로 이미 정의된 매크로를 무효화한다.

1
2
#define ADD(a, b) (a + b)
#undef ADD(a, b)

라고 하면 앞으로 사용되는 ADD(…)는 ‘undefined symbol’이 되어 에러 처리 된다.

 

#if ~ #endif

#if 구문은 if랑 아주 비슷하다. 이것은 어떠한 구문을 컴파일 할지 안할지를 지정할 수 있다.

1
2
3
4
#define A 1
#if A
    source code.....
#endif

위 source code 부분은 컴파일이 된다. if문에서와 같이 참, 거짓을 구분하여 컴파일이 된다. 위에서 A값은 1 즉 0보다 큰 수이기 때문에 참인 것이다.
직접 아래와 같이 하면 거짓이기 때문에 source code 부분은 컴파일이 되지 않는다.

1
2
3
#if 0
    source code....
#endif

 

#ifdef ~ #endif

컴파일 할 때

1
2
3
4
5
6
#define MYDEF /* MYDEF는 값은 가지지 않았지만 어쨋든 정의는 되었다 */
#ifdef YOURDEF /* 만약 YOURDEF가 정의되어 있다면... */
    #define BASE 10 /* BASE == 10 */
#elif MYDEF /* 그외에 MYDEF가 정의되었다면... */
    #define BASE 2 /* BASE == 2 */
#endif

와 같이 쓰면 BASE는 상수 2로 치환되어 전처리기가 컴파일러에게 넘겨준다.

 

#ifndef 헤더명_H__ ~ #endif

헤더파일이 겹치는 것을 막기 위한 일종의 매크로이다. 예를 들어 헤더파일에 어떤 클래스의 인터페이스 선언을 넣었다고 하자. 이 클래스 인터페이스에서 다른 파일의 프로토타입이 필요해서 다른 A 파일을 include 하고 있는데 이 헤더 파일을 include 하는 파일에서 A라는 헤더 파일을 이미 include 하고 있다면 두번 define한 것이 된다. 그러면 SYNTAX 에러가 난다. 그래서 그런 것을 막는 방법의 하나로 #ifndef을 사용한다. 이전에 include되어 있으면 #endif쪽으로 점프해버려 결국 한번 선언되는 것이다.

1
2
#include <stdio.h> ------ (a)
#include <stdio.h> ------ (b)

위와 같이 동일한 구문을 두번 썼다고 하자. 그런데 앞에 이미 include를 했는데 밑에 또 한다면 문제가 된다. 컴파일러가 검사해야할 코드량도 많아진다. 그래서 stdio.h에는

1
2
#ifndef STDIO_H__
#define STDIO_H__

가 선언되어 있다. 만약 STDIO_H가 선언되어 있지 않다면 선언한다는 뜻이다. 그 뒤 (b)에서는 이미 (a)쪽에서 STDIO_H__ 을 선언한 상태이기 때문에 전처리기 쪽에서 무시해버린다. 그러므로 컴파일러는 (a)만 검사한다.

 

#defined

define이 여러 개 되어 있는지를 검사할 때 쓴다. 이것은 여러 개를 동시에 검사 할 수 있다.

1
#if #defined A || #defined B

 

#ifdef와 #if defined의 차이

#ifdef는 정의가 되어 있는지를 테스트 하기 때문에, 한번에 여러 개를 사용할 수 없다. 형식이

1
#ifdef name

처럼, 하나의 토큰이 되어야 하기 때문이다.

여러 개가 정의되어 있는지를 테스트 하기 위해서는

1
#if defined(MACRO1) || defined(MACRO2)

처럼 사용한다.
#if는 ||로 중첩해서 사용할 수 있다. 형식이, #if expression이므로, C 표현이 올 수 있다.

1
#if MACRO1 || MACRO2

처럼 사용해도 된다.

 

#error

소스 라인에 직접 에러 메세지를 출력한다. 전처리기가 #error 문을 만나면 그 즉시 컴파일을 중단하고 다음과 같은 에러 메시지를 출력한다.
ERROR : XXXXX.c ########: Error directive: 내용
– XXXXX.c –> 현재 컴파일 중인 파일명
– ####### –> 전처리기가 #error 문을 만난 시점에서의 행 번호(헤더 포함)

1
2
3
#ifdef __LARGE__
    #error This program must be compiled in LARGE memory model!
#endif

이 내용은 만일 프로그램이 LARGE 모델이 아니라면 “#error” 뒤에 표시된 메세지를 출력하고 컴파일을 중지하게 된다.

 

#line

이 명령은 소스 코드의 행번호를 지정하기 위한 것으로 주로 컴파일러에 의해 미리 정의된 __LINE__과 함께 사용된다. C에서는 베이식과 같은 행번호를 사용하지 않는다. 하지만 디버깅을 하다 보면 “행번호를 알 수 있으면 좋을텐데”하는 경우가 있다. 예를 들자면 어떤 표현들이 있고, 그 표현들 중의 어딘가에 잘못된 부분이 있는 것 같은데 정확히 그 표현이 있는 행을 찾지 못한다면 “#line”을 사용함으로써 그 일을 보다 수월하게 할 수 있게 된다.
__LINE__과 __FILE__을 각각 행번호와 파일명으로 변경한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#define DEBUG
void main(void)
{
    int count = 100;
    #line 100 /* 다음 줄번호를 100으로 설정한다 */
    /* <-- 이 줄의 번호가 100이다 */
    #ifdef DEBUG /* <-- 이 줄의 번호가 101이다 */
        printf("line:%d, count = %d\n", __LINE__, count);
    #endif
    count = count * count - 56;
    #ifdef DEBUG
        printf("line:%d, count = %d\n", __LINE__, count);
    #endif
    count = count / 2 + 48;
    #ifdef DEBUG
        printf("line:%d, count = %d\n", __LINE__, count);
    #endif
}

 

C의 predefined macro

__FILE__a string that holds the path/name of the compiled file
__LINE__an integer that holds the number of the current line number
__DATE__a string that holds the current system date
__TIME__a string that holds the current system time
__STDC__defined as the value ‘1’ if the compiler conforms with the ANSI C standard
__cplusplusdetermines if your compiler is in C or C++ mode. Usually used in headers

 

예)

1
#include <stdio.h>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
void main(void
    printf("The path/name of this file is %s\n", __FILE__); 
    printf("The current line is %d\n", __LINE__); 
    printf("The current system date is %s\n", __DATE__); 
    printf("The current system time is %s\n", __TIME__); 
    #ifdef __STDC__ 
        printf("The compiler conforms with the ANSI C standard\n"); 
    #else 
        printf("The compiler doesn't conform with the ANSI C standard\n"); 
    #endif 
    #ifdef __cplusplus 
        printf("The compiler is working with C++\n"); 
    #else 
        printf("The compiler is working with C\n"); 
    #endif 
}

 

프로그래머들 마다 코딩 스타일(암시적 약속)이 있다. 보통 매크로, const 변수는 대문자로 적는 것이 원칙이다. 매크로 함수와 일반함수, 매크로 대상체(object-like macro)와 일반 변수를 구분하기 쉽게 해주는 것이기 때문이다.

#define STDIO_H_
왜 뒤에 _를 붙였을까? 이것도 하나의 암시적 약속이다. 컴파일러 제작회사는 매크로를 정의할 때 사용자들과 이름이 충돌이 나지 않게 하기 위해서 대부분 _를 뒤어 덧붙인다. 또한 _를 하나 혹은 두 개 연속으로 시작하는 것은 컴파일러 내부에서 사용하는 매크로라는 성격이 강하다. 물론 강제적인 뜻은 없으며 단지 관습상 그렇다. 왜 이것이 관습이 되었나 하면 보통 매크로 변수 이름이나 함수 이름을 지을 때 뒤에 _를 붙이지 않기 때문이다. 그래서 함수 제작자들이 _를 단골로 붙였다.



출처 : http://sarghis.com/blog/802/

728x90
728x90

MainFrame Class, View Class 참조

CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();
CCeSerialView* pView = (CCeSerialView*)pFrame->GetActiveView();
위와같이 참조하기 위해선 메인프레임, 도큐먼트, 뷰 헤더파일 인클루드

#include "CeSerial.h"
#include "MainFrm.h"
#include "CeSerialDoc.h"
#include "CeSerialView.h"

App 헤더파일을 인클루드 하지않으면 아래와 같은 에러 발생 

error C2065: 'IDD_CESERIAL_FORM' : undeclared identifier
error C2057: expected constant expression

CTestSendDlg *p_Dlg = (CTestSendDlg*)AfxGetMainWnd(); 임의클래스에서 다이얼로그 참조

------------------------------------------------------------------------------------------------

MFC로 프로그래밍을 하다 보면, 각 클래스에 어떻게 접근해서 포인터를 얻어와야 하는지 매우 어려울 때가 많다... 
특히 초중급자 시절엔 말이다.. 사용한 지 오래 되어도 여전히 헷갈리기는 매 한가지다.자 그럼.. 하나씩.. 이해해 보자.

우선 MFC로 프로그램을 만들면, 다음과 같이 클래스가 생성된다. 프로젝트 명을 Test라고 가정해 보자.

CTestApp - CWinApp 클래스를 상속, 프로그램 초기화 클래스 (InitInstance)

CMainFrame - CFrameWnd 클래스를 상속, 전체 윈도우 관련 클래스(툴바, 메뉴, 상태바 등등)

CTestDoc - CDocument 클래스를 상속, 문서 관련 클래스(Open, Save, Serialize)

CTestView - CView 클래스를 상속, 사용자 화면 클래스(OnPaint, OnDraw)

CAboutDlg - CDialog 클래스를 상속, 도움말 대화 상자 클래스(DoModal)

------------------------------------------------------------------------------------------------


우선 가장 쉬운 것부터 설명하자면, 
어느 곳에서나 CTestApp의 포인터를 얻고자 한다면, 다음과 같이 코딩한다.
AfxGetApp()는 전역 함수이므로 어느 곳에서나 호출할 수 있다.

CTestApp* pApp = AfxGetApp();

하지만 위와 같이 사용하면, 다음과 같은 에러가 발생한다.

error C2440: 'initializing' : cannot convert from 'class CWinApp *' to 'class CTestApp *'

에러를 보면, CWinApp*를 CTestApp*로 변환할 수 없다는 것인데, AfxGetApp()의 함수 원형이

CWinApp* AfxGetApp();

이기 때문이다. 그래서 이 문제를 해결하기 위해서는 아래와 같이 형변환(casting)을 해 주어야 한다.

CTestApp* pApp = (CTestApp*)AfxGetApp();

이제는 컴파일 에러 없이 잘 될 것이다. 그리고 다음과 같은 에러가 발생될 수도 있느데,

error C2065: 'CTestApp' : undeclared identifier

이는 CTestApp 클래스의 선언 부분이 포함(include)되지 않았기 때문이다. 이럴 때는

CTestApp 클래스의 선언을 다음처럼 포함시켜야 한다.

#include "Test.h"

pApp는 이미 생성되어 있는 CTestApp의 객체 포인터이기 때문에,
이젠 pApp 포인터를 통해 CTestApp 객체에 쉽게 접근할 수 있다. 만약에 CTestApp에 

int i;

라는 멤버 변수가 있었다면, 

pApp->i = 5;

와 같이 사용할 수 있다. 또한 멤버 함수 func()가 있다면,

pApp->func();

처럼 접근할 수 있다. 주의해야 할 것은 객체 접근이기 때문에 public 속성의 멤버들만 접근할 수 있고, 
private와 protected는 접근할 수 없음에 유의해야 한다.

------------------------------------------------------------------------------------------------

두 번째는 CMainFrame의 포인터을 얻어 오는 방법이다. 다음과 같이 사용하려 할 때,

CMainFrame* pFrame = AfxGetMainWnd();

두 가지 에러와 만나게 된다.

첫 번째, CMainFrame에 대해서 컴파일러가 모르겠다는 에러이다.

error C2065: 'CMainFrame' : undeclared identifier

이 에러를 수정하기 위해서는 CMainFrame 클래스가 선언되어 있는 헤더 파일을

다음처럼 포함시키면 된다. 그리고 포함시키는 위치도 Test.h와 TestDoc.h 사이가 좋다.

그것은 클래스들간에 서로 관계가 있기 때문에 포함 순서는 매우 중요하다.


#include "stdafx.h"
#include "Test.h"


#include "MainFrm.h"


#include "TestDoc.h"
#include "TestView.h"

이 에러를 해결하고 나면, 다음과 같이 CTestApp에서 발생했던 에러가 발생한다.


error C2440: 'initializing' : cannot convert from 'class CWnd *' to 'class CMainFrame *'

에러를 보면, CWnd*를 CMainFrame*로 변환할 수 없다는 것인데, AfxGetMainWnd()의 함수 원형이


CWnd* AfxGetMainWnd();


이기 때문이다. 그래서 이 문제를 해결하기 위해서는 아래와 같이 형변환(casting)을 해 주어야 한다.


CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();


이제는 컴파일 에러 없이 잘 될 것이다. CMainFrame은 CFrameWnd를 상속 받고, CFrameWnd는

CWnd를 상속 받기 때문에, pFrame 포인터를 통해서 CFrameWnd와 CWnd가 갖고 있는 모든 함수를

호출하여 사용할 수 있다. 만약 윈도우의 타이틀에 나오는 제목을 바꾸고 싶다면,


pFrame->SetWindowText( "야.. 바뀌어라" ); 
처럼 사용하면 된다. 

------------------------------------------------------------------------------------------------

CMainFrame*는 또 다른 이유로 자주 사용하게 되는데, 그 이유는 도큐먼트(CTestDoc)와

뷰(CTestView) 클래스에 접근하기 위해서이다. CMainFrame 클래스는 뷰에 접근하는 GetActiveView(); 함수와

문서에 접근하는 GetActiveDocument() 함수를 제공한다.


우리가 어느 특정 클래스를 만들어서 사용하고, 이 클래스가 뷰에 접근해야 한다면 다음과 같은 문장을 사용해야 한다.


CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();

CTestView* pView = (CTestView*)pFrame->GetActiveView();


만약 도큐먼트 클래스에 접근해야 한다면,


CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();

CTestDoc* pDoc = (CTestDoc*)pFrame->GetActiveDocument();


물론 이 때는 각각의 클래스에 대한 헤더를 반드시 포함시켜야 함을 잊지 말아야 된다.

가끔 아래와 같은 순서로 헤더를 포함시키지 않는 경우가 있는데, 반드시 아래와 같이 포함해야 한다.


#include "MainFrm.h"

#include "TestDoc.h"

#include "TestView.h"


만약 대화 상자에서 도큐먼트 클래스에 접근하려면 어떻게 할 것인가? 방법은 위와 동일하다.

------------------------------------------------------------------------------------------------

다음은 뷰에서 도큐먼트에 접근하는 것과 도큐먼트에서 뷰에 접근하는 것에 대해 알아보자.

뷰와 도큐먼트는 서로를 직접 접근할 수 있는 함수가 제공된다. 뷰에서 도큐먼트를 직접 접근하려면, 

CTestDoc* pDocument = (CTestDoc*)GetDocument();

를 호출하면 된다. 물론 View 클래스에는 GetDocument() 함수가 존재해야 한다. 일반적으로 이 함수는

MFC로 프로젝트 생성 시 기본적으로 있다. 하지만 만약 직접 만든 새로운 클래스라면 GetDocument() 함수를

직접 만들어줘야 한다. 직접 만들기가 귀찮다면, 다음과 같이 사용해도 된다.

CTestDoc* pDocument = (CTestDoc*)m_pDocument;

m_pDocument는 CTestView 클래스의 멤버이며, 다음과 같이 선언되어 있다.

CDocument* m_pDocument;


그리고, 도큐먼트에 뷰 클래스를 참조하려면 아래와 같이 해야 한다.


POSITION pos = GetFirstViewPosition();
CTestView* pView = (CTestView*)GetNextView( pos );

도큐먼트는 뷰 클래스를 내부적으로 링크드리스트를 사용해서 관리하고 있다. 그러므로 GetFirstViewPosition()

함수를 통해 POSITION을 얻어 온 다음, GetNextView() 함수를 통해 뷰의 포인터를 얻어오면 된다.

만약 창 분할에 의해 뷰가 여러 개 존재한다면, 다음과 같이 얻어 올 수 있다. 만약 컴파일 에러가 발생하면,

#include "TestView.h" 처럼 해서 헤더 파일을 포함하는 것도 잊지 말자.


POSITION pos = GetFirstViewPosition();
CTestView1* pView1 = (CTestView1*)GetNextView( pos );

CTestView2* pView2 = (CTestView2*)GetNextView( pos );
CTestView3* pView3 = (CTestView3*)GetNextView( pos );
CTestView4* pView4 = (CTestView4*)GetNextView( pos );
...

------------------------------------------------------------------------------------------------

그렇다면,,, CTestApp 클래스에서 뷰(CTestView) 클래스는 어떻게 얻어 올 수 있을까? 쉽게 응용할 수 있지만

머리가 꽉 막힐 수도 있다. 이 곳에서도 혹시 에러가 나면 헤더 파일을 자~알 추가해야 할 것이다.


CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();
CTestView* pView = (CTestView*)pFrame->GetActiveView();

------------------------------------------------------------------------------------------------

마지막으로, 뷰(CTestView) 클래스에서 CMainFrame에 접근할 수 있는데 AfxGetMainWnd() 함수외에도

CMainFrame* pFrame = (CMainFrame*)GetParentFrame();

------------------------------------------------------------------------------------------------

MDI 환경에서 포인터를 가져와 보자.

CTestApp* pApp = (CTestApp*)AfxGetApp();

POSITION pos = pApp->GetFirstDocTemplatePosition();
    
CDocTemplate* pDocTemplate;
pDocTemplate = pApp->GetNextDocTemplate( pos );
  
// 첫 번째 등록한 템플릿
pDocTemplate->OpenDocumentFile( NULL );               // 첫 번째 문서 생성


POSITION posDoc = pDocTemplate->GetFirstDocPosition();
CTestDoc* pDoc = (CTestDoc*)pDocTemplate->GetNextDoc( posDoc );
  // 첫 번째 문서 포인터
ASSERT( pDoc->IsKindof( RUNTIME_CLASS(CMongDoc) ) );                   // 포인터 유효성 검사
//CXXXDoc* pDoc2 = (CXXXDoc*)pDocTemplate->GetNextDoc( posDoc );             // 두 번째 문서 포인터

POSITION posView = pDoc->GetFirstViewPosition();
CTestView* pView = (CTestView*)pDoc->GetNextView( posView );
       // 첫 번째 뷰 포인터
ASSERT( pView->IsKindof( RUNTIME_CLASS(CTestView) ) );                // 포인터 유효성 검사

//CXXXView* pView2 = (CXXXView*)pDoc->GetNextView( posView );                 // 두 번째 뷰 포인터
//ASSERT( pView2->IsKindof( RUNTIME_CLASS(CXXXView) ) );                         // 포인터 유효성 검사

// 다음 템플릿이 존재할 경우만 가능합니다.

pDocTemplate = pApp->GetNextDocTemplate( pos );  // 두 번째 등록한 템플릿
if( pDocTemplate )                                                         
// 두 번째 템플릿이 존재하는지 검사

{

    pDocTemplate->OpenDocumentFile( NULL );          // 두 번째 문서 생성

}

------------------------------------------------------------------------------------------------

분할 뷰에서는 위의 방법과 같이 포인터를 얻어 올 수 있다. 또는 다음과 같은 방법으로 얻어 올 수 있다.

만약 창이 두 개로 분할되어 있다면,


CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();

CTestView* pView1 = (CTestView*)pFrame->m_wndSplitter.GetPane(0,0);   // 첫 번째 뷰 포인터 얻기

CXXXView* pView2 = (CTestView*)pFrame->m_wndSplitter.GetPane(0,1);   // 두 번째 뷰 포인터 얻기


뷰 포인터는 다음과 같이 좌표를 사용하여 구할 수 있다.


------------------------------------------------------------------
|                                |                               |
|         GetPane( 0, 0 )        |     GetPane( 0, 1 )           |
|                                |                               |
------------------------------------------------------------------
|                                |                               |
|         GetPane( 1, 0 )        |     GetPane( 1, 1 )           |
|                                |                               |
---------------------------------------------------------------------------------------------
|                                |                               |                          |
|         GetPane( 2, 0 )        |     GetPane( 2, 1 )           |     GetPane( 2, 2 )      |
|                                |                               |                          |
---------------------------------------------------------------------------------------------

728x90
728x90

내 컴퓨터 - C 드라이브 -  Windows - Appcheck 에 들어가보면


drvmain.sdb

frxmain.sdb

msimain.sdb

pcamain.sdb

sysmain.sdb


와 같은 파일이 있다.(윈8 기준)


.sdb의 뜻은 MSDN에서 검색해보면


조직에 사용자 지정 호환성 수정 데이터베이스를 배포하려면 다음 작업을 수행해야 합니다.

  1. 조직의 모든 컴퓨터가 액세스할 수 있는 위치에 사용자 지정 호환성 수정 데이터베이스(.sdb 파일)를 저장합니다.

  2. Sdbinst.exe 명령줄 도구를 사용하여 사용자 지정 호환성 수정 데이터베이스를 로컬에 설치합니다.

위의 두 요구 사항을 충족하려면 다음 두 가지 방법 중 하나를 사용하는 것이 좋습니다.

  • Windows Installer 패키지 및 사용자 지정 스크립트 사용 
    .sdb 파일 및 사용자 지정 배포 스크립트를 .msi 파일로 패키징한 다음 .msi 파일을 조직에 배포할 수 있습니다.



또한 여기에는 Shim 구조를 알 수 있는데,


즉 간단히 말해서 윈도우에서 호환을 되기 위해 수정하는 데이터베이스라고 보면 되는데,


이 파일은 윈도우에서 기본적으로 제공하는 application으로 읽을 수 없다.


필자는 FileViewPro를 설치하여 해보려 확인해보려 했으나, 유료라는 사실에 감탄하고 다른 방법을 사용할 것이다.



다음 url에서 링크되어있는 sdb2xml 이라는 파일을 사용해서 확인할 것이다.


sdb2xml 파일은 이곳에서 다운 받을 수 있다. ( https://blogs.msdn.microsoft.com/heaths/2007/11/03/shim-database-to-xml/ )


아래쪽 부분의 sdb2xml.exe  를 다운받으면 된다.


이 exe파일을 다운 받고, 바로 실행하면 꺼지기 마련이다.




우선 [내 컴퓨터] - [C 드라이브] -  [Windows] - [Appcheck] 에 들어가서


확인할 파일을 복사하여 바탕화면에다가 둔다.


(저는 sysmain.sdb를 복사하여 두었습니다.)




그다음 [시작 ] - cmd(명령 프롬포트 창)에 들어간다.


파일이 바탕화면(데스크탑)에 있으므로 


cd Desktop 를 입력하고


엔터 후


sdb 2xml (파일명).sdb -out (저장할파일명) 으로 치면 된다.


ex) sdb2ml sysmain.sdb -out kaen2891


그러면 바탕화면에 kaen2891.sdb가 생성 될 것이다.




이것을 메모장을 열고 드레그 하면 현재 호환되는 목록들을 확인할 수 있다. 


왼쪽 메모장이 제일 윗부분, 오른쪽 메모장은 chrome을 확인했을 때 호환 된 부분이다.


내용에서 볼 수 있듯이 호환되는 목록들을 확인할 수 있다.(고전 게임과 같은 것들도 종종 보인다.)






728x90

'Program > Window' 카테고리의 다른 글

[Window] 컴 스레드 모델 STA, MTA  (0) 2016.09.26

+ Recent posts