'3. Implementation/MFC'에 해당되는 글 52건

  1. 2009.01.16 CWnd::GetCurrentMessage : 현재 메시지 핸들러가 처리하고 있는 MSG 정보 가져오기
  2. 2008.12.10 How to use a user-defined message
  3. 2008.09.29 [번역] Architecture of BCGControlBar Library (Professional Edition)
  4. 2008.07.13 TN017: Destroying Window Objects
  5. 2008.07.09 TN038: MFC/OLE IUnknown Implementation
  6. 2008.07.05 Windows (창) 개체
  7. 2008.07.05 CObject 관련 정리
  8. 2008.07.02 Tip: Detecting a HMODULE/HINSTANCE Handle Within the Module You're Running In
  9. 2008.06.21 IDL 파일, TypeLibrary, 그리고 Automation
  10. 2008.06.18 Adding automation to MFC applications
  11. 2008.06.16 파일 Drag & Drop 간단하게 구현하기
  12. 2008.06.15 VariantUse 샘플: Variant 사용 설명
2009. 1. 16. 16:19

CWnd::GetCurrentMessage : 현재 메시지 핸들러가 처리하고 있는 MSG 정보 가져오기

CWnd::GetCurrentMessage

This method obtains a pointer to the MSG structure that contains the message the window is currently processing.

static const MSG* PASCAL GetCurrentMessage( ); 

Return Value

Returns a pointer to the MSG structure that contains the message the window is currently processing. Should only be called when in an OnMessage handler.

Remarks

This method returns a pointer to a MSG structure. In Windows CE, the time member of this structure contains the system time at which CWnd::GetCurrentMessage is called, rather than the time at which the message was posted.

 
// The following example toggles two views in a single document
// interface (SDI) frame window. A design decision must be made about
// whether to leave the inactive view connected to the document,
// allowing the inactive view to continue receiveing OnUpdate
// notifications from the document. It is often desirable to
// keep the inactive view continuously in synchronization with the
// document, even though it is inactive. However, doing so incurs a
// performance cost,as well as the programming cost of implementing
// OnUpdate hints. It may be less expensive, in terms of performance and
// programming,to re-sync the inactive view with the document only when it
// is reactivated. The following example illustrates the latter approach, // by reconnecting the newly active view and disconnecting the newly
// inactive view, through calls to CDocument::AddView and RemoveView.

BOOL CMainFrame::OnViewChange(UINT nCmdID)
{
 CView* pViewAdd;
 CView* pViewRemove;
 CDocument* pDoc = GetActiveDocument();
 UINT nCmdID;
   
 nCmdID = LOWORD(GetCurrentMessage()->wParam);

 if((nCmdID == ID_VIEW_VIEW1) && (m_currentView == 1))
   return;
 if((nCmdID == ID_VIEW_VIEW2) && (m_currentView == 2))
  return;

 if (nCmdID == ID_VIEW_VIEW2)
 {
  if (m_pView2 == NULL)
  {
   m_pView1 = GetActiveView();
   m_pView2 = new CMyView2;

// If OnSize has been overridden in CMyView2
// and GetDocument() is used in this override it can
// cause assertions and, if the assertions are ignored,
// cause access violations.

   m_pView2->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
     rectDefault, this, AFX_IDW_PANE_FIRST + 1, NULL);
  }
   pViewAdd = m_pView2;
   pViewRemove = m_pView1;
   m_currentView= 2;
 }
 else
 {
  pViewAdd = m_pView1;
  pViewRemove = m_pView2;
  m_currentView= 1;
 }
    
// Set the child identifier of the active view to AFX_IDW_PANE_FIRST,
// so that CFrameWnd::RecalcLayout will allocate to this
// first pane, that portion in a client area of the frame window that is
// not allocated to control bars. Set the child identifier of the
// other view to anything besides AFX_IDW_PANE_FIRST; this
// example switches the child identifiers of the two views.

 int nSwitchChildID = pViewAdd->GetDlgCtrlID();
 pViewAdd->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
 pViewRemove->SetDlgCtrlID(nSwitchChildID);

 // Show the newly active view and hide the inactive view.

 pViewAdd->ShowWindow(SW_SHOW);
 pViewRemove->ShowWindow(SW_HIDE);

 // Connect the newly active view to the document and
 // disconnect the inactive view.
 pDoc->AddView(pViewAdd);
 pDoc->RemoveView(pViewRemove);

 SetActiveView(pViewAdd);
 RecalcLayout();
}


출처 : MSDN
2008. 12. 10. 08:27

How to use a user-defined message

  1. Create a user-defined message with ::RegisterWindowMessage like this:

    const
    UINT msgFileChange = ::RegisterWindowMessage("FILECHANGE");

    Put this in the application's header file so that you can use this everywhere in your application

  2. In your document-class, create a member-variable to hold a pointer to the window you want to process the message.
     
  3. CView *m_pView;
    // This variable must be initialized in your view-class.
  4. In the method called by the thread, post the message to the processing window.

    //
    You can pass wparam, lparam if you wish m_pView->PostMessage(msgFileChange);
  5. In the processing view header file, add the following in the message map functions:

    //
    when you pass wparam, lparam add parameters to this function afx_msg void OnFileChange();
  6. In the processing view source file, add the following in the message map:
     
  7. ON_REGISTERED_MESSAGE(msgFileChange, OnFileChange)
  8. Add the source code for OnFileChange in the processing view source file.

    void
    CProcessView::OnFileChange() { CMyDocument *pDoc = GetDocument(); pDoc->UpdateAllViews(); }

From : http://www.codeproject.com/KB/files/filechange.aspx
2008. 9. 29. 00:30

[번역] Architecture of BCGControlBar Library (Professional Edition)

Architecture of BCGControlBar Library (Professional Edition)

 

