COM Apartment

COM의 Threading model에 대한 자료, 아래 사이트와 문서 자료로 정리한 것입니다. (DOIT, COM+, Essential COM의 Apartment내용을 보고 추가하고 Proxy / Stub을 추가 정리하자) COM을 사용할때 서버측에서는 일괄된 사용을 보장해야 한다. 위치 투명성을 보장하여 어디서든 호출 가능해야 하기 때문에 Proxy / Stub이 필요함과 동시에 재작된 클라이언트 어플리케이션에서 단일 쓰레드로 사용하거 멀티 쓰레드에서 사용하는 상황에서도 멀티쓰레드가 고려되지 않은 COM 오브젝트는 안전하게 구동되어야 한다.이에 Microsoft는 COM Thread model로 Apartment라는 것을 정의 한다.
  • Apartment는 COM 오브젝트를 Threading model에 따라 구분/배치하기 위한 논리적 공간
  • Apartment는 쓰레딩 모델을 공유하는 개체들이 존재하는 곳, 모든 객체는 Apartment안에서 들어야 한다.
  • Apartment는 하나의 프로세스에 하나식만 존재 합니다. 하나의 프로세스에 여러 Apartment가 있을 순 있지만 Apartment가 여러 프로세스에 걸쳐 존재할 수 없다.
  • Apartment에는 여러개의 Thread가 있을 수 있지만 하나의 Thread는 여러개의 Apartment에 있을 수 없습니다.
  • 단일 프로세스내에 오브젝트들이 Apartment라고 불리는 몇몇의 그룹으로 나누어져 있고 COM 오브젝트는 정확히 하나의 Apartment내에 있고 오브젝트의 메소드들은 그 Aparment에 속한 쓰레드들에 의해서만 호출 될 수 있다. 다른 쓰레드들이 이 오브젝트를 호출하기 위해서는 마샬링이 필요하다.
  • CoInitialize?(Ex)를 통해서 Apartment에 들어간다.

STA, Single Thread Apartment

  • 프로세스는 여러개의 STA를 가진다 STA는 쓰레드를 하나만 가진다.
  • STA는 하나의 쓰레드만이 들어 갈수 있으면 들어 올 수 있는 쓰레드는 STA를 만든 쓰레드이다.
  • 윈도우의 경우 호출은 우니도우즈 메시지 큐를 사용한다.

MTA, Mutil Thread Apartment

  • 하나의 프로세스는 하나의 MTA만 가질 수 있다.
  • MTA는 여러 COM오브젝트를 가질 수 있다.
  • STA와는 달리 동기화에 신경 써주어야 하며 Thread-context가 줄어든다.
  • Reference counting되어서 쓰레드 집입을 카운트하여 쓰레드가 나갈때 카운트는 줄어들며 0이되면 사라진다.
  • COM오브젝트가 쓰레드가 요구한 Apartment와 호환이 되면 그 Apartment에 객체가 생성되여 raw pointer를 얻게되지만 호환이 되지 않는다면 다른 Apartment에 생성이 되고 proxy객체에 대한 포인터를 얻게 된다.

COM thread model: Main Thread

  • COM객체는 프로세스 상에서 가장 먼저 만들어진 STA와 호환된다는 것을 의미한다.
  • Main Thread다(entry pointer가 있는). 호환이 되지 않는 쓰레드에서 객체 생성이 요구되면 객체는 Main thread에서 만들어진 Apartment에서 생성이 되면 쓰레드는 Proxy 객체에 대한 포인터를 받는다.
  • 모델은 하위 호환성을 위해서만 존재하고 만약 동기화를 지원하지 않는 객체를 작성할때는 Apartment thread 모델을 사용한다.

COM thread model: Apartment Thread

  • COM객체는 STA와 호환되다는 것은 의미한다.
  • MTA에 들어간 쓰레드가 이 객체를 생성하면 객체는 새로운 쓰레드를 하나 만들고 그 쓰레드에 STA를 만들어 Proxy 객체 포인터를 리턴한다.
  • 이 모델은 Thread Affinity를 지니는 기능(CRITICAL_SECTION, TLS등)을 사용하거나 UI관련된 기능을 지니는 객체의 경우 추천된다. STA모델이고 필요할 경우 STA를 생성함으로 별도의 동기화 방법은 필요없다.
  • 비베(6.0?)의 경우 Main thread와 Apartment thread를 지원하는데 가능한 이 모델을 구현해야 한다.

COM thread model: Free Thread

  • COM객체는 MTA와만 호환이 된다는 것을 의미한다.
  • STA에 들어간 쓰레드가 Free thread를 지원하는 COM객체를 만들면 객체는 MTA에서 만들어진다.
  • MTA가 존재하면 그 MTA에 만들어지고 아니라면 새로운 쓰레드가 생성되고 MTA를 생성한다. 그리고 Proxy 객체 포인터를 리턴한다.
  • 동기화가 지원되어야 한다.

COM thread model: Both

  • COM객체는 쓰레드가 어떤 Apartment에 속해 있건 모두 호환된다는 것을 의미한다.
  • 쓰레드가 속한 Apartment에 생성되며 무조건 raw pointer를 리턴한다.
  • 생성된 COM객체는 여러 쓰레드가 사용 할 수 있음으로 동기화가 지원되어야 한다.

COM thread model: Neutral (NA)

  • 윈도우즈 2000에서 추가된 모델
  • COM객체는 TNA(Thread Neutral Apartment)와 호환되며 객체는 무조건 TNA에 만들어진다.
  • NA는 Proxy객체 포인터만 리턴한다. 쓰레드 무조건 COM객체의 Proxy 객체에 대한 포인터를 얻게 된다.
  • 프로세스당 하나의 Neutral apartment를 가진다.
  • 동기화가 지원되어야 한다.
  • COM+ 모델이 나온후 지원하기 위해 생긴 Apartment이다.
  • NA는 컴포넌트가 순차적인 접근을 허용할 뿐만 아니라, 어느 스레드에서 든지 수행할 수 있다.
  • Role-base security, 비동기적 객체 실행, out-of-process 서버에서 실행 중인 객체 인스턴스를 참조하는 새로운 내장 모니커(moniker)가 추가되었다.

TNA에 대해 설명하기 전에 우선 STA와 MTA의 반응성과 대해 알아보자. STA의 경우 한 아파트먼트 안에 있는 모든 개체는 하나의 스레드를 통해서만 실행될 수 있다. 이러한 점은 STA 안에 여러 개의 객체가 존재하게 될 때 큰 문제를 지닌다. 이 STA에 속하지 않은 스레드로부터의 메서드 호출은 이 STA를 만든 스레드를 이용해 처리된다고 위에서 배웠다. 그렇다면 만약 STA에 속한 하나의 객체의 메서드를 호출하고 있다면 다른 객체에 대한 메서드 호출은 이 객체의 사용이 끝날 때 까지 기다려야 한다는 것을 쉽게 깨달을 수 있을 것이다. 이것은 STA가 보이지 않는 윈도우를 이용해 호출을 동기화 하기 때문이며 메시지 큐에 쌓인 메시지는 차례차례 처리된다. 이러한 점은 메서드가 블록킹 호출을 할 때 더욱 악화된다. 예를 들어 WaitForSingleMessage등의 메서드 호출을 한다면 사실 스레드는 아무 일도 안하는 상태임에도 불구하고 다른 객체에 대한 메서드 호출은 상당기간 동안 유보하게 된다. 이러한 점은 STA가 흔히 UI스레드가 만들게 되는 아파트먼트 하는 점에서 프로그램 자체의 반응성을 떨어뜨릴 수 도 있다. 물론 이를 해결하기 위해 CoWaitForMultipleHandles 와 같은 API가 있다. 하지만 이러한 API를 사용할 때에는 반드시 Reenterancy에 대한 내용을 숙지하여야만 한다.

반면에 MTA의 경우 이러한 반응성 저하는 없다고 볼 수 있다. 모든 스레드는 원하는 객체에 대한 메서드를 행하기 위해 Cache Thread를 통해 바로 메서드 호출을 할 수 있다. 같은 아파트먼트 안에 속해 있는 다른 객체뿐 아니라 심지어 자기자신이 사용 중이라 해도 이것은 마찬가지다. 또한 MTA에 속하지 않은 스레드에서의 호출 역시 기존의 스레드가 아닌 ORPC CACHE 스레드가 새로 생겨 처리 되므로 매서드 호출이 블록킹 되는 경우는 생기지 않는다. 이러한 점은 프로그래머에게 동기화 지원을 직접적으로 해줄 것을 요구하여 복잡함을 가중시킨다. 하지만 오히려 이러한 점은 숙련된 프로그래머로 하여금 아파트먼트 단위 또는 객체 단위가 아닌 자원단위의 또는 최소한의 잠금을 가지게 되는 단위의 동기화를 할 수 있게 함으로서 훨씬 더 낳은 반응성을 가지게 한다. 이러한 점이 STA 안에 단 하나의 객체 밖에 없다하더라도 MTA 안의 객체가 더욱 우수한 반응성을 지니게 하는 원인이 된다.

 

아파트먼트 종류와 스레딩 모델에 따른 성능 차이

 