번역 : codemuri (http://codemuri.tistory.com/)

 

BCGControlBar Library Proferssional Edition (Pro Version) 은 비 MFC 기반 도킹 아키텍처이다.

이것은 모든 control bar CControlBar를 상속하지 않는다는 것을 의미한다. Pro 버전에서 모든 컨트롤 바의 기본 클래스는 CBCGPBaseControlBar (CWnd로부터 상속된) 이다.

 

Pro 버전 아키텍처에 숨겨진 메인 아이디어는 MFC와 크게 다르지 않다. 오히려 Pro 버전이 MFC와 아주 호환될 수 있도록 만들려고 노력하고 있다.

 

아래는 Pro 버전으로 만들어진 애플리케이션의 이미지이다.



 

애플리케이션 메인 프레임 윈도우는 보통 CBCGPFrameWnd 또는 CBCGPMDIFrameWnd로 표현된다. 메인 프레임은 “dock site”라고 불린다. 모든 control bar는 세 종류의 부모를 가 질 수 있다:

l  “dock site”,

l  “dock bar” 또는

l  “miniframe window”.

Control bar는 두개의 일반적인 형태로 나뉠 수 있다:

l  non resizable control bar (status bar toolbar와 같은) 그리고

l  resizable control bar.

 

“resizable” control bar 의 경우 splitter(또는 slider)에 의해 크기가 조절될 수 있는 바를 의미하거나, “container”(다른 control bar에 도킹 될 수 있고 control bar들 사이에 splitter를 생성할 수 있는 하나의 control bar) 를 형성하는(form) 바를 의미하거나, 그리고 dock bar에 부착될 수 (또는, 도킹 될 수) 없는 bar를 의미한다.

모든 non resizable control barCBCGPControlBar를 상속해야 한다. 모든 resizable control barCBCGPDockingControlBar 를 상속해야 한다. (이 클래스는 dock site에 도킹될 수 있는, container 내에 놓일 수 있는, autohide가 될 수 있는 등의 기본적인 능력을 구현한다.)

 

Dock Site

메인 프레임 윈도우 또는 dock site는 모든 control bar miniframe window의 소유자(owner)이다. CBCGPDockManager 클래스의 내장 멤버를 포함한다 (뒤에서부터는 docking manager). docking manager dock site에 속하는 모든 control bar의 목록을 관리한다. 그 목록은 dock site의 바깥쪽 가장자리(outer edge)에서부터 생성된 바를 처음에 오게하는 방식으로 정렬된다. layout이 재계산 되어야할 필요가 있을 때, 그 목록을 순회하고(loop over), (dock siter) 현재 경계 영역(bounding rectangle)에 맞게 각 control bar layout을 조절하고, 그리고 control bar 영역 일부를 제거한다. 도킹 layout을 조절해야 할 필요가 있을 때마다 mainframeclass::AdjustDockingLayout 또는 RecalcLayout 을 호출할 수 있다 그리고 이러한 호출은 docking manager에게 재지정될 것이다. (redirected)

 

Dock bar

main frame border를 따라 위치한 (border에 배치된) “dock bar”를 가질 수 있다. Dock barCBCGPDockBar 클래스의 control bar이다. Dock bar CBCGPControlBar를 상속한 객체 (보통은 toolbar)를 받아들일 수 있다. Dock bar main frame window의 초기화 동안에 mainframeclass::EnableDocking(DWORD dwDockStyle) 을 호출함으로써 생성되어야 한다. main frame 윈도우의 어떤 사이드에서 “auto hide” 바를 활성화하길 원한다면 mainframeclass::EnableAutoHideBars(DWORD dwDockStyle)을 호출해야 한다. docking 배열을 상세하기 위해서 CBRS_ 스타일을 여전히 사용할 수 있다. dock bar“dock row”로 나뉘어 진다(“dock row”에 대한 목록을 포함한다). Dock rowCBCGPDockBarRow 클래스에 의해 표현된다. dock row는 툴바 목록을 포함하고 있다. 툴바가 도킹 될 때 (예를 들어, 마우스에 의해) 또는 동일한 dock bar내에서 한 행(row)에서 다른 행으로 이동될 때, framework는 새로운 행을 생성하고 적절히 dock bar의 크기를 변경하거나 현존하는 행(row) 위에 툴바를 위치시킨다.

 

Miniframe window

control bar가 떠다닐 때 control bar miniframe window위에 상주한다. miniframe window는 두개의 클래스로 표현된다. CBCGPMiniFrameWnd (오직 한 개의 control bar만을 포함할 수 있다) CBCGPMultiMiniFrameWnd (여러 개의 control bar를 포함할 수 있다). miniframe window control bar가 떠 다니게 될 때 framework에 의해 자동적으로 생성된다. code에서 control bar가 떠다니도록 만들기 위해서는 CBCGPBaseControlBar::FloatControlBar()를 호출할 수 있다. control bar가 떠 다니게 될 때 그 control bar의 부모는 자동적으로 변경되고 miniframe window에 설정된다. control bar가 도킹될 때 control bar의 부모는 dock bar (툴바를 위한)로 변경되거나 dock site (크기조절이 가능한 control bar를 위한)로 변경된다.

 

Sliders

Slider (또는 splitter)CBCGPSlider 클래스로 표현된다. Slider docking control bar dock site나 다른 control bar에 도킹될 때 framework에 의해 자동적으로 생성된다. bar dock site에 도킹될 때 그 새로운 slider“default slider”라 불린다. default slider는 크기를 조절하는 docking control bar layout에 관한 모든 정보를 포함하고 있다. Default sliderdock manager에 의해 관리되는 control bar의 목록 내에 위치하고 포함된(contained) docking control bar 위에서 수행된 모든 layout 연산을 제어한다.

 

Containers

모든 크기변경이 가능한 control bar, control bar에서 다른 control bar로 도킹될 때, “container”에 의해 관리된다. ContainerCBCGPBarContainer 클래스로 표현된다. container“left” bar, “right” bar, “left” sub-container, left right 부분사이의 “right” sub-conatiner splitter에 대한 포인터를 가지고 있다. 이런 식으로 여러 control bar splitter의 트리를 구축할 수 있다 그리하여 함께 크기가 변경될 수 있는 control bar들의 매우 복잡한 layout을 이룩할 수 있다. CBCGPBarContainerManager 클래스는 container의 트리를 관리한다 (CBCGPBarContainerManager root container에 대한 포인터를 포함한다). 또한, CBCGPBarConatinerManager는 이 트리 내에 위치된 control bar slider에 대한 두 개의 목록을 관리한다. Bar container manager는 대개 다중 control bar를 운반하는 default slider miniframe window에 내장된다(embedded).

 

Autohide control bars

docking control bar“autohide” 능력을 가지기 위해 자동적으로 활성화된다. 사용자가 docking control bar의 제목에 위치한 pin 버튼을 클릭할 때, bar autohide mode로 전환된다. frameworkCBCGPAutoHideToolbar 타입의 새로운 툴바와 CBCGPAutoHideButton 타입의 새로운 버튼을 생성하고, autohide dock bar 위에 툴바를 위치시키고 (현재 bar 정렬에 따라), 그 툴바에 autohide 버튼을 부착시킨다 그리고 docking control bar에 대한 포인터를 그 버튼에 부착한다. 이 때 docking control bar dock manager에 의해 관리되는 autohide bar의 특별한 목록 내에 놓여진다.

 

Tabbed control bars and outlook bars

CBCGPBaseTabWnd 클래스는 떼어낼수 있는(detachable) tab을 가진 tabbed window의 기본 기능을 구현한다. CBCGPBaseTabbedBarCBCPDockingControlBar를 상속하고 CBCGPBaseTabWnd 객체에 대한 포인터를 관리한다. 이런 식으로 tabbed control bar를 도킹하고 크기를 변경하는 능력을 달성한다. 떠다니는 동안 docking tabbed control bar를 생성하기 위해서는 CBCGPDockingControlBar::AttachToTabWnd 를 사용하라.

(Use CBCGPDockingControlBar::AttachToTabWnd to create docking tabbed control bars on the fly.)

Outlook bar control 또한 Pro Version에서는 tabbed bar 기초를 두고 있다 (CBCGPOutlookBar CBCGPBaseTabbedBar 상속한다)

 

Some tips how to customize the application behavior

1.    여러 새로운 스타일의 조합을 적용시키는 새로운 control bar 생성할 있다.

CBRS_BCGP_FLOAT              - control bar 떠다니게 만든다

CBRS_BCGP_AUTOHIDE        - auto hide mode 활성화 한다.

CBRS_BCGP_CLOSE              - control bar 닫힐 있다(숨겨질 있다)

 

CBCGPBaseControlBar 이러한 플래그를 반영하기 위한 다음의 virtual Boolean 메소드를 구현한다 : CanBeClosed(), CanAutoHide(), CanFloat(). 이런 행위를 커스터마이즈 하기 위해 상속된 클래스내에서 메소드들을 재정의할 있다.

 

2.    BOOL CanAcceptBar(const CBCGPBaseControlBar * pBar) 메소드를 재정의함(override)으로써 도킹되는 행위를 커스터마이즈 있다. control bar FALSE 반환할 pBar control bar 도킹되지 않을 것이다.

 

3.    이제 라이브러리는 두개의 도킹 모드를 허용한다: immediate (control bar 드래그 되고 어떤 context 도킹될 처음 베타 내에서 사용가능 했던) standard (control bar 드래그 되고 drag rectangle 사용하여 도킹될 ). 도킹 모드는 BCGP_DOCK_TYPE::DT_IMMEDIATE 또는 BCGP_DOCK_TYPE::BCGP_DT_STANDARD 있다 그리고 CBCGPDockManager::m_dockModelGlobal 의해 전역으로 정의된다. 변수를 언제든 변경할 있다. 개인적인 control bar 위한 도킹 모드를 제어하길 원한다면, BCGP_DOCKTYPE CBCGPBaseControlBar::GetDockMode() 재정의할 있다.

 

4.    다닐 없고 control bar 도킹 없는 static control bar 생성하길 원한다면 (BCGPOutlookDemo 예제에서 outlook bar 처럼) control bar non-floating 으로 생성해야 하고 FALSE 반환하는 DoesAllowDynInsertBefore 메소드를 재정의 해야 한다. bar CBRS_BCGP_FLOAT 스타일 없이 생성되었다면 버전 6.01 부터 디폴트 구현은 FALSE 반환한다.

 

5.    Outlook bar regular tabbed control bar로서 동작한다. control bar 다른 control bar 도킹할 초래되는 스타일은 두번째 도킹 control bar 생성될 상세했던 다음 스타일에 따라 정의된다.

 

CBRS_BCGP_REGULAR_TABS – regular tabbed control bar 생성한다

CBRS_BCGP_OUTLOOK_TABS – outlook-스타일의 tabbed bar 생성한다.

 

CBCGPDockingControlBar SetTabbedControlBarRTC 메소드를 가지고 있다. tab window 대한 도킹의 결과로 생성될 control bar 커스터마이즈 하기 위해서 이것을 사용할 있다.

 

6.    outlook tabbed control bar CWnd 상속한 모든 control 추가할 있다. 라이브러리는 CBCGPDockingCBWrapper 객체를 사용하여 그러한 bar 자동적으로 감쌀 것이다(Wrap). 이것은 요구되는 도킹 행위를 동시에 제공하는 outlook tabbed control bar로부터 이러한 bar들을 떼어내도록(detach) 허용한다. CBCGPBaseTabWnd( tab window outlook window base class 사용되는) wrapper appearance behavior 커스터마이즈 있도록 하는 SetDockingBarWrapperRTC 메소드를 가지고 있다. 기능을 비활성화 하기 위해서는 CBCGPBaseTabWnd::m_bEnableWrapping FALSE 설정한다. control bar wrap , control bar 직접적인 부모(immediate parent) tab window 추가되는 시점에 dummy docking control bar 설정된다.

7.    non-detachable tab 추가하고자 한다면 CBCGPBaseTabWnd::AddTab 호출하고 bDetachable FALSE 설정하거나 EnableTabDetach 호출할 있다.

 

8.    tab 전환(swapping) 활성/비활성하길 원한다면 CBCGPBaseTabWnd::EnableTabSwap 호출하라.

 

9.    -1 아닌 ID 모든 control bar 생성하길 강력하게 추천한다.

 

10. 초기 도킹 layout 변경하길 원한다면, 변경을 보기 위해서는 application 레지스트리 entry clear해야 한다. (application docking 상태를 registry 저장한다면)

 

11.  detachable tab 활성화 하고자 한다면, tabbed window 동적으로 생성되고 소멸될 것이다, 동적인 tabbed window 대한 포인터를 저장하지 마라.

 

12. 두개의 non-tabbed docking control bar 대해 CBCGPDockingControlBar::AttachToTabWnd 호출한다면, tabbed window 대한 포인터는 마지막 파라미터 ppTabbedControlBar 이용하여 반환될 것이다. 새로이 생성된 tabbed window tab 계속해서 추가하기 위해서 (또는 다른 연산을 위해서) 그것을 사용할 있다.  

 

13. docking control bar 다른 control bar 어떤 순서(alignment)로든 도킹시키길 원한다면, CBCGPDockingControlBar::DockToWindow 호출할 있다. 원본 control bar 이때 어딘가에 도킹되어야만 한다. 툴바의 경우 DockControlBarLeftOf 여전히 사용할 있다.

 

14. ShowControlBar 메소드는 세번째에 non-default 파라미터인 bActivate 가지고 있다, 그리하여 예전 코드는 컴파일되지 않을 있다.

 

15. CBCGPDockingControlBar::m_bHideInAutoHideMode ShowContrlBar 호출할 자동 숨김 모드일 경우 docking control bar 어떻게 동작할지에 대해 제어하는 새로운 정적 멤버이다. 변수가 만일 TRUE 설정된다면, control bar control bar 자동 숨김 버튼은 숨겨질 것이다. 그렇지 않은 경우, control bar slide in/out 것이다.

 

16. control bar 가시성(visibility) 결정짓기 위해 GetStyle() & WS_VISIBLE 사용하지 마라. 대신에 CBCGBaseControlBar::IsVisible 사용하라, 왜냐하면 함수가 tabbed/autohide 모드에서 visibility state 올바르게 다루기 때문이다.

 

17. non-floating, non-resizable control bar 생성하길 원한다면 DockControlBar 호출해서는 안된다. 대신에 CBCGPDockManager::AddControlBar(CBCGPControlBar * pNonFloatingBar, …) 호출해야 한다. 이것은 control bar docking manager 이용하여 등록할 것이고 bar docking layout 일부가 것이다.

 

18. non-floating resizable control bar 생성하길 원한다면, CBRS_BCGP_FLOAT 스타일 없이 그것을 생성해야만 하고 DockControlBar(CBCGPDockingControlBar* pResizableBar) 호출해야 한다.

 

19. docking layout에서 어떤 control bar 제거(exclude)하길 원한다면, control bar dock bar로부터 toolbar 제거하고 CBCGPBaseControlBar::UnDockControlBar 호출해야 한다. autohide 모드에 있는 control bar 대해서나 tabbed window tab 상주하고 있는 control bar 대해서는 메소드를 호출해서는 안된다.

 

20. control bar autohide 모드로 설정된 경우, CBCGPBaseControlBar::FloatControlBar 또는 CBCGPBaseControlBar::UnDockControlBar 호출하기 전에 SetAutoHideMode 호출해야 한다.

 

21. CBCGPDockingControlBar::m_bDisableAnimation 사용하여 autohide animation 비활성화 있다.


2008. 7. 13. 23:49

TN017: Destroying Window Objects

TN017: Destroying Window Objects

 

이 노트는 CWnd::PostNcDestory 멤버 함수의 사용을 설명한다. 만일 CWnd를 상속한 객체에 최적화된 할당을 만들려고 한다면 이 함수를 사용해라. 이 노트는 또한  아주 중요한 규칙에 대한 몇 가지 이유를 설명한다. C++ 윈도우 객체를 파괴(destroy)하기 위해서는, “delete” 하지 마라. 이것은 중요하다. 만약 아래의 가이드라인을 따른다면, Cleanup 문제를 거의 가지지 않을 것이다. (예를 들면, C++ 메모리를 delete/free 하는 것을 잊어먹거나, HWND 와 같은 시스템 리소스를 해제하는 것을 잊어먹거나, 너무 많이 객체를 해제한다거나)

 

The Problem

윈도우 객체(CWnd를 상속한 클래스의 객체)C++ 객체(애플리케이션의 힙에 할당된) HWND(윈도우 관리자에 의해 시스템 리소스 내에 할당된) 모두를 나타낸다. 윈도우 객체를 파괴하는 방법은 여러 가지가 있기 때문에, 시스템 리소스 또는 애플리케이션 메모리 누수를 방지하는 그리고 객체와 윈도우 핸들이 한번 이상 파괴되는 것을 방지하기 위한 어떤 규칙 세트를 제공해야만 한다.

이것은 메모리 관리 문제 보다 더 심각하다. 윈도우는 사용자-인터페이스를 가진다 : 화면에 그려지는 윈도우; 윈도우가 파괴되면, 시스템 리소스에도 영향이 있다. 애플리케이션 주소 공간 내에서 C++ 메모리 누수는 시스템 리소스가 누수 되는 것 만큼은 나쁘지 않다.

 

Destroying Windows

윈도우 객체를 파괴하는 두 가지 가능한 방법은 :

  • CWnd::DestoryWindow 를 호출하거나 윈도우 API ::DestoryWindow를 호출한다.
  • Delete 연산자를 사용하여 명시적으로 삭제 하기

 

첫 번째 경우는 지금까지 가장 일반적이다. 이 경우는 당신의 코드가 직접적으로

DestoryWindow를 호출하지 않더라도 적용된다. 사용자가 직접적으로 프레임 윈도우를 닫을 때(WM_CLOSE의 기본적인 행위는 DestoryWindow를 호출하는 것이다), 그리고 부모 윈도우가 파괴 될 때, 윈도우는 모든 자식에 대해 DestoryWindow를 호출한다.

두 번째 경우, 윈도우 객체에 대한 delete 연산자의 사용, 는 거의 사용되어서는 안되며 아래 소개된 경우에만 사용되어야 한다. (The second case, the use of the delete operator on Windows objects, should be very rare and only in the cases outlined below.)

 

Auto Cleanup with CWnd::PostNcDestroy

윈도우를 파괴할 때, 윈도우로 보내지는 마지막 윈도우 메시지는 WM_NCDESTORY 이다. 그 메시지에 대한 디폴트 CWnd 핸들러(CWnd::OnNcDestory)C++ 개체와 HWND를 분리할 것이다 그리고 가상 함수인 PostNcDestory를 호출할 것이다. 일부 클래스는 C++ 객체를 삭제하기 위해 이 함수를 재정의(override)한다.

CWnd::PostNcDestory의 디폴트 구현은 아무것도 하지 않는다 그것은 스택 프레임에 할당된 윈도우 객체 또는 다른 객체 내에 내장된(embedded) 윈도우 객체에 대해서는 적절하다. 힙에 할당되도록 디자인 된 (다른 C++ 객체 내에 내장되지 않은) 윈도우 객체에 대해서는 적절하지 않다.

The default implementation of CWnd::PostNcDestroy does nothing which is appropriate for window objects allocated on the stack frame or embedded in other objects. This is not appropriate for window objects that are designed to be allocated by themselves on the heap (not embedded in other C++ object).

 

힙에 할당되도록 디자인 된 클래스는 delete this를 수행하기 위해 PostNcDestory 멤버 함수를 재정의 한다. 이 문장은 C++ 객체와 연결된 모든 C++ 메모리를 해제할 것이다. 만일 m_hWnd NULL 이 아닌 경우 디폴트 CWnd 소멸자가 DestoryWindow를 호출하더라도, 핸들은 분리될 것이고, Cleanup 단계(phase)동안 NULL이 되기 때문에 이것은 무한 재귀(infinite recursion)로 빠지지는 않는다.

Note   CWnd::PostNcDestroy은 일반적으로 윈도우 파괴의 일부로서, WM_NCDESTORY 메시지가 처리된 후에 호출된다, 그리고 HWND C++ 윈도우 객체는 더 이상 붙여지지(attached) 않는다. CWnd::PostNcDestory 는 실패(failure)가 일어나면 대부분의 Create 호출의 구현 내에서 또한 호출될 것이다. (자동 Cleanup 규칙에 대해서는 아래를 보라)

 

Auto Cleanup Classes

아래 클래스는 auto-cleanup을 위해 디자인되지 않았다. 일반적으로 다른 C++ 객체 내에 내장되거나 스택 상에 존재한다:

  • All standard Windows controls (CStatic, CEdit, CListBox, and so on).
  • Any child windows derived directly from CWnd (for example, custom controls).
  • Splitter windows (CSplitterWnd).
  • Default control bars (classes derived from CControlBar, see Technical Note 31 for enabling auto-delete for control bar objects).
  • Dialogs (CDialog) designed for modal dialogs on the stack frame.
  • All the standard dialogs except CFindReplaceDialog.
  • The default dialogs created by ClassWizard.

아래 클래스는 auto-cleanup을 위해 디자인되어 있다. 일반적으로 힙에 할당된다.

  • Main frame windows (derived directly or indirectly from CFrameWnd).
  • View windows (derived directly or indirectly from CView).

만일 이러한 모든 규칙을 깨뜨리고 싶다면, 상속한 클래스 안에 PostNcDestory 멤버 함수를 재정의 해야만 한다. 클래스에 auto-cleanup을 추가하기 위해서는, 기본 클래스를 호출하고 그러고 나서 delete this 를 수행한다. Auto-cleanup을 클래스로부터 제거하기 위해서는, 기본 클래스내에 PostNcDestroy 함수 대신에 바로 CWnd::PonstNcDestroy 를 호출한다.

위에서 언급한 것에 대한 가장 일반적인 사용은 힙에 할당될 수 있는 modeless 다이얼로그를 생성하는 것이다.

 

When to Call 'delete'

윈도우 객체를 파괴하기 위한 권장되는 방법은 C++ 멤버 함수 또는 전역 ::DestroyWindow API 둘 중 하나, DestroyWindow를 호출하는 것이다.

MDI 자식 윈도우를 파괴하기 위해서는 전역 ::DestroyWindow API를 호출해서는 안된다. 대신에 가상 멤버 함수 CWnd::DestroyWindow 를 사용한다.

Auto-cleanup을 수행하지 않는 C++ 윈도우 객체의 경우, delete 대신에 DestroyWindow 를 사용하는 것은 CWnd::~CWnd 소멸자 내에서 VTBL 이 올바르게 상속된 클래스를 가리키고 있지 않는 DestoryWindow를 호출해야만 하는 문제를 피하게 한다. MFC의 진단 (디버그) 버전이 경고할 미묘한 버그가 이 경우에 만들어 질 수 있다.

 

Warning: calling DestroyWindow in CWnd::~CWnd

   OnDestroy or PostNcDestroy in derived class will not be called

 

Auto-cleanup을 수행하는 C++ 윈도우 객체의 경우, 반드시 DestroyWindow를 호출해야만 한다. 만약 delete 연산자를 직접적으로 사용한다면, MFC 진단 메모리 할당자(MFC diagnostic memory allocator)는 당신이 메모리를 두 번 해제하고 있다고 알려 줄 것이다 (PostNcDestroy auto-cleanup 구현 내에서 delete에 대한 첫 번째 호출뿐만 아니라 “delete this”에 의한 간접적인 호출).

 
출처 : MSDN 2003 (ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1042/vclib/html/_MFCNOTES_TN017.htm)

2008. 7. 9. 21:09

TN038: MFC/OLE IUnknown Implementation

IUnknown
OLE는 interface로서 IUnknown 으로부터 상속된 모든 클래스를 참조한다.
IUnknown "interface"는 구현이 되지 않은 추상 인터페이스만을 아래와 같이 정의하고 있다.

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Note : __stdcall 과 같은 필수 호출 규약은 생략하였음

COM은 객체들을 추적하는데 있어 참조 카운트 스키마를 사용한다. 하나의 객체는 C++ 내에서 절대로 직접적으로 참조 될 수 없다. 대신에, COM 객체는 항상 포인터를 통해서 참조된다.

소유자(Owner)가 객체를 사용을 완료했을 때, 그 객체를 해제하기 위해서는, 그 객체의 Release 멤버 함수가 호출 된다. (그와 반대로 전통적인 C++에서는 delete 연산자를 이용한다.)

참조 카운트 메커니즘은 단일 객체에 대해 다중 참조를 관리하게 해준다. AddRefRelease는 그 객체에 대한 참조 카운트를 관리한다. 객체는 자신의 참조 카운트가 0이 될 때가지 삭제되지 않는다.

아래 AddRefRelease의 간단한 구현 예이다.

ULONG CMyObj::AddRef()
{
    return ++m_dwRef;
}

ULONG CMyObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

QueryInterface는 동일한 객체에 대한 다른 인터페이스를 얻을 수 있도록 한다. 이 인터페이스들은 보통 IUnknown으로부터 상속되고 새로운 멤버 함수를 추가함으로서 부가적인 기능을 추가한다. COM 인터페이스는 인터페이스 내에 선언된 멤버 변수들을 절대로 소유하지 않으며, 모든 멤버 함수는 순수-가상 함수로 선언된다. 아래는 그 예이다.

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

만일 IUnknown 개체만을 가지고 있는 경우에 IPrintInterface 을 얻기 위해서는 IPrintInterfaceIID를 사용하여 IUnknown::QueryInterface를 호출한다. IID는 인터페이스를 식별하는 고유의 128 비트 숫자이다. 각각의 인터페이스마다 IID가 존재한다. pUnk가 IUnknown 객체의 포인터라면, IPrintInterface 를 얻어오는 코드는 다음과 같다.

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface,
    (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();  
        // release pointer obtained via QueryInterface
}

객체에 IPrintInterface와 IUnknown 인터페이스 둘다를 지원하기 위해서는 어떻게 해야 할까?
이 경우에, IPrintInterface가 IUnknown을 직접적으로 상속하고 있기 때문에 아주 단순하다. IPrintInterface를 구현함으로써, IUnknown 은 자동적으로 지원된다. 예로 :

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

AddRef와 Release의 구현은 위에서 구현된 코드와 같을 것이다. CPintObj::QueryInterface는 아래와 같다.

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

보는바와 같이, Interface Identifier(IID)가 인식되면, 포인터가 개체에 대입된다. 성공적인 QueryInterface 는 암시적인 AddRef를 초래한다는 것을 기억하라.

물론, CEditObj::Print를 또한 구현해야 한다. IPrintInterface는 직접적으로 IUnknown 인터페이스로부터 상속되었기 때문에 간단하다. 그러나 만약 IUnknown 인터페이스로부터 상속된 두개의 다른 인터페이스를 지원하길 원한다면, 어떨지 고려해보자.

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

C++ 다중 상속을 이용하는 것과 같이 IEditInterface와 IPrintInterface 모두를 지원하는 클래스를 구현하는 방법은 매우 많다. 이 문서는 이것을 구현하기 위한 중첩클래스(Nested class)를 사용하는 것에 집중할 것이다.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

전체 구현은 아래와 같다.

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef()
{
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CEditObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(
    REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

ULONG CEditPrintObj::CPrintObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CPrintObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(
    REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

대부분의 IUnknown 구현은 CEditPrintObj::CEditObj 와 CEditPrintObj::CPrintObj내에 코드를 중첩하는 것보다 CEditPrintObj 클래스에 위치시키는 것을 기억하라. 이것은 코드량을 줄여주고 버그를 피할 수 있게 한다. 여기서 핵심은 IUnknown 인터페이스로부터 그 객체가 지원하는 모든 인터페이스를 가져오게 하기 위해 QueryInterface를 호출하는 것이 가능하다는 것이다, 그리고 그러한 각각의 인터페이스로부터도 모든 인터페이스를 가져오는 것 또한 가능하다. 이것은 각각의 인터페이스로터 이용가능한 모든 QueryInerface 함수는 똑같이 동작한다는 것을 의미한다. 이러한 내장 객체들이 외부객체(Outer Object)에 있는 구현을 호출하기 위해서는, Back-Pointer가 사용된다(m_pParent). m_pParent 포인터는 CEditPrintObj 생성자에서 초기화된다.

이제 CEditPrintObj::CPrintObj::PrintObject와 CEditPrintObj::CEditObj::EditObject를 구현해야 한다. 한가지 기능을 추가하기 위해 약간의 코드가 더해졌다. 운이 좋게도, 인터페이스들이 한개의 멤버 함수만을 가지고 있는 경우는 매우 일반적이지 않으며 이러한 경우, EditObject와 PrintObject는 보통 한개의 인터페이스로 합쳐질 것이다.

이러한 단순한 시나리오의 경우에 해당하는 많은 설명과 코드가 있다. MFC/OLE 클래스는 더 심플한 대안을 제공한다. MFC 구현은 윈도우 메시지가 메시지 맵에 래핑되는것과 유사한 테크닉을 사용한다. 이러한 기능은 Interface Map이라 불리우고 다음 섹션에서 논의된다.

MFC Interface Maps

MFC/OLE 는 개념(concept)과 수행(execution)에 있어 MFC의 메시지 맵과 유사한 인터페이스 맵과 디스패치 맵의 구현을 제공한다. MFC 인터페이스 맵의 핵심 기능은 아래와 같다.

* CCmdTarget 클래스내에 구현된 IUnknown의 표준 구현
* AddRef 와 Release에 의해 수정되는 참조 카운트의 관리
* QueryInterface의 데이터 주도 구현(Data driven implementation)

게다가, 인터페이스 맵은 아래의 향상된 기능을 지원한다.

* 집합체가 될 수 있는 COM 개체(aggregatable COM ojbect)를 생성하는 것을 지원
* COM 객체의 구현 안에 집합체(aggregate object)를 사용하는 것을 지원
* 구현은 후킹가능하고 확장할 수 있다.

집합(aggregation)에 대한 자세한 정보는, OLE Programmer's Reference를 보라.

MFC의 인터페이스 맵 지원은 CCmdTarget 클래스 내에 내장되어 있다. CCmdTarget은 참조 카운트 뿐만 아니라 IUnknown 구현과 연결된 모든 멤버함수를 가진다 (예를 들어 참조 카운트는 CCmdTarget내에 있다). OLE COM을 지원하는 클래스를 생성하기 위해서는, CCmdTarget을 상속하고 의도하는 인터페이스를 구현하기 위한 다양한 매크로와 CCmdTarget의 멤버 함수를 사용한다. MFC의 구현은 위에서 설명한 예제와 같이 각각의 인터페이스 구현을 정의하기 위해 중첩 클래스를 사용한다. IUnknown의 표준 구현 뿐만 아니라 몇가지 반복적인 코드를 제거하는 여러 매크로를 이용하여 더 쉽게 구현할 수 있게 해준다

Inteface Map Basic

MFC 인터페이스 맵을 사용하는 클래스를 구현하기

1. 직접 혹은 간접적으로 CCmdTarget을 상속한다.
2. 클래스 정의에 DECLARE_INTERFACE_MAP 함수를 사용한다.
3. 지원하고자 하는 각각의 인터페이스에 대해, 클래스 정의 내에 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로를 사용한다.
4. 구현 파일 내에, 클래싀 인터페이스 맵을 정의하기 위해 BEGTIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로를 사용한다.
5. 지원된 각각의 IID에 대해, IID를 클래스의 특정 부분에 맵핑하기 위해 BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로 사이에 INTERFACE_PART 매크로를 사용한다.
6. 지원하는 인터페이스를 나타내는 각각의 중첩 클래스를 구현한다.
7. 부모, CCmdTarget을 상속한 객체에 접근하기 위해 METHOD_PROLOGUE를 사용한다.
8. AddRef, Release, 그리고 QueryInterface는 이와 같은 함수의 CCmdTaret 구현 (ExternalAddRef, ExternalRelease, 그리고 ExternalQueryInterface)에 위임할 수 있다.

위의 CPrintEditObj 예제는 아래와 같이 구현될 수 있다.

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

위의 선언은 CCmdTarget을 상속한 클래스를 생성한다. DECLARE_INTERFACE_MAP 매크로는 프레임워크에게 이 클래스는 커스텀 인터페이스 맵을 가질것이다라는 것을 알려준다. 게다가, BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로는 CEditObj와 CPrintObj로 이름지어진 중첩 클래스를 정의한다. (X는 "C"로 시작하는 전역 클래스와 "I"로 시작하는 인터페이스 클래스와 중첩 클래스를 구분하기 위해서만 사용된다.) 이 클래스들에 대한 두개의 중첩 멤버(nested member)가 생성된다 : m_CEditObj 와 m_CPrintObj. 매크로는 자동적으로 AddRef, Release, 그리고 QueryInterface 함수를 선언한다. 그리하여 이 인터페이스 고유의 함수들만 선언한다. (OLE 매크로 STDMETHOD는 대상 플랫폼에 적합한 _stdcall 과 virtual 키워드를 제공하기 위해 사용된다.)

이 클래스의 경우 인터페이스를 정의하기 위해서는

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

이것은 각각 m_CPrintObj와 IID_IPrintInterface를, m_CEditObj와 IID_IEditInterface를 연결한다. QueryInterface의 CCmdTarget 구현(CCmdTarget::ExternalQueryInterface)는 m_CPrintObj와 m_CEditObj에 대한 포인터를 반환하기 위해 이 맵을 사용한다. IID_IUnknown에 대한 엔트리를 포함하는 것은 불필요하다. 프레임워크는 IID_IUnknown이 요구되었을 때 맵에 있는 첫번째 인터페이스(이 경우에, m_CPrintObj)를 사용할 것이다.

비록 BEGIN_INTERFACE_PART 매크로가 자동적으로 AddRef, Release 그리고 QueryInterface 함수를 선언했다고 할지라도, 여전히 그것들을 구현해야할 필요가 있다.

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

CEditPrintObj::CPrintObj에 대한 구현은 위의 CEditPrintObj::CEditObj의 정의와 비슷할 것이다. 비록 이러한 함수들을 자동적으로 생성하기 위해 사용될 수 있는 매크로를 만들수 있을지라도, 매크로가 한줄 이상의 코드를 생성할 때 브레이크 포인터를 걸기가 어려워 질 것이다. 이러한 이유로, 이 코드는 수동으로 확장된다.

메시지 맵의 프레임워크 구현을 사용함으로써, 다음과 같은 작업을 할 필요가 없다.

* QueryInterface 구현
* AddRef와 Release 구현
* 두 인터페이스에 대한 각각의 built-in 메소드 선언

게다가, 프레임워크는 내부적으로 메시지 맵을 사용한다. 이것은 이미 특정 인터페이스를 지원하는 그리고 그 인터페이스에 대한 대체(replacement) 또는 추가(addition)를 제공하는 COleServerDoc와 같은 프레임워크 클래스를 얻어 올 수 있게 한다. 이것은 프레임워크가 베이스 클래스로부터 인터페이스 맵을 상속하는 것을 전적으로 지원한다는 사실에서 가능해진다 - 즉 BEGIN_INTERFACE_MAP이 두번째 파라미터로 베이스 클래스의 이름을 가지는 이유이다.

Aggregation and Interface Maps

stand-along COM 개체를 지원하는 것에 외에, MFC는 또한 집합(aggregation)을 지원한다. 집합은 여기서 다루기에는 너무 복잡하다. 집합에 대한 자세한 정보는 OLE Programmer's Reference를 참조해라. 이 문서는 간단하게 프레임워크와 인터페이스 맵 내에 집합을 구축하는 것에 대해 설명할 것이다.

집합을 사용하는 방법에는 두가지가 있다.

(1) 집합을 지원하는 COM 객체를 사용하는 것
(2) 다른 객체에 의해 집합될 수 있는(can be aggregated) 객체를 구현하는 것

이러한 기능은 "집합체 사용하기(Using an aggregate object)"와 "집합될 수 있는 객체 만들기(Making an object aggregatable)" 로 다루어 진다. MFC는 두가지 모두 지원한다.

Using an aggregate object

집합체를 사용하기 위해서는, 그 집합체를 QueryInterface 매커니즘 내에 묶는 몇가지 방법이 필요한다. 다른 말로 하면, 집합체는 객체의 고유한 일부(native part)인 것처럼 동작해야 한다. 그래서 이것을 어떻게 MFC의 인터페이스 맵 매커니즘으로 묶을수 있을까? INTERFACE_PART 매크로외에, CCmdTarget 을 상속한 클래스의 일부로 집합체를 선언할 수 있다. 그렇게 하기 위해, INTERFACE_AGGREGATE 매크로가 사용된다. 이것은 멤버 변수( IUnknown이나 상속클래스의 포인터이어야 한다)를 지정할 수 있게 한다 그리고 이것은 인터페이스 맵 매커니즘 안으로 통합된다. CCmdTarget::ExternalQueryInterface가 호출될 때 포인터가 NULL이 아니고,  그리고 요청된 IID가 CCmdTarget 개체 자신에 의해 지원되는 고유의 IID가 아니라면 프레임워크는 자동적으로 집합체의 QueryInterface 멤버 함수를 호출할 것이다, .

INTERFACE_AGGREGATE 매크로 사용하기

1. 집합체에 대한 포인터를 포함할 멤버변수(IUnknown 포인터)를 선언한다
2. 인터페이스 맵내에 INTERFACE_AGGREGATE를 포함한다. 이것은 멤버변수를 이름으로 참조한다.
3. 특정 지점(보통 CCmdTarget::OnCreateAggreagets 내에서), 멤버 변수는 NULL이 아닌 다른것으로 초기화 한다.

아래는 예:

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

m_lpAggrInnter 은 생성자에서 NULL로 초기화 된다. 프레임워크는 QueryInterface의 디폴트 구현안에서 NULL 멤버 변수는 무시할 것이다. OnCreateAggregates는 집합체를 실질적으로 생성하기 위한 좋은 위치이다. 만일 COleObjectFactory의 MFC 구현 외부에서 객체를 생성하고 있다면, 명시적으로 그것을 호출해야만 할 것이다. CCmdTarget::OnCreateAggregates내에서 집합체를 생성하는 이유뿐만 아니라 CCmdTarget::GetControllingUnknown 의 사용은 집합체를 생성하는 것에 대해 논의될 때 명백해 질 것이다.

이 기법은 객체에 집합체가 지원하는 모든 인터페이스를  고유의 인터페이스에 추가해 줄 것이다. 집합체가 지원하는 인터페이스 일부만을 원한다면, CCmdTarget::GetInterfaceHook 을 재정의 할 수 있다. 이것은 QueryInterface와 유사하게, 아주 낮은 레벨의 hookability를 제공한다. 보통, 집합체가 지원하는 모든 인터페이스를 원할 것이다.

Making an object Implemtation Aggregatable

집합될 수 있는 객체의 경우, AddRef, Release, 그리고 QueryInterface의 구현은 "Controlling Unknown"에 위임해야만 한다. 다른 말로 하면, 객체가 다른 객체의 일부가 되기 위해서는, IUnknown을 상속한 다른 객체에 AddRef, Release, 그리고 QueryInterface를 위임해야만 한다. 이 "controlling unknown"는 객체가 생성될 때 그 객체에 제공된다, 즉, COleObjectFactory의 구현에 제공된다. 이것을 구현하는 것은 약간의 오버헤드를 수반하며 어떤 경우에는 바람직하지 않을수 있다. 그래서 MFC는 이것을 선택할 수 있게 만들었다. 객체가 집합가능하게 만들기 위해서는, 객체의 생성자에서 CCmdTarget::EnableAggregation을 호출해야 한다.

그 객체가 또한 집합체를 사용한다면, 그 집합체에다가 올바른 "controlling unknown"을 전달해야만 한다. 보통 이 IUnknown 포인터는 집합체가 생성될 때 객체에 전달된다. 예를 들면, pUnkOuter 파라미터는 CoCreateInstance로 생성된 객체들을 위한 "controlling unknown"이다. 올바른 "controlling unknown" 포인터는 CCmdTarget::GetControllingUnknown을 호출함으로써 얻을 수 있다. 그러나 그 함수에 의해 반환되는 값은 생성자에서는 유효하지 않다. 이 이유로, GetControllingUnknown 의 반환값이 신뢰할 수 있는, 비록 COleObjectFactory 구현으로부터 생성된 것조차, CCmdTarget::OnCreateAggreates 를 재정의하여 집합체를 생성하도록 권유된다.

인위적인 참조 카운트를 증가시키거나 해제할때 객체가 올바른 참조 카운트를 조절하는 것 또한 매우 중요하다. 이것을 보장하기 위해, InternalReleaseInternalAddRef 대신에 ExternalAddRefExternalRelease를 항상 호출해라. 집합체를 지원하는 클래스에서 InternalRelease 또는 InternalAddRef를 호출하는 것은 거의 볼 수 없다.



참고 : MSDN 2003 Technical Note #38

2008. 7. 5. 23:22

Windows (창) 개체

MSDN 2003 에서 Windows 창 개체 관련 정리



키워드 정리:
CFrameWnd; CMDIFrameWnd; CMDIChildWnd; Registerclass; AfxRegisterWndClass; OnClose; DestoryWindow; OnNcDestory; PostNcDestory; Detach; CWindowDC; CMetaFileDC; CClientDC; CPen; CBrush; CFont; CBitmatp; CPalette; CRgn; CImage; DeviceContext; PreCreateWindow; FWS_ADDTOTITLE; CDocTemplate; MDICLIENT: OnActivateView; ON_UPDATE_COMMAND_UI; DragAcceptFiles; LoadStdProfileSettings; DDE 응답; RegisterShellFileTypes; EnableShellOpen;
2008. 7. 5. 23:17

CObject 관련 정리

CObject 관련한 MSDN 2003 문서 정리



키워드:
Serialization; DECLARE_DYNAMIC; DECLARE_DYNCREATE; DECLARE_SERIAL; IsKindOf; CRuntimeClass; DumpallObjectsSince; CDumpContext; AssertValid; ASSERT_VALID;
2008. 7. 2. 13:34

Tip: Detecting a HMODULE/HINSTANCE Handle Within the Module You're Running In

2008. 6. 21. 01:47

IDL 파일, TypeLibrary, 그리고 Automation

Automation(자동화)으로 생성하지 않았던 Project에 자동화 기능을 추가함에 있어
몇가지 알아야할 사항이 있다.

-------------------------------------------------------------------------------
[ uuid(E4029F3C-B8DE-45A0-A190-2C5A8B240487), version(1.0) ]
library TypeLibTest
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");

 //  CTypeLibTestDoc의 기본 디스패치 인터페이스
 
 [ uuid(DBF2D3CB-9991-4D7F-954C-2DDF53B597DF) ]
 dispinterface ITypeLibTest
 {
  properties:
   
  methods:
  [id(1), helpstring("메서드 Test")] LONG Test(LONG lTemp, LONG* lpTemp);
 };

 //  CTypeLibTestDoc의 클래스 정보
 
 [ uuid(41BB86A1-7049-4C43-92DC-07C3E5629C6C) ]
 coclass CTypeLibTestDoc
 {
  [default] dispinterface ITypeLibTest;
 };
};
-------------------------------------------------------------------------------

위 기본적인 IDL 파일에는 세가지 uuid가 있다.

TypeLibrary ID : 타입라이브러리 아이디로 CWinApp를 상속하는 클래스에서 InitInstance 부분에서
등록된다.

DispInterface ID : Coclass로 정의된 CTypeLibTestDoc의 인터페이스를 정의하는 ID로 CTypeLibTestDoc
내에서 Interface로 정의된다. 이 dispinterface에 정의된 메쏘드 및 속성은 Coclass에서 정의되어야 한다.
(IDL 파일에 정의하지 않고, Coclass에만 정의해도 되는 듯 하나 나중에 코드 관리가 어려워진다.)

그리고 마지막 coclassID : Inteface ID와 연결된 Coclass의 uuid로 TypeLibrary와 마찬가지로
CWinApp를 상속하는 클래스의 InitIntance 부분에서 등록된다.

이러한 TypeLibrary 를 기존 Project에 추가하기 위해서는 몇가지 추가작업이 더 필요하다.

그 중에서,

resource를 직접 편집하여 TypeLibrary를 등록하는 부분이 필요하다.

그리고

프로젝트 속성에서 Post-Build Event에서 $(TargetPath) /register를 등록하면
컴파일시 등록까지 할 수 있도록 설정할 수 있다.

이에 대한 좀더 자세한 내용은 CodeGuru에서 퍼온 다른 글을 참고하기 바람.

( Adding automation to MFC applications )


2008. 6. 18. 23:35

Adding automation to MFC applications

Table of contents

Introduction

Automation support in MFC is a great example of why some people don't like MFC: it works great as long as you follow the pre-made examples and use MFC exactly like it was designed, but as soon as you get even a little bit off the beaten path, you're on your own. There are some tutorials in MSDN and articles online that will teach you how to use the 'New project' wizard to generate an automation-enabled application, and some of those even explain some of the working of the code the wizard generates - but unfortunately, that's not always how things go in the Big Bad Real World. Sometimes, you inherit an application that is so old that the initial skeleton was probably generated with the wizard of Visual Studio 4; that has been expanded and hacked on by dozens of people, each with their own understanding (or lack thereof) of the MFC doc/view architecture; that has mutated from an afternoon-hack-proof-of-concept to a piece of software that is critical for the survival of the company; that has had so much surgery that its innards have started to resemble (metaphorically speaking) the leftovers of the struggle that a 3-year old had with a big bowl of spaghetti bolognese. Believe me - not pretty.

So, what can you do when you are asked to add automation support to such an application? Starting over with a fresh application and moving the functionality of the old code back in is not an option. And even if it was, you would find out that the wizard-generated code is built for the idea that the central element of your application's object model is the Document. In some MFC applications, that is simply not the case any more. The only recourse is to figure out exactly what it is that makes automation tick in an MFC application - and add those pieces into the old application.

The obvious way to start is to take an application that is wizard-generated with automation support, and compare it to an application that doesn't have automation support. Add those differences to the old application and you're all set, right? Well, that's partially true - as long as the object model that you want to implement is centered around a Document, as I said before. If you have an object model that doesn't work this way, and you wonder how to get automation to work, read on.

Automation

"But wait", you say, "what is this 'automation' thing you've been talking about?". Ah yes, esteemed reader, excuse me for not elaborating on this earlier. Automation is, simply put, a way for your application to interact with Visual Basic, VBScript, JavaScript and, indeed, any other language that can work with COM objects. Which reveals how automation is implemented: as a COM interface. Which also reveals that in order for your application to support automation, it will have to support COM. It would be outside the scope of this article to give a complete primer on COM (and indeed for a complete primer on automation as well); I refer to the section 'References' for further reading for those who do not have at least some notion of what COM is and how it works. I will suppose from now on that you know what COM and automation are; that you know what an 'object model' is, and that you have one (or at least have an idea of what it will look like) for the application you wish to automate; and that you know about MFC's Doc/View architecture. You don't have to know a whole lot about that last one though, since the biggest part of this article is about how to *not* use it.

Although I said that you can use automation from any language, there is a little nuance to to be made to that statement. For scripting languages to access a COM object, they would need to have a description of the interface of the object. That description can be read from a type library (.tlb file) but not all scripting languages have access to that. Therefore, there is a way to query an object for the methods it provides: implementing the IDispatch interface. But for languages who *can* read a tlb, there should be a way to do that, too. The solution is simple: a dual interface. Again, for details on the theory on dual interfaces, see the section 'References'; I'm bringing it up here because I will assume that you want your objects to have a dual interface.

The Problem

By now, being the interested reader you are, you've looked up 'automation' in MSDN, and you've seen a wealth of articles that explain the concept of automation in MFC and how to use the class wizard to add automation-enabled classes. So, you're probably wondering "Why am I reading this - I can get this same information from MSDN?". You see, the problem is that these articles are based on the following (implicit) assumptions:

  • You generated your application skeleton with the 'enable automation' option on.
  • Your object model revolves around a Document object.
  • You want a dispinterface, not a dual interface.

No documentation gives a step-by-step overview of what you have to do to automate an existing application, detailing what everything does, and the considerations to take into account. That is what this article tries to remedy.

The Solution

This article, of course! Below, I will present the steps to take to make your application scriptable from every COM-enabled language. It turns out that the changes you have to make can be divided in these groups:

  • Defining your object model in IDL.
  • Implementing your object model as CCmdTarget-derived classes.
  • The general initialization. Initializing COM, registering your objects with the system.

I've also included a small section on how to start your automated application from C++ and VBScript for writing small test clients.

I will present all steps in a tutorial-style way, and explain in the process what those steps do and what they are for. The included sample project contains a vanilla MFC multiple-document application, except for the minimal changes necessary to get automation to work. Those changes are clearly marked in the code.

To keep the code in this article to a minimum, I will only add one COM object. The name of the application that I'll automate is 'MyCoolApp', and the COM object that will be accessible from the outside will be a generic object named 'Application' with one method: Show(), which will, unsurprisingly, show the window. I find this to be a good first method to implement since automated applications will not be shown by default when they are instantiated from an automation client. When you are developing your application, you can call the Show() method, and when your application shows up, you know that the automation works.

What to do in your application

IDL file

The first step is to add a file that has the description of the COM objects. In the Solution Explorer, right-click on your application and select 'Add' -> 'Add New Item'. Choose 'MIDL file' and type in a file name, like 'mycoolapp.idl'. This will add the file to your project and open it. As mentioned before, we'll make an object named 'application' with one method: Show(). The IDL is mostly boilerplate code. The only thing you must remember if you choose to copy this sample is to change the GUIDs. A GUID is a globally unique identifier for your interfaces; if you copy the GUIDs below, they may conflict with another application that also uses them! This may or may not pose a problem in the future, but to be sure, change them! It's easy to generate a GUID: in Visual Studio, click 'Tools', 'Create GUID', and there you go. Click the 'Copy' button to copy the newly generated GUID to the clipboard.

That being said, here is the code:

#include "olectl.h"
[ uuid(526E36B7-F346-4790-B741-75D9E5B96F4B), version(1.0) ]
// This is usually the name of your application.
library mycoolapp
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [ uuid(6263C698-9393-4377-A6CC-4CB63A6A567A),
      oleautomation,
      dual
    ]
    interface IApplication : IDispatch
    {
        [id(1), helpstring("method Show")] HRESULT Show (void);
    };

    [ uuid(9ACC7108-9C10-4A49-A506-0720E0AACE32) ]
    coclass Application
    {
        [default] interface IApplication;
    };
};

Some points of attention:

  • you need to have 3 different GUIDs
  • you need to have a coclass which will be named after the object you're modeling
  • you need an interface definition which is (by convention) named 'I' + the name of the object you're modeling.

Automated class header

Now, we'll add a class that represents the object that will be automated (that is, that can be accessed by automation clients). This is, your client's entry point into your application (or one of the entry points if you implement multiple interfaces). Adding it is fairly straightforward, I'll walk you through it:

Add a class and name it after your interface, 'Application' in this case. I'll stick with the MFC naming and call it CApplication for now (although I hate the C prefix personally - see References). You can do this with the wizard or add a class by hand. Derive it from CCmdTarget. Add in #include statement (I'll explain later where this comes from):

#include "mycoolapp_h.h"

Add the following functions:

virtual void OnFinalRelease()
{
  CCmdTarget::OnFinalRelease();
}

HRESULT Show()
{
  AfxGetApp()->m_pMainWnd->ShowWindow(TRUE);
  return TRUE;
}

This is the place where you would place any cleanup code for your object. We have a simple example, there is no cleanup needed; we just call the parent.

Add the following macros in the header:

  DECLARE_DYNCREATE(CApplication)
  DECLARE_MESSAGE_MAP()
  DECLARE_OLECREATE(CApplication)
  DECLARE_DISPATCH_MAP()
  DECLARE_INTERFACE_MAP()

These macros set up some members and functions that are needed to register the class with the system and to route calls to the COM object to your (C++) object.

Add an enum:

enum 
{
  dispidShow = 1L
};

In this enum, you need to have an entry for every function you add to your interface. In this example, there is only one, Show(), so there is only one entry in the enum. You can make up your own names here - later, we'll see where those names are referenced.

Next, declare an interface map and put in entries for every function you want your automation object to have. This sounds complicated but it's just a simple macro and some cut and paste:

BEGIN_INTERFACE_PART(LocalClass, IApplication)
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
        UINT itinfo,
        LCID lcid,
        ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
        REFIID riid,
        OLECHAR FAR* FAR* rgszNames,
        UINT cNames,
        LCID lcid,
        DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
        DISPID dispidMember,
        REFIID riid,
        LCID lcid,
        WORD wFlags,
        DISPPARAMS FAR* pdispparams,
        VARIANT FAR* pvarResult,
        EXCEPINFO FAR* pexcepinfo,
        UINT FAR* puArgErr);
    STDMETHOD(Show)(THIS);
END_INTERFACE_PART(LocalClass)

"But Roel", you'll ask, "what the hell is all of that?", and I'll tell you: GetTypeInfoCount(), GetTypeInfo(), GetIDsOfNames() and Invoke() form the implementation of IDispatch, and Show() is the implementation of the method in our interface. If you want to know exactly what the first four do, I'll refer you to MSDN, but believe me, you're better off copying and pasting - that's what I did. For those wondering if all that standard stuff cannot be wrapped in a macro: see at the end of this article. The MFC ACDual example provides such macros.

Automated class implementation

Now, it's time for the implementation of the class that we just wrote a header for. Start with the MFC macro to implement dyncreate:

IMPLEMENT_DYNCREATE(CApplication, CCmdTarget)

Next, implement the constructor and the destructor, and add the following statements to them besides your own code:

  • EnableAutomation() and ::AfxOleLockApp() in the constructor
  • ::AfxOleUnlockApp() in the destructor

Then implement the message map:

BEGIN_MESSAGE_MAP(CApplication, CCmdTarget)
END_MESSAGE_MAP()

Now comes the interesting part: the dispatch map. A dispatch map looks a lot like a message map, in that it has a BEGIN_DISPATCH_MAP part, entries for every function that your interface has, and ends with a END_DISPATCH_MAP macro. This example will show the dispatch map for our simple interface with only one method:

BEGIN_DISPATCH_MAP(CApplication, CCmdTarget)
  DISP_FUNCTION_ID(CApplication, "Show", dispidShow, Show, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

The arguments to the BEGIN_DISPATCH_MAP macro are the same as those to the message map: the name of the class and the name of the class it is derived from. The arguments to DISP_FUNCTION_ID are more interesting. The first one is (again) the name of the class you're implementing. The second one is a short string description of your method. It will generally be the same as the name you use in your class. The third one is a unique number that we've set up in an enum in the class declaration. This number has to be unique, that's why an enum is convenient here (and of course, also because it allows you to work with descriptive names instead of numbers). The next argument is the name of the method of the class in which the interface method is implemented. As I mentioned, this is usually the same as the second argument (except for the quotes, that is).

The fifth argument then is the return type of the method. This is not as you would expect VT_HRESULT but rather VT_NONE for all methods. It would take us too far to explain here why that is exactly, but in a few words: all methods of a dispinterface return HRESULTs, as reflected by the return type of the Show() method. That HRESULT, however, is used for error reporting, not to actually return a value. As such, when you call Show() from, for example, Visual Basic, you don't get to see that HRESULT at all - if an error would occur, VB's standard error handling mechanism would kick in. Therefore, you should declare all functions in your dispatch map as returning VT_NONE. The last argument to the DISP_FUNCTION_ID macro then is a space-separated list of the arguments that the function takes. Since ours doesn't take any arguments at all, we put in VTS_NONE. If it would have taken a string and an integer, we would have used "VTS_BSTR VTS_I2", for example. See MSDN for a full list of constants that are allowed here.

So far, for the dispatch map. On to the next map: the interface map. This is where the actual 'connection' between the COM object (your automated application) and your C++ object is made. Again, let's start with an example:

BEGIN_INTERFACE_MAP(CApplication, CCmdTarget)
  INTERFACE_PART(CApplication, IID_IApplication, LocalClass)
END_INTERFACE_MAP()

The first argument of the INTERFACE_PART macro is once again the name of the class we're implementing; the second argument is the interface name (most likely 'IID_' + the name of your interface), and the third argument is the first argument to the BEGIN_INTERFACE_PART macro (which we put in the declaration). Easy as that.

One more 'standard' implementation has to be done, using the IMPLEMENT_OLECREATE macro. Sample:

IMPLEMENT_OLECREATE(CApplication, "mycoolapp.Application", 
  0x9acc7108, 0x9c10, 0x4a49, 0xa5, 0x6, 0x7, 0x20, 0xe0, 0xaa, 0xce, 0x32)

The first argument is the name of the coclass as you specified it in the IDL file. The second name is the textual description by which your object will be known to, for example, Visual Basic; if you're not sure what to choose, make it '<library name>' + '.' + '<interface name>'. Look back at the IDL example and you'll see what I mean. The third parameter is very important: it's the GUID of the coclass you declared in the IDL file.

We're almost done here: add the implementation of the IDispatch members to the class:

Collapse
STDMETHODIMP_(ULONG) CApplication::XLocalClass::AddRef()
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CApplication::XLocalClass::Release()
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalRelease();
}
STDMETHODIMP CApplication::XLocalClass::QueryInterface(
  REFIID iid, LPVOID* ppvObj)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CApplication::XLocalClass::GetTypeInfoCount(
    UINT FAR* pctinfo)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetTypeInfoCount(pctinfo);
}
STDMETHODIMP CApplication::XLocalClass::GetTypeInfo(
  UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}