한가지 중요한 점은 여러 개의 스레드가 돌아간다고 해서 해서 특정 작업이 빨리 수행 되는 것은 아니라는 점이다. STA에서 블록킹 메서드 호출을 하는 경우가 아닌 한 사실 STA와 MTA의 성능은 MTA에 여러 개의 스레드가 동시접근 할 수 있다는 점만으로 MTA가 우수하다고 할 수는 없다. 만약 다음의 요인을 제외 한다면 사실 STA와 MTA의 성능 차이는 없다고 볼 수 있다.

하지만 실질적으로 COM의 함수 호출이 느려지게 만드는 요인은 따로 있다. 바로 Cross-Apartment Call의 비용이다. STA에서 다른 STA로 또는 STA에서 MTA로 또는 MTA에서 STA로의 함수 호출은 모두 Cross-Apartment Call이라고 볼 수 있다. 물론 다른 프로세스나 네트웍상의 다른 컴퓨터에 대한 호출 역시 Cross-Apartment Call이지만 이 경우 사실 다른 요인이 더욱 성능을 감소시키게 되므로 일단 논의에서 제외하자. 그렇다면 무엇이 문제인가?

이 글의 맨 처음에 Thread Context Switching은 매우 비싼 명령이라고 설명했다. 문제는 Cross-Apartment Call이 이 Thread Context Switching을 일으키게 된다는 점이다. 예를 들어 다른 아파트먼트에 들어간 스레드A로부터 어떤 STA에 있는 객체로 메서드 호출을 생각해 보자. 이 스레드A는 STA에 들어간 스레드B에게 메시지를 보낸 후 대기 상태가 된다. 이후 그러면 스레드B가 그때부터 활성화된다.(일반적으로 GetMessage에서 깨어난다.) 여기서 한번의 Thread Context Switching이 일어난다. 그리고 스레드B는 객체에 대해 실제로 메서드 호출한 후 이 결과 값을 다시 스레드A에게 넘겨준다. 여기서 또 한 번의 Thread Context Switching이 일어난다. 한 메서드 호출당 두번의 Thread Context 호출은 절대 쉽게 넘어갈 수 있는 비용이 아니다. 만약 이 메서드가 매우 자주 불린다면 이것은 엄청난 성능 저하로 이어 질 것이다. MTA의 경우 MTA에 호환되도록 만들어진 객체를 호출할 경우 같은 MTA에 속하는 객체이기만 하면 같은 아파트먼트에 속하므로 Thread Context Switching이 필요 없다는 점에서 STA보다 훨씬 낳은 상황을 만든다. 하지만 MTA에 들어가있는 스레드가 STA에 있는 객체에 접근 할려면 Cross-Apartment Call이 이루어져야만 하며 여전히 Context Switching이 필요하다.

 따라서 특정 객체의 스레딩 모델을 정하거나 스레드가 어떤 아파트먼트안에 들어갈 것인가를 결정할 때에는 매우 주의를 기울여야 한다. 만약 특정 스레드가 Single Threaded 모델을 지니고 있는 객체를 생성해서 주로 사용한다면 그 스레드는 반드시 STA 안으로 들어가야 한다. 단순히 MTA가 더 낳은 성능을 제공하겠지 하고 기대하는 것은 엄청난 실수다. 성능을 향상하는 것은 최대한 Thread Context Switching을 줄이는 것이며 이것은 곧 Cross-Apartment Call의 횟수를 줄이는 것과 직결된다. 물론 MTA와 호환되는 객체를 사용한다면 MTA로 들어가는 것이 성능향상에 도움이 된다.