STDMETHODIMP CApplication::XLocalClass::GetIDsOfNames(
  REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames,
  LCID lcid, DISPID FAR* rgdispid) 
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, 
    lcid, rgdispid);
}
STDMETHODIMP CApplication::XLocalClass::Invoke(
  DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
  DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,
  EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->Invoke(dispidMember, riid, lcid,
    wFlags, pdispparams, pvarResult,
    pexcepinfo, puArgErr);
}

Again, this is boilerplate code that can be simplified a lot using a few macros that are provided with the ACDual MSDN example. See the section near the end about that.

And finally, add the implementation of the Show() function. We just call the owner class' Show() method:

STDMETHODIMP CApplication::XLocalClass::ShowWindow()
{
    METHOD_PROLOGUE(CApplication, LocalClass)
    pThis->ShowWindow();
    return TRUE;
}

Changes to OnInitInstance

We also need to make some changes to OnInitInstance() to setup and register the COM objects with the system (in the registry). The first one is to call:

COleTemplateServer::RegisterAll();

The project wizard will put this call right after the ini files are loaded with LoadStdProfileSettings(); I suggest putting it there as well. It doesn't really matter, but it will look more familiar if/when you'd compare it to a wizard-generated application.

Then, scroll down a few lines until you see a call to ParseCommandLine(). Right after that, add the following code:

if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
{
    return TRUE;
} else if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister){
    AfxOleUnregisterTypeLib(LIBID_mycoolapp);
} else {
    COleObjectFactory::UpdateRegistryAll();
    AfxOleRegisterTypeLib(AfxGetInstanceHandle(), LIBID_mycoolapp);
}

When your application is started from automation, it will be run with the parameters /Embedding or /Automation. In that case, we don't want to show the main window, so we return immediately. This means, of course, if you do want to show the main window, that you should call pMainFrame->ShowWindow(TRUE); before returning. The second 'if' tests whether your application was run with the /Unregserver or /Unregister switches. If it is, we remove all references to our application from the registry (actually, we let MFC do this for us). Finally, if we detect none of these switches, we let MFC put references to our COM objects into the registry. Yes, this means that every time your application is run, it is re-registered; this is to ensure that the registry is always consistent with the latest location of the executable.

That's all there is to it as far as the changes to OnInitInstance go. If you would generate an automation-enabled application with the wizard, you'd see more code here, more specifically a few calls to functions of an m_server object of type ColeTemplateServer. They are for the case that you want your Document to be automation-enabled; it is associated with a doctemplate so that a new document can be created when the automation server is started. Since this code is generated automatically with the wizard, I won't describe it here.

Resource

One final thing to do is to embed the type library in the resources section of your application. Go to the resource view, right-click on the resource file name, and choose 'resource includes'. At the bottom of the lower box, insert:

1 TYPELIB "<appname>.tlb"

where <appname> is the name of your application, of course (in our case, it would have been 'MyCoolApp.tlb'). This way, the resource compiler will embed the type library into the executable so that you don't have to distribute it separately.

Post-build step

This step isn't strictly necessary but will make your life easier: register your application every time it is build. It is simple: in the Properties of your project, add the following line to your post-build event:

"$(TargetPath)" /RegServer

Compiling your project

To get your project to compile, you need to link in a file that was generated by the MIDL compiler: mycoolapp_i.c. To do this, right-click on 'MyCoolApp' in the Solution Explorer, choose 'Add' -> 'Add Existing Item...', and select mycoolapp_i.c.

How to use your automation object

Of course, you want to test your new automated application. Remember that there are two ways to get to your objects: through 'regular' COM (or 'early binding', only for those environments that support it, like C++) and through IDispatch ('late binding', for scripting languages like VBScript). I'll demonstrate both methods here.

From C++

Let's start with a very simple C++ application. Make a simple dialog-based application with the class wizard, be it an MFC or an ATL/WTL application. Just make sure that :CoInitialize() and CoUninitialize() are called somewhere (that is done automatically in ATL applications). Put a button on the dialog somewhere, wire it up, and put the following in the message handler for the BN_CLICKED handler:

HRESULT hr;
hr = ::CoCreateInstance(CLSID_Application, NULL, 
     CLSCTX_LOCAL_SERVER, IID_IApplication, (void**)&m_IApplication);

if(SUCCEEDED(hr)) {
  if (m_IApplication) {
    m_IApplication->Show ();
  }
}

In the header for the dialog, declare a member like this:

IApplication* m_IApplication;

Now, all you need to do is include the file where IApplication is declared. It is automatically generated from the IDL file by the midl.exe IDL compiler, so you'll have to either copy it to the directory of your test application (which you'll have to do every time you change the IDL file) or construct a #include statement with a relative path in it. The file is named (by default) <coclassname>_h.h, so in our example, it is Application_h.h. When you go looking for this file, you'll notice another file: Application_i.c. This file contains the implementation of the interface and is needed by the linker. So, again, you can copy it, or add it to your project directly.

Now, build your application, click the button you've made, and voila - there is your application! Notice that if you take out the m_IApplication->Show(); line, you can still see your application being started by looking for it in the process list in the Windows Task Manager.

From VBScript

It's easier to start your application from VBScript. In three lines:

Dim obj
Set obj = CreateObject("mycoolapp.Application ")
obj.Show ()

Notice that the argument that we pass to CreateObject is the name we passed in the IMPLEMENT_OLECREATE macro. Also, note that if you leave out the last line in one script, create a new script with all three lines, and run first the two-line script and then the three-line one, your application will be started only once! That means that if you call CreateObject() when an instance of your application is already running, a reference to that running instance will be returned.

Using the macros from the ACDual example

A lot of the code in this article is very standard: it will be exactly the same in every application you will ever automate. To avoid that, you can use the code in a header file that is provided with the ACDual example: mfcdual.h. The example itself can be found on MSDN; apart from some error handling code (which we didn't discuss in this article), it contains these macros:

  • BEGIN_DUAL_INTERFACE_PART: use this instead of BEGIN_INTERFACE_PART; it takes care of declaring the implementation of the IDispatch interface.
  • DELEGATE_DUAL_INTERFACE: add this one to your .cpp file to implement the functions that were declared with BEGIN_DUAL_INTERFACE_PART.

I strongly suggest you look at the way these macros are used in the ACDual example, and that you look at their contents; it will help you understand your application better when something goes wrong (notice that I didn't say 'if' but 'when').

References

Books on COM and automation basics:

Online resources:

History

4 Jul 2005 - updated download by Trevisan Andrea: The download code was reworked a little to include some important parts and ensure the solution file type is compatible with Microsoft Visual Studio.NET 2002 Academic.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

roel_v2



Location: Belgium Belgium

From : http://www.codeproject.com/KB/COM/mfc_autom.aspx
2008. 6. 16. 00:20

파일 Drag & Drop 간단하게 구현하기

1. 드래그앤드랍을 받을 윈도우 핸들에서, DragAcceptFiles(...) 함수를 호출한다.

2. WM_DROPFILES 이벤트를 처리한다. 아래는 샘플.

void CDragNDropTestView::OnDropFiles(HDROP hDropInfo)
{
 UINT uiRet = DragQueryFile(hDropInfo,-1,NULL, NULL);
 if ( uiRet == 1)
 {
  CString sTemp;
  DragQueryFile(hDropInfo, 0, sTemp.GetBuffer(MAX_PATH), MAX_PATH);
  SetWindowText(sTemp);
 // 오픈한 파일 패스를 에디트에 표시합니다.
 }
 else if(uiRet > 1) // 파일이 한개 이상일 경우
 {
  MessageBox("Cannot open multiple files!");
 }
 
 CEditView::OnDropFiles(hDropInfo);
}

이 코드 적용시 SDI였기 때문에 uiRet가 1 이상일 경우에는 단순히 메시지 박스만 출력했다.

아래는 샘플.

(Visual C++ 6.0으로 빌드하였으며,
   SDI형태, 파일을 한개 드랍했을 경우 드랍한 파일의 패스를 보여주고,
   여러개의 파일을 드랍한 경우에는 다중 파일을 열수 없다는 메시지를 출력한다.
)


2008. 6. 15. 23:41

VariantUse 샘플: Variant 사용 설명

VariantUse 샘플에서는 기존 데이터를 variant로 변경하고 variant를 다른 데이터 형식으로 변경하는 방법을 보여 줍니다. 대부분의 COM 개체는 variant를 함수 매개 변수로 받아들입니다. 이 샘플을 통해 표준 C 데이터 형식을 variant로 변경하는 방법을 이해할 수 있습니다.

이 샘플에서는 통화, 날짜, SAFEARRAY, 다차원 배열, 문자열, char, short 및 long 형식을 다룹니다.

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

void CVarUseDlg::OnCurrency()
{
 VARIANT myVar;
 myVar.vt = VT_CY;
 

 // put 15.15 into the currency variant.
 CURRENCY myValue;
 myValue.Hi = 0;
 myValue.Lo = (LONG)(15*10000 + 1537);
// set the value to $15.15;
 myVar.cyVal = myValue;
 
//or
 myValue.int64 = (151234);
 myVar.cyVal = myValue;
 
 
 
 //Now get the value from a float and put it into the currency.
 float myMoney = (float)5.37;
 myVar.vt = VT_R4;
 myVar.fltVal = myMoney;
 //no loss of precission.
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_CY)));
 

 //Now get the value from a String
 USES_CONVERSION;
 myVar.bstrVal = SysAllocString(L"23.4345");
 myVar.vt = VT_BSTR;
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_CY)));

 //Finally output a currency to a string.
 myVar.cyVal.int64 += myVar.cyVal.int64;