만약 이정도로만 끝난다면 얼마나 좋을까? 하지만 아직도 몇 가지 고려사항이 남았다. 바로 사용할 특정 객체가 다른 객체들을 사용할 경우다. 이점은 바로 Both 스레딩 모델과 Free Threaded 모델이 따로 존재하는 이유이기도 하다. 지금까지의 논의만을 생각한다면 당연히 동기화 기능을 제공하는 모든 객체는 Both 스레딩 모델일 경우 더욱 우수한 성능을 보일 것으로 기대된다. 하지만 그렇게 간단하지는 않다. 예를 들어 객체(객체 A라고 하자)를 사용하는 스레드(스레드A라고 하자)가 STA에 들어가야만 하고 만약 객체A에 대한 메서드 호출을 할 때 객체A가 다른 객체(객체 B라고 하자)로의 메서드 호출을 한다면? 특히 객체B가 MTA에 존재 한다면? 이것은 매우 심각한 고려 사항이 된다. 객체A가 스레드A와 같은 아파트먼트 안에 존재해야 할 것인가? 아니면 객체B와 같은 아파트먼트 안에 존재해야 할 것인가? 이것은 얼마나 자주 객체A가 객체B의 메서드를 호출하는가에 달려 있다. 만약 객체A가 스레드A와 같은 스레드에 존재한다면 스레드A에서 객체A로의 메서드 호출은 직접적인 메서드 호출이다. 다만 문제는 이 메서드 내에서 객체B를 호출할 경우다. 이 경우 이 메서드를 실행중인 스레드A는 객체B와 다른 아파트먼트 안에 있으므로 Cross-Apartment Call을 하게 된다. 만약 이 메서드 내에서 객체B에 대한 호출이 여러 번 있다면 그 횟수만큼 Cross-Apartment Call을 하게 된다. 하지만 만약 객체A가 객체B와 같은 아파트먼트 안에 존재한다고 하자. 스레드A는 객체 A에 대한 매서드 호출을 할 때 Cross-Apartment Call을 해야만 한다. 하지만 그 이후 객체 A에서 이루어지는 객체 B에 대한 매서드 호출은 직접적인 메서드 호출이다. 이런 점을 종합해보면 만약 객체 A에서 객체 B로의 메서드 호출이 잦다면  객체A는 Both 스레딩 모델을 사용해 스레드A와 같은 아파트먼트 안에 들어가는 것보다는 Free 스레딩 모델을 사용해 무조건 MTA에 들어가는 것이 더 효율적이다.

 

Standard Marshaling과 Custom Marshaling

위에서 채널을 통해 데이터를 직렬화해서 보낸다고 했다. 이렇게 데이터를 직렬화 하는 것을 COM의 용어로는 마샬링이라고 한다. 사실 지금까지의 논의는 객체의 인터페이스를 Standard 마샬링 하는 경우를 가정한 것이다. 그렇다면 마샬링이란 무엇인가? 마샬링이라는 것은 특정 아파트먼트 안에서의 데이터나 인터페이스를 다른 아파트먼트에 전달될 수 있는 스트림 형태로 바꾸는 과정을 의미한다. 우선 데이터의 마샬링에 관한 내용은 이곳의 내용과 관련이 적으므로 다음으로 미루도록 하고 인터페이스의 마샬링에 대한 내용을 살펴보자. 지금까지 다른 아파트먼트에 속한 객체를 호출 할 때 프록시 객체가 만들어진다고 했다. 그렇다면 과연 프록시 객체는 누가 어떻게 만드는 것인가? 이것 즉 프록시를 구현하는 일이 바로 인터페이스 마샬링이 하는 일이다. (좀 더 기술적으로 말하면 두 가지는 동일하지는 않다. 프록시를 구현한다기 보다는 프록시를 만들 수 있는 스트림을 만들어내는 것이 마샬링이며 이것을 특정 아파트먼트에 넘기고 언마샬링하고 COM Library 또는 타입라이브러리 기반의 프록시/스텁 코다가 이 스트림을 해석해 원래의 인터페이스에 대한 호출을 한다. 마샬링에 관한 자세한 논의는 다음에 하도록 하자.) 하지만 앞에서 보았다면 프록시가 하는 일이 그렇게 간단하지만은 않다는 것을 보았을 것이다. 이것을 매번 프로그래머가 해줘야 한다면? 더욱이 지금까지 알아본 프로세스 내에서의 Cross-Apartment Call은 네트워크 상의 다른 컴퓨터의 객체를 부르는 것보다 훨씬 더 간단하다는 것을 상기한다면 네트워크 까지 생각해야하는 마샬링 코드를 매번 프로그래머가 작성해야 한다면 아마 전세계에 COM을 하는 프로그래머는 손가락을 꼽을 정도가 될 것이다. 하지만 다행히도 그렇지는 않다. 비록 C++의 데이터형이 조금 모호함을 가지고 있어 (이 부분에 대한 얘기도 다음에 하자) IDL이라는 언어로 interface를 만들어줘야 하긴 하지만 어쨌든 인터페이스에 대한 정의만 제대로 내려주면 인터페이스의 마샬링 자동으로 이루어지게 된다. 해줘야 할 일은 오직 CoMarshalInterface와 CoUnmarshalInterface를 호출하는 일이다.

하지만 특정한 객체의 경우 이러한 방법을 통한 마샬링이 비효율적인 경우가 있을 수 있다. 비록 IDL이 다양한 기능으로 여러가지 경우에 매우 효율적인 프록시/스텁 코드를 만들어 주기는 하지만 아무래도 객체에 대해 더 잘 알고 있는 프로그래머가 조금 더 효율적인 코드를 만들 수 있는 것은 당연한 것 아닌가? 그러다면 커스텀 마샬링은 어떻게 구현 하면 되는가? 바로 커스텀 마샬링을 하고 싶은 개체한테 IMarshal 이라는 인터페이스를 구현하면 된다.

interface IMarshal : IUnknown
   {
   HRESULT GetUnmarshalClass(REFIID iid, void *pvInterface
       , DWORD dwDestContext, void *pvDestContext, DWORD mshlflags
       , CLSID *pclsid);
   HRESULT GetMarshalSizeMax(REFIID iid, void *pvInterface
       , DWORD dwDestContext, void *pvDestContext, DWORD mshlflags
       , DWORD *pcb);
   HRESULT MarshalInterface(IStream *pstm, REFIID iid, void *pvInterface
       , DWORD dwDestContext, void *pvDestContext, DWORD mshlflags);
   HRESULT UnmarshalInterface(IStream *pstm, REFIID iid, void **ppv);
   HRESULT DisconnectObject(DWORD dwReserved);
   HRESULT ReleaseMarshalData(IStream *pstm);
   };
사실 IMarshal 인터페이스를 구현하는 경우는 매우 드물다. 하지만 이 인터페이스를 이해하고 있는 것은 큰 도움이 된다. COM Library는 객체를 마샬링할 필요가 있을 때 객체에게 QueryInterface를 통해 IMarshal 인터페이스를 구현하고 있는 지 알아본다. 그리고 만약 구현하고 있지 않다면 스탠다드 마샬링을 사용한다. 하지만 만약 구현하고 있다면 여기의 매서드를 통해 인터페이스를 마샬링한다. 가장 중요한 매서드는 MarshalInterface와 UnmarshalInterface 이다. 인자는 거의 명확하다. 직렬화 시킬 저장 공간과 인터페이스의 IID와 포인터 그리고 위치에 대한 정보다. 여기서 위치에 대한 정보는 이것이 같은 프로세스 안에서 사용 될 것인지 아니면 다른 프로세스 또는 다른 컴퓨터에서 사용될 것인지를 결정하는 내용이다. UnmarshalInterface의 경우는 반대로 이 스트림에서 프록시 객체를 만들어 내는 메서드이다.

 

Free Threaded Marshaler

여기서 이전의 논의를 기억해내자. 같은 프로세스 내에서 STA에서 MTA에 있는 객체의 메서드를 부르는 것도 아파트먼트의 경계를 지나게 되므로 프록시를 통해 매서드 호출을 하게 된다고 했다. 하지만 사실 이 객체는 MTA와 호환되는 객체이므로 분명 여러 스레드가 접근하는 것에 대해 안전한 객체일 것이다. 즉 STA에 있는 스레드가 직접 접근해도 문제가 생기지 않는 다는 뜻이다. 하지만 스탠다드 마샬링을 쓸 경우 프록시 객체를 쓰게 되며 이는 스레드 컨텍스트 스위칭을 발생시키고 상당한 성능 저하를 나타내게 된다. 하지만 우리에겐 Custom Marshaling이 있다. MarshalInterface와 UnmarshalInterface를 통해 마샬링 할 때 위치정보가 주어진다는 사실을 상기하며 만약 이 위치정보가 같은 프로세스 내를 가르키고 있을 때에는 프록시 객체가 아닌 실제 객체에 대한 포인터를 돌려 준다면? 그렇다면 만약 다른 아파트먼트에 있는 스레드가 접근 하더라하더라도 스레드 컨텍스트 스위칭을 일으키지 않고 메서드를 호출할 수 있게 된다. 사실 이러한 마샬링 방법은 흔히 사용되는 방법이기도 하다. 그래서 이러한 구현을 이미 해놓고 통합(Aggregation)을 통해 재사용 할 수 있는 API가 제공된다. 이것이 바로 CoCreateFreeThreadedMarshaler 라는 API이다.

HRESULT CoCreateFreeThreadedMarshaler(
LPUNKNOWN punkOuter,
LPUNKNOWN * ppunkMarshaler
);
첫번째 인자로 FTM을 구현하고자 하는 객체를 넣으면 두 번째 인자로 만들어진 객체가 나온다. 이렇게 만들어진 객체는 같은 프로세스내에서는 Cross-Apartment Call로 인한 어떠한 성능저하도 겪지 않는다.