// + myVar.cyVal;
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_BSTR)));
 
}

void CVarUseDlg::OnStrings()
{
 //Create a BSTR and assign it to a Variant
 BSTR x = SysAllocString(L"Hello");
 VARIANT myVariant;
 myVariant.vt = VT_BSTR;
 myVariant.bstrVal = x;
 SysFreeString(x);

 //Create a CString and change it to a variant;
 CString myCString(_T("My String"));
 CString mySecondString;
 
 BSTR y = myCString.AllocSysString();
 myVariant.bstrVal = y;
 mySecondString = y;
 SysFreeString(y);

 
 //Create two BSTRs and add them.
 BSTR a = SysAllocString(L"One two ");
 BSTR b = SysAllocString(L"three four.");
 _bstr_t my_bstr_t(a, TRUE);
 my_bstr_t += b;
 myVariant.bstrVal = my_bstr_t;
 // or
 myVariant.bstrVal = _bstr_t(a, FALSE) + b;

 //Change a bstr to a CString.
 CString ANewString(b);
 //or if CString already exists.
 myCString = b;


 //Use of CComBSTR
 CComBSTR myCComBSTR(L"Hello");
 myCComBSTR.Append(L", how are you?");
 VARIANT varFromCCom;
 varFromCCom.vt = VT_BSTR;
 varFromCCom.bstrVal = myCComBSTR;
 
}

void CVarUseDlg::OnBasic()
{
 //create a variant of the character 'c'
 VARIANT varC;
 varC.vt=VT_UI1;
 varC.cVal = 'c';

 //create a variant of the short 12
 VARIANT varS;
 varS.vt = VT_I2;
 varS.iVal = 12;

 //create a variant of the long 1234567;
 VARIANT varL;
 varL.vt = VT_I4;
 varL.lVal = 1234567;
}

void CVarUseDlg::OnSafeArray()
{

 //Create a standard array;
 int* pMyOriginalArray = new int[100];
 int count;
 for(count = 0; count < 100; count++) pMyOriginalArray[count] = count;

 
 //Now get ready to put it into a safe array
 HRESULT hr;
 //create an array bound
 SAFEARRAYBOUND rgsabound[1];
 rgsabound[0].lLbound = 0;  
//The first (and only) collumn of our array starts at 0.
 rgsabound[0].cElements = 100;
//and has 100 elements.
 
 //create the array
 SAFEARRAY FAR* pMySafeArray;
 pMySafeArray = SafeArrayCreate(VT_I4, 1, rgsabound);
//create the array of 4byte integers, one dimension
               //with the bounds stored in rgsabound.
 
 //now access the safe array's data.

 int* pData;
 hr = SafeArrayAccessData(  pMySafeArray, (void**)&pData);
//Get a pointer to the data.
 
 
 //copy the 400 bytes of data from our old array to the new one.
 memcpy_s(pData, 400, pMyOriginalArray, 400);
 
 //here we verify to you that the data is correct.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pMyOriginalArray[count]);
 SafeArrayUnaccessData(pMySafeArray);
 
 
 //To put the SafeArray in a Variant
 VARIANT myVariant;
 myVariant.parray = pMySafeArray;
 myVariant.vt = VT_ARRAY|VT_I4;
// state it's an array and what type it holds.
 
 
 //pass the Variant to a function call. 
 FunctionCallWithVariant(myVariant);
 //see that the original array was modified.
 hr = SafeArrayAccessData(myVariant.parray, (void**)&pData);
//Get a pointer to the data.
 ASSERT(pData[0] == 1234);
 SafeArrayUnaccessData(pMySafeArray);
 
 //Copy the variant.
 VARIANT NewVariant;
 NewVariant.vt = VT_EMPTY;
// must set variant type for copy to work.
 hr = VariantCopy(&NewVariant, &myVariant);

 //check array in copy of variant
 int* pNewData;
 hr = SafeArrayAccessData(NewVariant.parray, (void**) &pNewData);
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pNewData[count]);
 ASSERT(pData != pNewData); //copying the varriant created a new copy of the
         
//array it contained too.  There is now 2 copies of the data.
 SafeArrayUnaccessData(NewVariant.parray);

 //Or you could use CreateVector to create the 1 dimensional array.
 SAFEARRAY* pMyVector = SafeArrayCreateVector(VT_I4, -50, 100);  //Vecoor of VT_I4, starting at 0
               
//with 100 elements.
 int* pVectorData;
 hr = SafeArrayAccessData(pMyVector, (void**)&pVectorData);
 memcpy_s(pVectorData, 400, pMyOriginalArray, 400);
//copy the 400 bytes of data from our old array into the new one.
 int element;
 long location[1] = {-50};
 hr = SafeArrayGetElement(pMyVector, location, (void*)&element);
 ASSERT(element == 0);
//element -50 is the first element from the original array
 location[0] = 0;
 hr = SafeArrayGetElement(pMyVector, location, (void*)&element);
 ASSERT(element == 50);
// element 0 is the 50th element from the original array
 delete[] pMyOriginalArray;
 hr = SafeArrayDestroy(pMySafeArray);
 hr = SafeArrayDestroy(pMyVector);
 hr = SafeArrayDestroy(NewVariant.parray);
 
}

void CVarUseDlg::OnOleArray()
{
 //Create and fill an array.
 int* pMyOriginalArray = new int[100];
 int* pData;
 int count;
 for(count = 0; count < 100; count++) pMyOriginalArray[count]=count;
 
 //Put it into a safe array.
 COleSafeArray myOleSafeArray;
 myOleSafeArray.CreateOneDim(VT_I4, 100, pMyOriginalArray);
// create one dimension array of 4-byte values
                     // 100 entries w/data from pMyOriginalArray.
 // Access that safe array.
 //Get a pointer to the Data.
 myOleSafeArray.AccessData((void**)&pData);
 //verify all of the data.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pMyOriginalArray[count]);
 myOleSafeArray.UnaccessData();
 
 //clean up.
 delete[] pMyOriginalArray;
}

void CVarUseDlg::OnMultiDimensionArray()
{
 short pMyOriginalData[2][3][4][5];
 HRESULT hr;
 //initialize data
 for(short a = 0; a < 2; a++)
  for(short b = 0; b < 3; b++)
   for(short c = 0; c < 4; c++)
    for(short d = 0; d < 5; d++)
     pMyOriginalData[a][b][c][d] = a*b*c*d;

 

 SAFEARRAY* pMyArray;
 SAFEARRAYBOUND rgsabound[4];
 for(int count = 2; count < 6; count++)
 {
  rgsabound[count - 2].lLbound = 0;
  rgsabound[count - 2].cElements = count;
 }
 pMyArray = SafeArrayCreate(VT_I2, 4, rgsabound);
 short* pData;
 hr = SafeArrayAccessData(pMyArray, (void**)&pData);
 memcpy_s(pData, 2*3*4*5*2, pMyOriginalData, 2*3*4*5*2);
 short element;
 long location[4] = {1,2,3,4};
 hr = SafeArrayGetElement(pMyArray, location, (void*)&element);
 ASSERT(element == 1*2*3*4);
 
 
 
 hr = SafeArrayDestroy(pMyArray);
 ASSERT(hr != S_OK);
 hr = SafeArrayUnaccessData(pMyArray);
 hr = SafeArrayDestroy(pMyArray);
 ASSERT(hr == S_OK);
 
}

void CVarUseDlg::OnDate()
{
 VARIANT timeSelection;
 COleDateTime timeNow;
 DATE curDate;
 HRESULT hr;
 //Get current Time.
 timeNow = COleDateTime::GetCurrentTime();
 
 //Put time into variant.
 timeSelection.vt = VT_DATE;
 timeSelection.date = timeNow.m_dt;
 
 
//Convert Variant into string using Variant Change Type.
 hr = VariantChangeType(&timeSelection, &timeSelection, 0, VT_BSTR);
 CString sCurTime(timeSelection.bstrVal);
 
 
 
//Get Time as System Time.
 SYSTEMTIME mySysTime;
 timeNow.GetAsSystemTime(mySysTime);
 
 //Use COleDateTime functionality to get change SYSTEMTIME into DATE.
 COleDateTime pastTime(mySysTime);
 curDate = pastTime.m_dt;


 //Use COldeDateTime Format command to get date as CString.
 LPCTSTR format = _T("%X %z"); 
//Current time and time zone.
 
//Note see "strftime" help for valid formating strings.
 CString sTime = pastTime.Format(format);

}

void CVarUseDlg::FunctionCallWithVariant(VARIANT myVariant)
{
 HRESULT hr;
 int* pData;
 int count;
 hr = SafeArrayAccessData(myVariant.parray, (void**)&pData); //access the array stored in the varriant.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == count);
//check data;
 pData[0] = 1234;
//modify data
 SafeArrayUnaccessData(myVariant.parray);
}

------------------------------------------------------------------------------------------
출처 : MSDN 2005

첨부 파일 : 소스 원본 (Visual Studio 2005 Project)