주의 : 커스텀 마샬링을 구현하는 것은 COM+에서 Configured Component로 사용될 수 없음을 의미한다. FTM의 경우도 마찬가지 이므로 주의 하자. 사실 COM+는 마샬링 과정을 (COM+용어로 말하면 Interception이다) 자신이 제공하는 동기화, 보안, Queued Component등의 서비스를 제공하는데 매우 유용하게 이용하고 있다. 만약 Custom Marshaling을 구현한다면 이를 이용할 수 없게 되는 것이다. 만약 이 점이 COM+의 유용성에 큰 단점이 된다고 생각하면 COM+와 함께 새로 소개된 TNA라는 새로운 아파트먼트에 대해서 알아보자.

 

Thread Neutral Apartment

윈도우 2000, COM+는 TNA라고 하는 새로운 아파트먼트 모델을 발표하였다. 이 아파트먼트 역시 MTA 처럼 모든 프로세스에서 하나씩만 존재하는 아파트먼트이다. 하지만 중요한 점은 이 아파트먼트 안에 들어가기 위해 CoInitializeEx를 호출 할 필요가 없다는 점이다. 이 아파트먼트는 CoInitializeEx를 통해 들어가는 것이 아니다. 이 아파트먼트에는 어떤 스레드던 자신이 원한다면 들어갈 수 있다. 즉 STA에 들어가있는 스레드던 MTA에 들어가 있는 스레드건 TNA에 직접 들어가 메서드를 실행시킬 수 있는 것이다.

그러다면 TNA에 있는 객체와 FTM을 구현하는 객체와는 어떻게 다른가? FTM의 경우도 어떠한 아파트먼트에서의 호출이던 객체에 직접 접근하지 않는가? 바로 그것은 TNA에 있는 객체의 경우 다른 아파트먼트에서 이에 객체에 대한 인터페이스를 얻을려면 이것은 역시 직접적인 포인터가 아닌 프록시 객체라는 점이다. 다만 이 프록시 객체가 다른 아파트먼트의 경우처럼 스레드 컨텍스트 스위칭을 일으키지는 않는다. 다만 COM+가 제공하는 서비스에 대한 검사만을 행하고 메서드 호출을 한 스레드가 직접 TNA에 들어가서 메서드를 실행 시킨다.

그렇다면 무엇이 FTM에 비해 낳은가? 아무리 검사만을 행한다고 하지만 분명 FTM처럼 직접적인 포인터를 통한 접근은 아니다. 이는 조금은 속도 저하를 가져올 수도 있는 부분이다. 하지만 그 차이는 스레드 컨텍스트 스위칭과 비교한다면 정말 아무것도 아닌 비용이다. 하지만 이에 비해 COM+의 서비스가 제공하는 각종 서비스를 받을 수 있다는 점을 생각한다면 TNA는 성능과 유연성을 겸비한 매우 가치있는 아파트먼트 모델이 된다.

한가지 더 얘기 하자면 만약 Essential COM등의 책을 보신 분이나 기존에 COM+에 대한 개발방향등을 접하신 분이라면 RTA라는 모델에 대해서 들어보신 분이 있을 것이다. 이 모델은 TNA와 매우 비슷하지만 TNA의 경우 여러 개의 스레드가 동시에 접근 가능하고 RTA의 경우 한 스레드만이 접근할 수 있는 모델이다. (RTA란 모델은 실제 하지 않는다.) 하지만 마이크로소프트는 TNA를 RTA처럼 제한 되게 만들지 않고 기본적으로 여러 스레드의 접근을 허용한 후 COM+의 동기화등을 통해 RTA처럼 사용될 수 있게 만들었다.

 

결론

 

아파트먼트와 스레드에 관한 내용은 사실 보안과 함께 COM의 가장 어려운 부분이다. 사실 이 부분은 COM뿐 아니라 모든 어플리케이션에게 있어 가장 힘든 부분이기도 하다. 게다가 소스코드 거의 한 줄 없는 이 글은 매우 어려울지도 모른다. 하지만 이것은 분명 스레딩 모델의 차이 때문에 기존의 객체를 사용하지 못하는 것 보다는 낫다. 특히 Apartment Threaded 모델의 경우는 스레딩에 관해 생각하지 않고 짜도 멀티스레드 프로그램에서도 쉽게 재사용할 수 있다. 하지만 조금 더 우수한 컴포넌트를 만들기 위해 다른 스레딩 모델에 대해서도 알아보도록 하자. 최근들어 많은 컴포넌트의 개발이 VB를 통해 이루어지면서 Apartment Threaded 밖에 지원하지 않는 VB의 특성상 다른 아파트먼트 모델에 대해 관심을 가지는 경우는 매우 드문 경우 였던거 같다. 아니 VC++를 이용해 COM Component를 만드는 것 자체를 낭비라고 생각하는 것 같다. 이러한 점은 기존의 MTS가 Apartment Threaded모델 만을 지원했던 점과 맞물려 심지어 COM+ Component는 VB만으로 만들 수 있다, 내지는 COM+가 STA만 지원한다는 설까지 만들어 낸 거 같다. 물론 개인적으로 VB를 매우 유용한 언어라고 생각하며 프로젝트시 VC++보다 많이 쓰일 가능성이 높다는 점을 인정한다. 또한 개발기간이 프로젝트에 매우 중요한 요소임도 안다. 하지만  VC++로 좀 더 우수한 성능을 낼 수 있는 모델로 개발할 수 있다는 가능성마저 잊지는 말자. 어쨌든 최소한 개발기간 다음으로 중요한 요인은 성능과 안정성이니까.

또 하나 언급하고 싶은 점은 COM+의 경우 아파트먼트 보다 조금더 세분화 된 Context라는 단위로 객체가 존재하는 공간을 나눈다. 그리고 Context 간의 메서드 호출을 interceptor를 이용해 처리해 각종 서비스를 제공한다. 위의 논의의 상당부분이 이를 통해 논의되는 것이 정확함에도 불구하고 객체를 아파트먼트 단위로만 논의 했다. 이는 스레드에 관한 내용에 집중하기 위해서이다. 또한 마샬링에 관한 많은 내용이 자세히 설명되지 못했는데 이에 관한 논의는 다음에 하도록 하자.

 

참고서적

Essential IDL(Addison Wesley)
Essential COM (Addison Wesley)
COM+ Programming – A Practical Guide Using Visual C++ and ATL (Prentice Hall)
Application Programming for Microsoft Windows 4th(Microsoft Press)
MSJ – House of COM – Don Box

char *

char *orig = "Hello, World!";


size_t origsize = strlen(orig) + 1;    // wchar_t *

const size_t newsize = 100;

size_t convertedChars = 0;

wchar_t wcstring[newsize];

mbstowcs_s(&convertedChars, wcstring, origsize, orig, _TRUNCATE);

wcscat_s(wcstring, L" (wchar_t *)");


_bstr_t bstrt(orig);    // _bstr_t

bstr += " (_bstr_t)";


CComBSTR ccombstr(orig);    // CComBSTR

if(ccombstr.Append(L"(CComBSTR)") == S_OK) CW2A printstr(ccombstr);


CString cstring(orig);    // CString

cstring += " (CString)";


string basicstring(orig);    // basic_string

basicstring += " (basic_string)";


String ^systemstring = gcnew String(orig);    // System.String

systemstring += " (System::String)";

delete systemstring;


wchar_t *

wchar_t *orig = L"Hello, World!";


size_t origsize = wcslen(orig) + 1;    // char *

const size_t newsize = 100;

size_t convertedChars = 0;

char nstring[newsize];

wcstombs_s(&convertedChars, nstring, origsize, orig, _TRUNCATE);

strcat_s(nstring, " (char *)");


_bstr_t bstrt(orig);    // _bstr_t

bstrt += " (_bstr_t)";


CComBSTR ccombstr(orig);    // CComBSTR

if(ccombstr.Append(L" (CComBSTR)") == S_OK) CW2A printstr(ccombstr);


CString cstring(orig);    // CString

cstring += " (CString)";


wstring basicstring(orig);    // basic_string

basicstring += L" (basic_string)";


String ^systemstring = gcnew String(orig);    // System.String

systemstring += " (System::String)";

delete systemstring;


_bstr_t

    BSTR을 캡슐화하는 COM 지원 클래스 (comutil.h, comsuppw.lib 또는 comsuppwd.lib)

_bstr_t orig("Hello, World!");


const size_t newsize = 100;    // char *

char nstring[newsize];

strcpy_s(nstring, (char *)orig);

strcat_s(nstring, " (char *)");


wchar_t wcstring[newsize];    // wchar_t *

wcscpy_s(wcstring, (wchar_t *)orig);

wcscat_s(wcstring, L" (wchar_t *)");


CComBSTR ccombstr((char *)orig);    // CComBSTR

if(ccombstr.Append(L" (CComBSTR)") == S_OK) CW2A printstr(ccombstr);


CString cstring((char *)orig);    // CString

cstring += " (CString)";


string basicstring((char *)orig);    // basic_string

basicstring += " (basic_string)";


String ^systemstring = gcnew String((char *)orig);    // System.String

systemstring += " (System::String)";

delete systemstring;


CComBSTR        ATL의 BSTR wrapper 클래스 (atlbase.h)

CComBSTR orig("Hello, World!");

CW2A printstr(orig);


const size_t newsize = 100;    // char *

char nstring[newsize];

CW2A tmpstr1(orig);

strcpy_s(nstring, tmpstr1);

strcat_s(nstring, " (char *)");


wchar_t wcstring[newsize];    // wchar_t *

wcscpy_s(wcstring, orig);

wcscat_s(wcstring, L" (wchar_t *)");


_bstr_t bstrt(orig);    // _bstr_t

bstrt += " (_bstr_t)";


CString cstring(orig);    // CString

cstring += " (CString)";


wstring basicstring(orig);    // basic_string

basicstring += L" (basic_string)";


String ^systemstring = gcnew String(orig);    // System.String

systemstring += " (System::String)";

delete systemstring;


CString        ATL/MFC의 템플릿 클래스인 CStringT의 기본 구현 클래스.

CString orig("Hello, World!");


const size_t newsize = 100;    // char *

char nstring[newsize];

strcpy_s(nstring, orig);

strcat_s(nstring, " (char *)");


// wchar_t * (이 작업을 하려면 먼저 char *로 바꿔야 한다)

size_t origsize = strlen(orig) + 1;

size_t convertedChars = 0;

wchar_t wcstring[newsize];

mbstowcs_s(&convertedChars, wcstring, origsize, orig, _TRUNCATE);

wcscat_s(wcstring, L" (wchar_t *)");


_bstr_t bstrt(orig);    // _bstr_t

bstrt += " (_bstr_t)";


CComBSTR ccombstr(orig);    // CComBSTR

if(ccombstr.Append(L" (CComBSTR)") == S_OK) CW2A printstr(ccombstr);


string basicstring(orig);    // basic_string

basicstring += " (basic_string)";


String ^systemstring = gcnew String(orig);    // System.String

systemstring += " (System::String)";

delete systemstring;


basic_string        STL 클래스 (<string>)

string orig("Hello, World!");


const size_t newsize = 100;    // char *

char nstring[newsize];

strcpy_s(nstring, orig.c_str());

strcat_s(nstring, " (char *)");


// wchar_t * (이 작업을 하려면 먼저 char *로 바꿔야 한다)

size_t origsize = strlen(orig.c_str()) + 1;

size_t convertedChars = 0;

wchar_t wcstring[newsize];

mbstowcs_s(&convertedChars, wcstring, origsize, orig.c_str(), _TRUNCATE);

wcscat_s(wcstring, L" (wchar_t *)");


_bstr_t bstrt(orig.c_str());    // _bstr_t

bstrt += "(_bstr_t)";


CComBSTR ccombstr(orig.c_str());    // CComBSTR

if(ccombstr.Append(L" (CComBSTR)") == S_OK) CW2A printstr(ccombstr);


Cstring cstring(orig.c_str());    // CString

cstring += " (CString)";


String ^systemstring = gcnew String(orig.c_str());    // System.String

systemstring += " (System::String)";

delete systemstring;


System.String        .NET 프레임워크 클래스 (mscorlib)

String ^orig = gcnew String("Hello, World!");


pin_ptr<const wchar_t> wch = PtrToStringChars(orig);


size_t origsize = wcslen(wch) + 1;    // char *

const size_t newsize = 100;

size_t convertedChars = 0;

char nstring[newsize];

wcstombs_s(&convertedChars, nstring, origsize, wch, _TRUNCATE);

strcat_s(nstring, " (char *)");


wchar_t wcstring[newsize];    // wchar_t *

wcscpy_s(wcstring, wch);

wcscat_s(wcstring, L" (wchar_t *)");


_bstr_t bstrt(wch);    // _bstr_t

bstrt += " (_bstr_t)";


CComBSTR ccombstr(wch);    // CComBSTR

if(ccombstr.Append(L" (CComBSTR)") == S_OK) CW2A printstr(ccombstr);


CString cstring(wch);    // CString

cstring += " (CString)";


wstring basicstring(wch);    // basic_string

basicstring += L" (basic_string)";


delete orig;

'C / C++ > COM / ATL / WTL' 카테고리의 다른 글

COM Apartment  (0) 2008.02.28
아파트먼트 종류와 모델에 따른 반응성 차이  (1) 2008.02.28
WTL Button Popup Menu  (0) 2008.02.24

첨부파일을 포함

//클래스 멤버 변수를 만들고...
//버튼메뉴
CButtonMenu m_BtnMenuFile;

::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
함수안에서
 m_BtnMenuFile.SubclassWindow(GetDlgItem(IDC_BUTTON_XXX));
 m_BtnMenuFile.SetMenu(IDR_MENU_XXX);

이러면 결과가

사용자 삽입 이미지

이렇게 나온다...
첨부파일 안에 내용을 조금 수정하면 팝업위치를 원하는 위치로 변경시킬 수 있음...

+ Recent posts