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

  1. 2011.08.08 DLLHUSK Sample: Dynamically Links the MFC Library
  2. 2010.03.11 Adding automation to MFC applications
  3. 2010.01.06 다중 OLE 객체 인스턴스 지원하기
  4. 2009.10.22 CExpire - A C++ class that implements time and run based restrictions
  5. 2009.10.22 2K/XP 에서 포커스를 훔치는 방법
  6. 2009.10.22 모달 대화상자를 숨겨진 채로 시작하기
  7. 2009.10.16 CHtmlView 관련 몇가지 팁
  8. 2009.10.16 Automated IE SaveAs MHTML
  9. 2009.09.13 Multithreading: Programming Tips
  10. 2009.09.13 애플리케이션 리소스 Localization하기
  11. 2009.09.13 Accessing MFC Objects from Non-MFC Threads
  12. 2009.09.13 MFC DLL 간단 설명
  13. 2009.09.13 멀티스레드와 MF
  14. 2009.09.10 MFC DLL 간단 설명
  15. 2009.09.10 Multithreading: Programming Tips
  16. 2009.09.02 CWnd Derived 클래스 Drag and drop 구현하기 (With OLE)
  17. 2009.07.04 MFC에서 Undo 와 Redo 구현하기
  18. 2009.06.29 MDI에서 Subclassing 활용
  19. 2009.06.04 MFC 프로젝트에 ATL 지원 추가하기
  20. 2009.06.01 MFC Technical Notes
2011. 8. 8. 22:47

DLLHUSK Sample: Dynamically Links the MFC Library

원본: http://msdn.microsoft.com/en-us/library/4fezhh3d(v=vs.80).aspx

DLLHUSK는 동적으로 MFC DLL (Extension dll) 을 로드하는 방법에 대해서 설명하고 있습니다. 

MFC 를 오래동안 사용 해왔지만, Regular DLL 의 동적 로딩에만 신경썼을 뿐, MFC Dll 자체가 동적으로 될거냐라는 질문을 해본적이 없네요. 항상 느끼지만 호기심을 억누르는 선입견 또는 그냥 있는 그대로만 받아들이는 수동적인 자세를 경계해야 겠습니다.

디자인 관점에서 보면, View, Document 를 외부 DLL 에서 구현하고, 이를 로딩할 수만 있다면 다큐먼트 확장에 유연한 MFC Application 을 만들수 있지 않느냐라는 생각이 들었습니다. 또, 가끔 이런 요구사항이 극히 드물게 있기도 했습니다. 하지만 이를 유연하게 구현하는 App 은 보지 못한거 같습니다. (주변에서요....;;)

어쨋든 생각한 순간 예상했던 대로 Microsoft 는 이를 위한 방법을 제공하고 있으며, 친절하게도 그 샘플까지 공유하고 있습니다.

확장성은 trade-off 가 있을 수 있지만, 디자인 관점에서 보면 항상 극복하고 싶은 도전 과제라는 생각이 듭니다.


2010. 3. 11. 06:57

Adding automation to MFC applications

MFC Application 에 자동화를 추가하는 방법을 설명하는 글이다. 정리를 잘 한듯.

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.

......

http://www.codeproject.com/KB/COM/mfc_autom.aspx




2010. 1. 6. 06:26

다중 OLE 객체 인스턴스 지원하기

OLE 자동화 객체를 생성할 때 매번 새로운 인스턴스를 띄우게 하기 위해서는 아래처럼 IMPLEMENT_OLECREATE 를 재정하여야 한다. 원래는 FALSE 로 되어 있어서 단일 인스턴스만 지원하게 되어 있기 때문에 이 값만 TRUE 로 변경한 것이다.

 #define MY_IMPLEMENT_OLECREATE(class_name, external_name, l, w1, w2,
        b1, b2, b3, b4, b5, b6, b7, b8) \
        COleObjectFactory class_name::factory(class_name::guid, \
        RUNTIME_CLASS(class_name), TRUE, _T(external_name)); \
        const GUID CDECL class_name::guid = \
        { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \


MY_IMPLEMENT_OLECREATE(CMyClass, "Test", 0xc9c99ae0, 0xad41, 0x101b,
                       0x83, 0xea, 0x0, 0x0, 0x83, 0x78, 0xac, 0x8b)

그리고 항상 새로운 인스턴스를 생성하기 위해서는 :

( 그냥 이 매크로만 재정의해서 사용하는 경우에는 COM 객체가 연결되지 않은 기존의 인스턴스가 실행되어 있는 경우 이 인스턴스로부터 COM 객체를 생성한다. )

InitInstance 에서 COleTemplateServer::RegisterAll(); 함수를 적절한 곳에 이동시킨다.

     // App was launched with /Embedding or /Automation switch.
    // Run app as automation server.
    if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
    {
        COleTemplateServer::RegisterAll();
        // Don't show the main window
        return TRUE;
    }

참고 : http://support.microsoft.com/kb/141154/en-us/
2009. 10. 22. 06:18

CExpire - A C++ class that implements time and run based restrictions

편리하게 Shareware 를 만들 수 있게 해주는 클래스를 제공한다.


Purpose

This class is something I wrote to kill time. But by the time I finished it, I thought it might be pretty useful for shareware developers. It can be used to implement limited-number-of-runs and limited-time-period-only programs. Means if you want to allow only 25 runs, then this class will do that for you. Or if you want to restrict execution to 15 days from first run, this program will do that too. By the way, even if the user changes his system date back to the allowed-time-period after it has expired, he/she won't be able to run it. 

Disclaimer

This is by no means a fool-proof protection scheme, but it serves its purpose at the intermediate level. And usually crackers manage to actually patch the executable to disable these checks or they manage to write key generators for your protected programs. But this class will save some time for you in the sense, it actually does what you would otherwise have to code on your own.

Protection Mechanism

It uses a combination of registry and file based protection mechanisms. You should be smart enough to pick the right innocent sounding registry keys and filenames to prevent the casual user from simply deleting a few odd keys and break the protection.

How to use the class.

You must instantiate an object of the class as early as possible. You may declare it as a global object if you would like to. In an SDK program, you might want to instantiate the class on the first line in your WinMain() and in an MFC program you might want to put it in your CWinApp derived class's InitInstance().

There are two ways in which you can use the class. In the first way you specify a specific number of times that the program can be run. In the second you set a number of days so that the program is active for that many days from the time of first execution. I shall demonstrate both methods below.

The constructor takes 4 parameters.

CExpire(const char*  ProgName,const char* KeyName, UINT Num, UINT ExpireType);

ProgName - This is a unique value that will be used to create the unique registry key for protection. It is a good idea to choose a GUID for this key. It will be placed in a location confusing enough for most of the average users of your program.

KeyName - This is again used as part of the unique registry key as well as in the generation of the special file. You can use a unique, but natural sounding name like MouseDrv98 or something that would not actually stand out.

Num - This is either the number of days from the date of execution or the total number of runs allowed. This value's interpretation depends upon the ExpireType parameter.

ExpireType - This can be one of two values as shown below

TYPERUNS - This implies run-count based protection
TYPEDAYS - This implies date-interval based protection

There are only three public functions that you'll need to call.

bool HasExpired();

It returns true if your program has expired and false if the user is still within his allowed limits.

UINT GetDaysLeft();

This returns the number of days left before the program expires. Use this if you have chosen ExpireType as TYPEDAYS

UINT GetRunsLeft();

This returns the number of runs left before the program expires. Use this if you have chosen ExpireType as TYPERUNS

Example Usage

SDK example

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int)
{
CExpire Protector("{00000000-0000-0000-8888-00AA006D2EA4}",
"InprocServer32",6,TYPEDAYS);
if(Protector.HasExpired())
{
MessageBox(0,
"This program has expired. Please buy the registered version.",
"Fatal Error",0);
}
else
{
char s[128];
sprintf(s,"you have %d days left",Protector.GetDaysLeft());
MessageBox(0,s,"",0);
StartExecution();
}
return 0;
}


MFC Example

BOOL CTest_deleteApp::InitInstance()
{
CExpire expire("JPEG-File-Viewer","FileStore",30,TYPERUNS);
if(expire.HasExpired())
{
AfxMessageBox("This program will not run anymore...");
return FALSE;
}
char s[128];
sprintf(s,"you have %d runs left",expire.GetRunsLeft());
AfxMessageBox(s);

AfxEnableControlContainer();

#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif

CTest_deleteDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();

// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}



Important Warning

DON'T choose a value for the KeyName parameter that might already define a file in the Windows System directory. It will be over-written. The best thing to do is to choose a filename and then add some numbers to the end like 32_32 or something natural sounding.

Thank you.


From : http://www.voidnish.com/Articles/ShowArticle.aspx?code=cexpire01

2009. 10. 22. 06:04

2K/XP 에서 포커스를 훔치는 방법

How to steal focus on 2K/XP

I bet that sometimes you long for the old days when a simple SetForegroundWindow brought your dialog into focus. Sigh! Now with 2K/XP things have sorta changed so that if you try a simple SetForegroundWindow you end up flashing the taskbar icon a few times (I never counted but something tells me it flashes thrice). Not exactly what you wanted to do, eh? Luckily there are ways to bring your dialog into the foreground.

The trick is to use AttachThreadInput to attach the thread that owns the current foreground window to our thread, then call SetForegroundWindow and then detach the attached thread, again using AttachThreadInput. Cool, huh?


//Attach foreground window thread
//to our thread
AttachThreadInput(
GetWindowThreadProcessId(
::GetForegroundWindow(),NULL),
GetCurrentThreadId(),TRUE);

//Do our stuff here ;-)
SetForegroundWindow();
SetFocus(); //Just playing safe

//Detach the attached thread
AttachThreadInput(
GetWindowThreadProcessId(
::GetForegroundWindow(),NULL),
GetCurrentThreadId(),FALSE);

From : http://www.voidnish.com/Articles/ShowArticle.aspx?code=dlgboxtricks
2009. 10. 22. 06:02

모달 대화상자를 숨겨진 채로 시작하기

Starting a modal dialog hidden

You often hear people complain that despite putting a ShowWindow(SW_HIDE) in their OnInitDialog their modal dialog still starts up in a shown state. The problem here is that when CDialog::OnInitDialog() finishes it will call ShowWindow(SW_SHOW). Thus your dialog box is again made visisble. But then as, is to be expected, people have worked around this. Here is what you need to do.

Add a BOOL member to your dialog class and call it something, say visible.

Now in your dialog constructor set visible to false.

visible = false;

Now you need to override WM_WINDOWPOSCHANGING. You might have to change your message filtering options to have this message show up in the Class Wizard.

void CTest_deleteDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
if(!visible)
lpwndpos->flags &= ~SWP_SHOWWINDOW;

CDialog::OnWindowPosChanging(lpwndpos);
}

That's it. Now your modal dialog actually starts up in a hidden state. And when you want to make it visible this is what you need to do.

visible = true;
ShowWindow(SW_SHOW);

From : http://www.voidnish.com/Articles/ShowArticle.aspx?code=dlgboxtricks
2009. 10. 16. 03:40

CHtmlView 관련 몇가지 팁

). 브라우져캐쉬(CHCHE) 제거 - 가끔씩 HTML 이 캐쉬되면서 Refresh 가 작동하지 않을때는 아래 함수를 호출해 주면 됨

        DeleteUrlCacheEntry(lpURL);

 

    ) 브라우져컨트롤(CHtmlView)에서 스크립트 오류안나오게하기 - CHtmlView::OnAmbientProperty 오버로딩해서 구현하면 됩니다( http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=6243&ref=6243 사이트에서정보얻음)

        BOOL CHtmlView::OnAmbientProperty(COleControlSite* pSite, DISPID dispid, VARIANT* pvar)

        {

            // TODO: Add your specialized code here and/or call the base class

            if (pvar && dispid == DISPID_AMBIENT_DLCONTROL) {

                V_VT(pvar) = VT_I4;

                V_I4(pvar) =

                    DLCTL_DLIMAGES|

                    DLCTL_VIDEOS|

                    DLCTL_BGSOUNDS|

                    DLCTL_NO_SCRIPTS|

                    0;

                return TRUE;

            }

            else

            {

                return CHtmlView::OnAmbientProperty(pSite, dispid, pvar);

            }

            //return CHtmlView::OnAmbientProperty(pSite, dispid, pvar);

        }



출처 : http://eslife.tistory.com/entry/MFC-STL-%EA%B0%84%EB%8B%A8-%EC%82%AC%EC%9A%A9%ED%8C%81

2009. 10. 16. 02:11

Automated IE SaveAs MHTML


아래 링크에서는 Web Browser 에서 MHTML 로 변환할 수 있게 해준다.

http://www.codeproject.com/KB/shell/iesaveas.aspx?display=Print

아래는 관련 소스


되게 유용한데 현재 프로젝트에 적용하는데는 문제가 있다.  App 내에서 노출된 자동화 개체와의 연결 때문이다. 이런 html 파일을 mhtml 로 저장하더라도 보이는 화면 자체가 mhtml 으로 저장되는 게 아니라 아쉽게도 javascript 가 포함된 문서가 저장된다. 그렇게 되면 동적으로 생성된 그림등은 mhtml 문서에서도 역시 동적으로 생성되기때문에 스크립트 에러가 나고 이미지도 저장되지 않는다.

과연... javascript 가 완전히 번역된 후의 html 을 기반으로 mhtml 을 저장할 수 는 없을까?

항상 난관이 있네...~ ㅎㅎ
2009. 9. 13. 20:49

Multithreading: Programming Tips

Multithreaded applications require stricter care than single-threaded applications when accessing data. Because there are multiple, independent paths of execution in use simultaneously in multithreaded applications, either the algorithms, the data, or both must be aware that data could be used by more than one thread at a time. This topic explains techniques for avoiding potential problems when programming multithreaded applications with the Microsoft Foundation Class (MFC) library.

Accessing Objects from Multiple Threads

For size and performance reasons, MFC objects are not thread-safe at the object level, only at the class level. This means that you can have two separate threads manipulating two different CString objects, but not two threads manipulating the same CString object. If you absolutely must have multiple threads manipulating the same object, protect such access with appropriate Win32 synchronization mechanisms, such as critical sections. For more information about critical sections and other related objects, see Synchronization in the Platform SDK.

The class library uses critical sections internally to protect global data structures, such as those used by the debug memory allocation.

Accessing MFC Objects from Non-MFC Threads

If you have a multithreaded application that creates a thread in a way other than using a CWinThread object, you cannot access other MFC objects from that thread. In other words, if you want to access any MFC object from a secondary thread, you must create that thread with one of the methods described in Multithreading: Creating User-Interface Threads or Multithreading: Creating Worker Threads. These methods are the only ones that allow the class library to initialize the internal variables necessary to handle multithreaded applications.

Windows Handle Maps

As a general rule, a thread can access only MFC objects that it created. This is because temporary and permanent Windows handle maps are kept in thread local storage to help maintain protection from simultaneous access from multiple threads. For example, a worker thread cannot perform a calculation and then call a document's UpdateAllViews member function to have the windows that contain views on the new data modified. This has no effect at all, because the map from CWnd objects to HWNDs is local to the primary thread. This means that one thread might have a mapping from a Windows handle to a C++ object, but another thread might map that same handle to a different C++ object. Changes made in one thread would not be reflected in the other.

There are several ways around this problem. The first is to pass individual handles (such as an HWND) rather than C++ objects to the worker thread. The worker thread then adds these objects to its temporary map by calling the appropriate FromHandle member function. You could also add the object to the thread's permanent map by calling Attach, but this should be done only if you are guaranteed that the object will exist longer than the thread.

Another method is to create new user-defined messages corresponding to the different tasks your worker threads will be performing and post these messages to the application's main window using ::PostMessage. This method of communication is similar to two different applications conversing except that both threads are executing in the same address space.

For more information about handle maps, see Technical Note 3. For more information about thread local storage, see Thread Local Storage and Using Thread Local Storage in the Platform SDK.

Communicating Between Threads

MFC provides a number of classes that allow threads to synchronize access to objects to maintain thread safety. Usage of these classes is described in Multithreading: How to Use the Synchronization Classes and Multithreading: When to Use the Synchronization Classes. For more information about these objects, see Synchronization in the Platform SDK.

See Also

2009. 9. 13. 20:37

애플리케이션 리소스 Localization하기


  CWinApp상속받은 나의 App 클래스의 맴버로
HINSTANCE m_hInstDLL;
를 추가한 후
  OnInitInstance() 첫줄에

   if ((m_hInstDLL = ::LoadLibrary("mylocal.dll")) < HINSTANCE_ERROR)
   {
      return FALSE; // failed to load the localized resources
   }
   else
   {
      AfxSetResourceHandle(m_hInstDLL); // get resources from the DLL
   }

하고 OnExitIntance() 부에

   int CMyApp::ExitInstance()
   {
      FreeLibrary(m_hInstDLL);
      return CWinApp::ExitInstance();
   }

출처 : 인터넷 검색하면 나오는데 원본 출처는 불분명

2009. 9. 13. 20:36

Accessing MFC Objects from Non-MFC Threads

출처 : 인터넷 검색하면 나오는데 원본 출처는 불분명

MSDN : Multithreading: Programming Tips
만일 CWinThread 객체를 이용하지 않고 멀티스레드 애플리케이션을 만들었다면 다른 스레드의
MFC 객체를 접근할 수 없을 것이다. 반대로 말해 MFC객체를 이차 스레드에서 접근하려면
반드시 AfxBeginThread를 사용해 CWinThread의 GUI 스레드 방식을 쓰거나 . 워커 스레드 방식
으로 써야 한다.
이는 멀티스레드 애플리케이션이 클래스 라이브러리를 초기화하고 필요한 내부변수를 핸들링
하기 위한 것이다.

CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
        int nPriority, UINT nStackSize, DWORD dwCreateFlags,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
        pfnThreadProc;
        pParam;
        nPriority;
        nStackSize;
        dwCreateFlags;
        lpSecurityAttrs;

        return NULL;
#else
        ASSERT(pfnThreadProc != NULL);

        CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam);
        ASSERT_VALID(pThread);

        if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED, nStackSize,
                lpSecurityAttrs)) // CreateThread에서
        {
                pThread->Delete();
                return NULL;
        }
        VERIFY(pThread->SetThreadPriority(nPriority));
        if (!(dwCreateFlags & CREATE_SUSPENDED))
                VERIFY(pThread->ResumeThread() != (DWORD)-1);

        return pThread;
#endif //!_MT)
}


BOOL CWinThread::CreateThread(DWORD dwCreateFlags, UINT nStackSize,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
        dwCreateFlags;
        nStackSize;
        lpSecurityAttrs;

        return FALSE;
#else
        ASSERT(m_hThread == NULL);  // already created?

        // setup startup structure for thread initialization
        _AFX_THREAD_STARTUP startup; memset(&startup, 0, sizeof(startup));
        startup.pThreadState = AfxGetThreadState(); // 스레드 상태를 얻어온다.
        startup.pThread = this;
        startup.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
        startup.hEvent2 = ::CreateEvent(NULL, TRUE, FALSE, NULL);
        startup.dwCreateFlags = dwCreateFlags;
        if (startup.hEvent == NULL || startup.hEvent2 == NULL)
        {
                TRACE0("Warning: CreateEvent failed in CWinThread::CreateThread.\n");
                if (startup.hEvent != NULL)
                        ::CloseHandle(startup.hEvent);
                if (startup.hEvent2 != NULL)
                        ::CloseHandle(startup.hEvent2);
                return FALSE;
        }

        // create the thread (it may or may not start to run)
        m_hThread = (HANDLE)_beginthreadex(lpSecurityAttrs, nStackSize,
                &_AfxThreadEntry, &startup, dwCreateFlags | CREATE_SUSPENDED, (UINT*)&m_nThreadID);
        if (m_hThread == NULL)
                return FALSE;

 

// MFC 스레드 상태 구조체 안에는 여러 핸들맵등 중요한 스레드별로 관리되는
// 핸들들이 있다. 따라서 AfxBeginThread를 이용해야만 2차 스레드가 핸들맵을
// 옳바로 전달 받을 수 있게 된다.

// AFX_MODULE_THREAD_STATE (local to thread *and* module)
class AFX_MODULE_THREAD_STATE : public CNoTrackObject
{
public:
        AFX_MODULE_THREAD_STATE();
        virtual ~AFX_MODULE_THREAD_STATE();

        // current CWinThread pointer
        CWinThread* m_pCurrentWinThread;

        // list of CFrameWnd objects for thread
        CTypedSimpleList<CFrameWnd*> m_frameList;

        // temporary/permanent map state
        DWORD m_nTempMapLock;           // if not 0, temp maps locked
        CHandleMap* m_pmapHWND;
        CHandleMap* m_pmapHMENU;
        CHandleMap* m_pmapHDC;
        CHandleMap* m_pmapHGDIOBJ;
        CHandleMap* m_pmapHIMAGELIST;

        // thread-local MFC new handler (separate from C-runtime)
        _PNH m_pfnNewHandler;

#ifndef _AFX_NO_SOCKET_SUPPORT
        // WinSock specific thread state
        HWND m_hSocketWindow;
#ifdef _AFXDLL
        CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle;
        CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets;
        CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications;
#else
        CMapPtrToPtr* m_pmapSocketHandle;
        CMapPtrToPtr* m_pmapDeadSockets;
        CPtrList* m_plistSocketNotifications;
#endif
#endif
};

class _AFX_THREAD_STATE : public CNoTrackObject
{
public:
        _AFX_THREAD_STATE();
        virtual ~_AFX_THREAD_STATE();

        // override for m_pModuleState in _AFX_APP_STATE
        AFX_MODULE_STATE* m_pModuleState;
        AFX_MODULE_STATE* m_pPrevModuleState;

        // memory safety pool for temp maps
        void* m_pSafetyPoolBuffer;    // current buffer

        // thread local exception context
        AFX_EXCEPTION_CONTEXT m_exceptionContext;

        // CWnd create, gray dialog hook, and other hook data
        CWnd* m_pWndInit;
        CWnd* m_pAlternateWndInit;      // special case commdlg hooking
        DWORD m_dwPropStyle;
        DWORD m_dwPropExStyle;
        HWND m_hWndInit;
        BOOL m_bDlgCreate;
        HHOOK m_hHookOldCbtFilter;
        HHOOK m_hHookOldMsgFilter;

        // other CWnd modal data
        MSG m_lastSentMsg;              // see CWnd::WindowProc
        HWND m_hTrackingWindow;         // see CWnd::TrackPopupMenu
        HMENU m_hTrackingMenu;
        TCHAR m_szTempClassName[96];    // see AfxRegisterWndClass
        HWND m_hLockoutNotifyWindow;    // see CWnd::OnCommand
        BOOL m_bInMsgFilter;

        // other framework modal data
        CView* m_pRoutingView;          // see CCmdTarget::GetRoutingView
        CFrameWnd* m_pRoutingFrame;     // see CCmdTarget::GetRoutingFrame

        // MFC/DB thread-local data
        BOOL m_bWaitForDataSource;

        // common controls thread state
        CToolTipCtrl* m_pToolTip;
        CWnd* m_pLastHit;       // last window to own tooltip
        int m_nLastHit;         // last hittest code
        TOOLINFO m_lastInfo;    // last TOOLINFO structure
        int m_nLastStatus;      // last flyby status message
        CControlBar* m_pLastStatus; // last flyby status control bar

        // OLE control thread-local data
        CWnd* m_pWndPark;       // "parking space" window
        long m_nCtrlRef;        // reference count on parking window
        BOOL m_bNeedTerm;       // TRUE if OleUninitialize needs to be called
};

EXTERN_THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)

_AFX_THREAD_STATE* AFXAPI AfxGetThreadState();

2009. 9. 13. 20:33

MFC DLL 간단 설명


MFC에서 DLL의 종류는 두가지이다. _USRDLL(MFC정규DLL)과 _AFXDLL(MFC확장DLL)이다.
_USRDLL은  MFC라이브러리의 초기화 종료를 위해 하나의 CWinApp 객체를 필요로한다.
_AFXDLL은 CWinApp 객체를 필요로 하지 않는다. 이는 애플리케이션의 MFC 클래스들을
공유하기 때문이다.

AFXEXT는 AFXDLL이다.
_AFXDLL이 정의가 되면, AfxGetResourceHandle함수로 리턴되는 값은 MFC의 전역 변수로
저장된 값이 리턴되는데 이 값은 EXE나 확장 DLL또는 MFC DLL 모두가 공유하는 값이다.
엄격히 말해, 리소스를 로드한다는 말은 인스턴스 핸들 보다는 모듈 핸들가 연관있다.
(인스턴스는 각 다른 데이타를 갖는 모듈(즉, 코드와 리소스들)들을 공유한다. )
DLL은 EXE의 핸들과 다른 모듈 핸들을 갖는다.
::GetModuleHandle을 DLL의 모듈핸들을 얻기 위해 쓸수 있고, AfxSetResourceHandle을
통해서 DLL이 리소스를 찾을려고 할때 바로 찾을수 있게 처음에 위치하게 할 수 있다.
그러나 알아둘점은 이렇게 하게 되면 .EXE 모듈을 찾을 수 없게 된다. 그래서 보통
AfxSetResourceHandle을 호출하기전에 AfxGetResourceHandle로 핸들값을 복사해 저장한다.
DLL리소스의 로드가 끝나면 다시 EXE모듈 핸들을 세팅하면된다.

출처 : 검색하면 여기저기에서 나옴.

2009. 9. 13. 20:30

멀티스레드와 MF


MSDN : CWnd Derived MFC Objects and Multi-threaded Applications

멀티스레드 애플리케이션을 MFC를이용하여 짠다면, MFC 객체를 스레드간에 넘겨서는
안된다. 이 일반적인 룰에서 한 스레드는 오직 자신이 생성한 MFC객체들만 접근해야한다.
이렇게 하지 않으면 assertion이나 기대치 못한 행동등의 런타임 문제를 발생시킨다.

Win32 프로세스 주소영역 안에서 동작하는 모든 스레드는 모든 전역,static 자료를
접근할 수 있다. 스레드는 TLS(Thread-Local-Storage)로 스레드 자신만의 자료를 쓸수있다.

윈도우는 스레드들에 의해 관리되므로 멀티스레드 환경하에서, MFC는 Thread Local Storage
안에 임시적적이거나 영구적인(temporary and permanent) 윈도우 핸들 맵을 관리한다.
물론 다른 GDI 객체나 디바이스 컨텍스트를 위한 핸들 맵 역시 그렇게 관리한다.
TLS에 관리되는 윈도우 핸들 맵은 여러 스레드들 사이에서 동시에 접근되어선 안된다.

CHandleMap::LookupPermanent()와 CHandleMap::LookupTemporary() 함수는 이런 사실들의
직접적인 예이다. 주어진 윈도우 핸들에 대해 이런 함수들은 MFC CWnd를 상속받은 객체와
연관있는 현재 스레드의 permenent하거나 temporary한 윈도우 핸들맵을 체크한다.
만일 그런 함수로 다른 스레드에 의해 생성된 윈도우 객체에 대한 MFC 객체를 찾으려고 한다면
함수 호출은 실패가 될것이다.

CHandleMap::LookupPermanent()와 CHandleMap::LookupTemporary()를 호출하는 함수들이
몇개 있다. CWnd::AssertValid()( ASSERT_VALID라는 매크로도 역시) 그런 함수들중 하나다.
이 함수는 객체의 유효성을 체크하기 위해 쓰인다. 만일 AssertValid()가 MFC 객체의 m_hWnd
맴버를 핸들맵에서 찾지 못하거나, 잘못된 엔트리에서 찾게되면 이 함수는 assertion을 일으킨다.
VC++ 2.1에서는 이런 assertion은 Wincore.cpp파일의 797과 788번재줄이며
VC++ 2.2에서는 Wincore.cpp파일의 804와 805번째줄,
VC++ 4.0에서는 Wincore.cpp파일의 871과 872번째 줄에 assertion이 생기게 된다

ASSERT_VALID 매크로는 MFC 소스코드 전반에 걸쳐 많이 존재한다. 따라서 만일 특정스레드에서
다른 스레드에 속하는 MFC 윈도우 객체에 ASSERT_VALID를 호출한다면, assertion이 발생한다.
만일 assertion이 발생하지 않았더라도, 다른 스레드에 생성된 윈도우를 조작하는것은 허락
된 것이 아니므로 비정상적으로 동작할 것이다.

이런 경우 MFC 객체를 이용하지 말고 윈도우 핸들로 작업해야 한다.
윈도우 핸들을 스레드간에 전달하는 것은 안전하다. 만일 스레드 A에서 스레드 B로
윈도우 핸들을 전달했다면, 스레드 B는 이 윈도우 핸들로 Send 나 Post Message 를 할 수 있다. 이 메시지가 처리될 때, 스레드 A의 컨텍스트로 바뀌게 되고, Cwnd::AssertVaild() 은 스레드 A의 윈도우 핸들맵을 체크하고 성공을 리턴할 것이다.

이런 시나리오에서 스레드 B는 역시 CWnd::Fromhandle() 함수를 통해서 스레드 B의 임시적인(temporary) 핸들맵에 임시적인 CWnd 객체를 생성할 수 있다. 그러나 이 객체는 스레드 A의 핸들맵에 존재하는 MFC 객체와 동기화 하는 방법이 없으므로 사용상 제약을 받을 것이다.

출처 : 인터넷 검색하면 나오는데 원본 출처는 불분명

2009. 9. 10. 07:38

MFC DLL 간단 설명


오래전에 쓰인 글이지만 좋은 글.
출처 : 검색하면 여기저기에서 나옴.

관련 MSDN 참고 자료 : http://msdn.microsoft.com/en-us/library/h14y172e%28VS.80%29.aspx#_core_windows_handle_maps

오후 5:05 2002-01-23
조경민 MFC DLL 간단 설명
========================================================================

MFC에서 DLL의 종류는 두가지이다. _USRDLL(MFC정규DLL)과 _AFXDLL(MFC확장DLL)이다.
_USRDLL은  MFC라이브러리의 초기화 종료를 위해 하나의 CWinApp 객체를 필요로한다.
_AFXDLL은 CWinApp 객체를 필요로 하지 않는다. 이는 애플리케이션의 MFC 클래스들을
공유하기 때문이다.

AFXEXT는 AFXDLL이다.
_AFXDLL이 정의가 되면, AfxGetResourceHandle함수로 리턴되는 값은 MFC의 전역 변수로
저장된 값이 리턴되는데 이 값은 EXE나 확장 DLL또는 MFC DLL 모두가 공유하는 값이다.
엄격히 말해, 리소스를 로드한다는 말은 인스턴스 핸들 보다는 모듈 핸들가 연관있다.
(인스턴스는 각 다른 데이타를 갖는 모듈(즉, 코드와 리소스들)들을 공유한다. )
DLL은 EXE의 핸들과 다른 모듈 핸들을 갖는다.
::GetModuleHandle을 DLL의 모듈핸들을 얻기 위해 쓸수 있고, AfxSetResourceHandle을
통해서 DLL이 리소스를 찾을려고 할때 바로 찾을수 있게 처음에 위치하게 할 수 있다.
그러나 알아둘점은 이렇게 하게 되면 .EXE 모듈을 찾을 수 없게 된다. 그래서 보통
AfxSetResourceHandle을 호출하기전에 AfxGetResourceHandle로 핸들값을 복사해 저장한다.
DLL리소스의 로드가 끝나면 다시 EXE모듈 핸들을 세팅하면된다.

- 멀티스레드와 MFC
MSDN : CWnd Derived MFC Objects and Multi-threaded Applications
멀티스레드 애플리케이션을 MFC를이용하여 짠다면, MFC 객체를 스레드간에 넘겨서는
안된다. 이 일반적인 룰에서 한 스레드는 오직 자신이 생성한 MFC객체들만 접근해야한다.
이렇게 하지 않으면 assertion이나 기대치 못한 행동등의 런타임 문제를 발생시킨다.

Win32 프로세스 주소영역 안에서 동작하는 모든 스레드는 모든 전역,static 자료를
접근할 수 있다. 스레드는 TLS(Thread-Local-Storage)로 스레드 자신만의 자료를 쓸수있다.

윈도우는 스레드들에 의해 관리되므로 멀티스레드 환경하에서, MFC는 Thread Local Storage
안에 임시적적이거나 영구적인(temporary and permanent) 윈도우 핸들 맵을 관리한다.
물론 다른 GDI 객체나 디바이스 컨텍스트를 위한 핸들 맵 역시 그렇게 관리한다.
TLS에 관리되는 윈도우 핸들 맵은 여러 스레드들 사이에서 동시에 접근되어선 안된다.

CHandleMap::LookupPermanent()와 CHandleMap::LookupTemporary() 함수는 이런 사실들의
직접적인 예이다. 주어진 윈도우 핸들에 대해 이런 함수들은 MFC CWnd를 상속받은 객체와
연관있는 현재 스레드의 permenent하거나 temporary한 윈도우 핸들맵을 체크한다.
만일 그런 함수로 다른 스레드에 의해 생성된 윈도우 객체에 대한 MFC 객체를 찾으려고 한다면
함수 호출은 실패가 될것이다.

CHandleMap::LookupPermanent()와 CHandleMap::LookupTemporary()를 호출하는 함수들이
몇개 있다. CWnd::AssertValid()( ASSERT_VALID라는 매크로도 역시) 그런 함수들중 하나다.
이 함수는 객체의 유효성을 체크하기 위해 쓰인다. 만일 AssertValid()가 MFC 객체의 m_hWnd
맴버를 핸들맵에서 찾지 못하거나, 잘못된 엔트리에서 찾게되면 이 함수는 assertion을 일으킨다.
VC++ 2.1에서는 이런 assertion은 Wincore.cpp파일의 797과 788번재줄이며
VC++ 2.2에서는 Wincore.cpp파일의 804와 805번째줄,
VC++ 4.0에서는 Wincore.cpp파일의 871과 872번째 줄에 assertion이 생기게 된다

ASSERT_VALID 매크로는 MFC 소스코드 전반에 걸쳐 많이 존재한다. 따라서 만일 특정스레드에서
다른 스레드에 속하는 MFC 윈도우 객체에 ASSERT_VALID를 호출한다면, assertion이 발생한다.
만일 assertion이 발생하지 않았더라도, 다른 스레드에 생성된 윈도우를 조작하는것은 허락
된 것이 아니므로 비정상적으로 동작할 것이다.

이런경우 MFC 객체를 이용하지 말고 윈도우 핸들로 작업을 해야 한다. 윈도우 핸들을 스레드간
에 전달하는것은 안전하다. 만일 스레드 A에서 스레드 B로 윈도우 핸들을 전달했다면, 스레드 B는
이 윈도우 핸들로 send 나 post message를 할 수 있다. 이 메시지가 처리될때, 스레드 A의
컨텍스트로 바뀌게 되고, CWnd::AssertValid()은 스레드 A의 윈도우 핸들맵을 체크하고 성공을
리턴할 것이다.

이런 시나리오에서 스레드 B는 역시 CWnd::FromHandle()함수를 통해서 스레드 B의 임시적인
(temporary)핸들맵에 임시적인 CWnd객체를 생성할 수 있다. 그러나 이 객체는 스레드 A의 핸들
맵에 존재하는 MFC 객체와 동기화하는 방법이 없으므로 사용상 제약을 받을 것이다.


-Accessing MFC Objects from Non-MFC Threads
MSDN : Multithreading: Programming Tips
만일 CWinThread 객체를 이용하지 않고 멀티스레드 애플리케이션을 만들었다면 다른 스레드의
MFC 객체를 접근할 수 없을 것이다. 반대로 말해 MFC객체를 이차 스레드에서 접근하려면
반드시 AfxBeginThread를 사용해 CWinThread의 GUI 스레드 방식을 쓰거나 . 워커 스레드 방식
으로 써야 한다.
이는 멀티스레드 애플리케이션이 클래스 라이브러리를 초기화하고 필요한 내부변수를 핸들링
하기 위한 것이다.

CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
        int nPriority, UINT nStackSize, DWORD dwCreateFlags,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
        pfnThreadProc;
        pParam;
        nPriority;
        nStackSize;
        dwCreateFlags;
        lpSecurityAttrs;

        return NULL;
#else
        ASSERT(pfnThreadProc != NULL);

        CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam);
        ASSERT_VALID(pThread);

        if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED, nStackSize,
                lpSecurityAttrs)) // CreateThread에서
        {
                pThread->Delete();
                return NULL;
        }
        VERIFY(pThread->SetThreadPriority(nPriority));
        if (!(dwCreateFlags & CREATE_SUSPENDED))
                VERIFY(pThread->ResumeThread() != (DWORD)-1);

        return pThread;
#endif //!_MT)
}


BOOL CWinThread::CreateThread(DWORD dwCreateFlags, UINT nStackSize,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
        dwCreateFlags;
        nStackSize;
        lpSecurityAttrs;

        return FALSE;
#else
        ASSERT(m_hThread == NULL);  // already created?

        // setup startup structure for thread initialization
        _AFX_THREAD_STARTUP startup; memset(&startup, 0, sizeof(startup));
        startup.pThreadState = AfxGetThreadState(); // 스레드 상태를 얻어온다.
        startup.pThread = this;
        startup.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
        startup.hEvent2 = ::CreateEvent(NULL, TRUE, FALSE, NULL);
        startup.dwCreateFlags = dwCreateFlags;
        if (startup.hEvent == NULL || startup.hEvent2 == NULL)
        {
                TRACE0("Warning: CreateEvent failed in CWinThread::CreateThread.\n");
                if (startup.hEvent != NULL)
                        ::CloseHandle(startup.hEvent);
                if (startup.hEvent2 != NULL)
                        ::CloseHandle(startup.hEvent2);
                return FALSE;
        }

        // create the thread (it may or may not start to run)
        m_hThread = (HANDLE)_beginthreadex(lpSecurityAttrs, nStackSize,
                &_AfxThreadEntry, &startup, dwCreateFlags | CREATE_SUSPENDED, (UINT*)&m_nThreadID);
        if (m_hThread == NULL)
                return FALSE;



// MFC 스레드 상태 구조체 안에는 여러 핸들맵등 중요한 스레드별로 관리되는
// 핸들들이 있다. 따라서 AfxBeginThread를 이용해야만 2차 스레드가 핸들맵을
// 옳바로 전달 받을 수 있게 된다.

// AFX_MODULE_THREAD_STATE (local to thread *and* module)
class AFX_MODULE_THREAD_STATE : public CNoTrackObject
{
public:
        AFX_MODULE_THREAD_STATE();
        virtual ~AFX_MODULE_THREAD_STATE();

        // current CWinThread pointer
        CWinThread* m_pCurrentWinThread;

        // list of CFrameWnd objects for thread
        CTypedSimpleList<CFrameWnd*> m_frameList;

        // temporary/permanent map state
        DWORD m_nTempMapLock;           // if not 0, temp maps locked
        CHandleMap* m_pmapHWND;
        CHandleMap* m_pmapHMENU;
        CHandleMap* m_pmapHDC;
        CHandleMap* m_pmapHGDIOBJ;
        CHandleMap* m_pmapHIMAGELIST;

        // thread-local MFC new handler (separate from C-runtime)
        _PNH m_pfnNewHandler;

#ifndef _AFX_NO_SOCKET_SUPPORT
        // WinSock specific thread state
        HWND m_hSocketWindow;
#ifdef _AFXDLL
        CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle;
        CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets;
        CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications;
#else
        CMapPtrToPtr* m_pmapSocketHandle;
        CMapPtrToPtr* m_pmapDeadSockets;
        CPtrList* m_plistSocketNotifications;
#endif
#endif
};

class _AFX_THREAD_STATE : public CNoTrackObject
{
public:
        _AFX_THREAD_STATE();
        virtual ~_AFX_THREAD_STATE();

        // override for m_pModuleState in _AFX_APP_STATE
        AFX_MODULE_STATE* m_pModuleState;
        AFX_MODULE_STATE* m_pPrevModuleState;

        // memory safety pool for temp maps
        void* m_pSafetyPoolBuffer;    // current buffer

        // thread local exception context
        AFX_EXCEPTION_CONTEXT m_exceptionContext;

        // CWnd create, gray dialog hook, and other hook data
        CWnd* m_pWndInit;
        CWnd* m_pAlternateWndInit;      // special case commdlg hooking
        DWORD m_dwPropStyle;
        DWORD m_dwPropExStyle;
        HWND m_hWndInit;
        BOOL m_bDlgCreate;
        HHOOK m_hHookOldCbtFilter;
        HHOOK m_hHookOldMsgFilter;

        // other CWnd modal data
        MSG m_lastSentMsg;              // see CWnd::WindowProc
        HWND m_hTrackingWindow;         // see CWnd::TrackPopupMenu
        HMENU m_hTrackingMenu;
        TCHAR m_szTempClassName[96];    // see AfxRegisterWndClass
        HWND m_hLockoutNotifyWindow;    // see CWnd::OnCommand
        BOOL m_bInMsgFilter;

        // other framework modal data
        CView* m_pRoutingView;          // see CCmdTarget::GetRoutingView
        CFrameWnd* m_pRoutingFrame;     // see CCmdTarget::GetRoutingFrame

        // MFC/DB thread-local data
        BOOL m_bWaitForDataSource;

        // common controls thread state
        CToolTipCtrl* m_pToolTip;
        CWnd* m_pLastHit;       // last window to own tooltip
        int m_nLastHit;         // last hittest code
        TOOLINFO m_lastInfo;    // last TOOLINFO structure
        int m_nLastStatus;      // last flyby status message
        CControlBar* m_pLastStatus; // last flyby status control bar

        // OLE control thread-local data
        CWnd* m_pWndPark;       // "parking space" window
        long m_nCtrlRef;        // reference count on parking window
        BOOL m_bNeedTerm;       // TRUE if OleUninitialize needs to be called
};

EXTERN_THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)

_AFX_THREAD_STATE* AFXAPI AfxGetThreadState();


- 애플리케이션 리소스 Localization하기
  CWinApp상속받은 나의 App 클래스의 맴버로
HINSTANCE m_hInstDLL;
를 추가한 후
  OnInitInstance() 첫줄에

   if ((m_hInstDLL = ::LoadLibrary("mylocal.dll")) < HINSTANCE_ERROR)
   {
      return FALSE; // failed to load the localized resources
   }
   else
   {
      AfxSetResourceHandle(m_hInstDLL); // get resources from the DLL
   }

하고 OnExitIntance() 부에

   int CMyApp::ExitInstance()
   {
      FreeLibrary(m_hInstDLL);
      return CWinApp::ExitInstance();
   }
2009. 9. 10. 07:35

Multithreading: Programming Tips

Multithreaded applications require stricter care than single-threaded applications when accessing data. Because there are multiple, independent paths of execution in use simultaneously in multithreaded applications, either the algorithms, the data, or both must be aware that data could be used by more than one thread at a time. This topic explains techniques for avoiding potential problems when programming multithreaded applications with the Microsoft Foundation Class (MFC) library.

Accessing Objects from Multiple Threads

For size and performance reasons, MFC objects are not thread-safe at the object level, only at the class level. This means that you can have two separate threads manipulating two different CString objects, but not two threads manipulating the same CString object. If you absolutely must have multiple threads manipulating the same object, protect such access with appropriate Win32 synchronization mechanisms, such as critical sections. For more information about critical sections and other related objects, see Synchronization in the Platform SDK.

The class library uses critical sections internally to protect global data structures, such as those used by the debug memory allocation.

Accessing MFC Objects from Non-MFC Threads

If you have a multithreaded application that creates a thread in a way other than using a CWinThread object, you cannot access other MFC objects from that thread. In other words, if you want to access any MFC object from a secondary thread, you must create that thread with one of the methods described in Multithreading: Creating User-Interface Threads or Multithreading: Creating Worker Threads. These methods are the only ones that allow the class library to initialize the internal variables necessary to handle multithreaded applications.

Windows Handle Maps

As a general rule, a thread can access only MFC objects that it created. This is because temporary and permanent Windows handle maps are kept in thread local storage to help maintain protection from simultaneous access from multiple threads. For example, a worker thread cannot perform a calculation and then call a document's UpdateAllViews member function to have the windows that contain views on the new data modified. This has no effect at all, because the map from CWnd objects to HWNDs is local to the primary thread. This means that one thread might have a mapping from a Windows handle to a C++ object, but another thread might map that same handle to a different C++ object. Changes made in one thread would not be reflected in the other.

There are several ways around this problem. The first is to pass individual handles (such as an HWND) rather than C++ objects to the worker thread. The worker thread then adds these objects to its temporary map by calling the appropriate FromHandle member function. You could also add the object to the thread's permanent map by calling Attach, but this should be done only if you are guaranteed that the object will exist longer than the thread.

Another method is to create new user-defined messages corresponding to the different tasks your worker threads will be performing and post these messages to the application's main window using ::PostMessage. This method of communication is similar to two different applications conversing except that both threads are executing in the same address space.

For more information about handle maps, see Technical Note 3. For more information about thread local storage, see Thread Local Storage and Using Thread Local Storage in the Platform SDK.

Communicating Between Threads

MFC provides a number of classes that allow threads to synchronize access to objects to maintain thread safety. Usage of these classes is described in Multithreading: How to Use the Synchronization Classes and Multithreading: When to Use the Synchronization Classes. For more information about these objects, see Synchronization in the Platform SDK.


출처 : http://msdn.microsoft.com/en-us/library/h14y172e%28VS.80%29.aspx#_core_windows_handle_maps

참고 :http://www.webdizen.net/blog/2520


2009. 9. 2. 06:48

CWnd Derived 클래스 Drag and drop 구현하기 (With OLE)

Drag and drop 을 구현할 CWnd-derived 클래스에,

1. CDropTarget 을 상속하여 virtual 메소드를 구현해주면 된다.
2. 생성할 때, register(this)만 호출만 해주면 끝.

자세한 내용은 아래 링크 참고

Drag and Drop Between Any CWnd-Derived Window
(출처 : codeguru)

2009. 7. 4. 09:40

MFC에서 Undo 와 Redo 구현하기

MSDN How Do I: Implement Undo and Redo in MFC 의 내용을 정리한 것입니다. 링크는 맨 아래.

 

이 강좌에서 중요하다고 여겨지는 부분은 Undo Redo 를 구현하기 위해 데이터를 중복해서 저장하는 것을 방지하는 것이다. 그래서 이 강좌에서는 효율적으로 메모리를 사용하기 위해, 전체 Array 를 복사하는 것이 아니라 꼭 필요한 데이터만 백업해 놓고, 그 데이터를 이용하여 Undo/Redo 를 구현하였다. 동시에 Undo Redo 의 제한을 가하여 과다하게 메모리가 사용되는 것을 금지하였다.

 

강좌에서 사용된 예제는 마우스가 클릭될 때마다 그 지점에 원을 그리는 것이다. 그 코드는 아래와 같다.

 

Doc 클래스(CDocument를 상속)에 먼저 Array를 하나 선언하고,

 

// Attributes

public:

        CArray<CPoint> m_Points;

 

Doc 클래스에 AddPoint 함수를 추가한다.

 

void AddPoint(CPoint point)

{

        m_Points.Add(point);

 

        UpdateAllViews(NULL);

}

 

View 클래스에 마우스 클릭시 점을 추가하는 코드와 그리는 코드를 추가한다.

 

void CUndoRedoView::OnLButtonDown(UINT nFlags, CPoint point)

{

        CUndoRedoDoc* pDoc = GetDocument();

        pDoc->AddPoint(point);

 

        CView::OnLButtonDown(nFlags, point);

}

 

void CUndoRedoView::OnDraw(CDC* pDC)

{

        CUndoRedoDoc* pDoc = GetDocument();

        ASSERT_VALID(pDoc);

        if (!pDoc)

               return;

 

        // TODO: add draw code for native data here

        for (int i = 0; i < pDoc->m_Points.GetCount(); i++)

        {

               CPoint& pt = pDoc->m_Points[i];

               pDC->Ellipse(pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);

        }

}

 

이제 본격적으로 Undo/Redo를 구현해보자.

 

Undo/Redo 와 관련된 정보를 저장하는 CAction 클래스를 클래스를 정의한다. 이 클래스의 인스턴스는 CList 에 저장되므로 복사생성자 또한 구현한다.

 

class CAction

{

public:

        enum Actions

        {

               actionAdd,

               actionDelete,

               actionMove

        };

 

        CAction()

        {

        }

 

        CAction(CAction& action)

        {

               m_nAction = action.m_nAction;

               m_nInfo = action.m_nInfo;

               m_Point = action.m_Point;

        }

 

        int m_nAction;

        int m_nInfo;

        CPoint m_Point;

};

 

Doc Undo/Redo 를 위한 리스트 멤버 변수를 추가한다.

 

protected:

        CList<CAction> m_Undo;

        CList<CAction> m_Redo;

 

이제 AddPoint 를 수정하고, Undo, Redo, CanUndo, CanRedo 함수를 구현한다.

 

#define MAX_UNDO 50

 

void AddPoint(CPoint point)

{

        // Record action for undo

        CAction action;

        action.m_nAction = CAction::actionAdd;

        action.m_nInfo = (int)m_Points.Add(point);

        action.m_Point = point;

        m_Undo.AddHead(action);

        while (m_Undo.GetCount() > MAX_UNDO)

               m_Undo.RemoveTail();

 

        // Redo sequence

        m_Redo.RemoveAll();

 

        UpdateAllViews(NULL);

}

 

bool CanUndo()

{

        return (m_Undo.GetCount() > 0);

}

 

void Undo()

{

        CAction action = m_Undo.RemoveHead();

        switch (action.m_nAction)

        {

               case CAction::actionAdd:

                       m_Points.RemoveAt(action.m_nInfo);

                       m_Redo.AddHead(action);

                       while (m_Redo.GetCount() > MAX_UNDO)

                              m_Redo.RemoveTail();

                       break;

        }

        UpdateAllViews(NULL);

}

 

bool CanRedo()

{

        return (m_Redo.GetCount() > 0);

}

 

void Redo()

{

        CAction action = m_Redo.RemoveHead();

        switch (action.m_nAction)

        {

               case CAction::actionAdd:

                       m_Points.Add(action.m_Point);

                       m_Undo.AddHead(action);

                       while (m_Undo.GetCount() > MAX_UNDO)

                              m_Undo.RemoveTail();

                       break;

        }

        UpdateAllViews(NULL);

}

 

여기서 주의깊게 봐야 할 부분은 AddPoint 함수내에서 m_nInfo 에 저장되는 값이다.

 

action.m_nInfo = (int)m_Points.Add(point);

 

CArray<>::Add 에 의해 반환되는 값은 추가된 엘리먼트의 인덱스값이다. 이 값은 Undo 가 수행될 때 이 인덱스를 이용하여 Array 에서 해당 값을 삭제한다.

 

m_Points.RemoveAt(action.m_nInfo);

 

이처럼 Redo/Undo 시에 Doc 에 존재하는 전체 데이터의 Snapshot 을 가지는 게 아니라, 최소한의 데이터만으로 구현하는 것이 젤 중요한 포인트일 것이다.

 

참고 : http://msdn.microsoft.com/ko-kr/visualc/cc948995(en-us).aspx

2009. 6. 29. 20:43

MDI에서 Subclassing 활용

MDI에서 Child Frame 을  생성하는 등의 일을 하는 CMDIFrameWnd의 멤버 m_hWndMDIClient 의 Window Procedure 를 재정의하면 WM_MIDCREATE 메시지를 재정의할 수도 있다.

CMDIFrameWnd 의 OnCreate 에서
 LRESULT FAR PASCAL SubClassFunc2(HWND hWnd,UINT Message,WPARAM wParam,
                                 LPARAM lParam);
FARPROC lpfnOldWndProc2 = NULL;

....

CMainFrame::OnCreate()
{
if(lpfnOldWndProc2 == NULL && m_hWndMDIClient != NULL)
        lpfnOldWndProc2 = (FARPROC)SetWindowLong(m_hWndMDIClient,    GWL_WNDPROC, (DWORD) SubClassFunc2);
}


그리고 핸들러는 다음과 같다.

 LRESULT FAR PASCAL SubClassFunc2(   HWND hWnd,
                                UINT Message,
                                WPARAM wParam,
                                LPARAM lParam)
{
    if( Message == WM_MDICREATE)
        ASSERT(FALSE);

    return CallWindowProc((WNDPROC)lpfnOldWndProc2, hWnd, Message, wParam,
        lParam);
}

OnDestroy 에서 다시 원본 프로시저를 반환하는 것은 좋은 습관일 것이다.

 void CMainFrame::OnDestroy()
{
SetWindowLong(GetSafeHwnd(), GWL_WNDPROC, (DWORD) lpfnOldWndProc);
...
}



이런 식으로 SubClassing 을 활용하면 여러 메시지를 재정의할 수 있을 것이다. 후킹도 이와 비슷하지만 조금 사용하기가 까칠하잖아...

근데 의아해 할지도... 굳이 WM_MDICREATE 를 재정의할 필요가라고? .. ㅎㅎ 그러게...

그치만, CMainFrame 의 멤버로 있는 m_hWndMDIClient 관련 메시지는 재정의할 수 없을거라 생각했다. 주로 MFC를 다루다 보니, 서브클래싱보다는 메시지 핸들러를 이용하고 HWND 보다는 CWnd 를 주로 사용하기 때문이 아닐까. 이런식으로 하나를 오랫동안 다루다 보면 가끔씩 사고가 갑갑하고 막힐때가 있다.
2009. 6. 4. 22:03

MFC 프로젝트에 ATL 지원 추가하기

Adding ATL Support to Your MFC Project 

If you have already created an MFC-based application, then you can add support for the Active Template Library (ATL) easily by running the Add ATL Support to MFC Project Wizard.

NoteNote

This support applies only to simple COM objects added to an MFC executable or DLL project. You can add other COM objects (including ActiveX controls) to MFC projects, but the objects might not operate as expected.

To add ATL support to your MFC project

  1. In Solution Explorer, right-click the project to which you want to add ATL support.

  2. On the shortcut menu, click Add, and then click Add Class.

  3. Select the Add ATL Support to MFC Project icon.

    NoteNote

    This icon is located in the ATL folder in the Categories pane.

  4. When prompted, click Yes to add ATL support.

For more information about how adding ATL support changes your MFC project's code, see Details of ATL Support Added by the ATL Wizard


출처 : http://msdn.microsoft.com/en-us/library/f8bk32xd(VS.80).aspx

2009. 6. 1. 12:13

MFC Technical Notes


Visual Studio 영문 MSDN 링크는 아래와 같다.

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_vclib/html/8aa01d66-0b07-4d0a-a080-4bb00c7baaac.htm

만약 로컬 MSDN에서 찾으려면 Technicals notes 를 입력한 후 MFC를 선택한다.

Microsoft MSND 웹 링크는

http://msdn.microsoft.com/en-us/library/h6h0eact(VS.80).aspx

숫자와 카테고리별로 분류가 잘되어 있다.

OLE와 관련된 내용이 많다. 아래는 그 항목들.

MFC and Windows
MFC Architecture
MFC Controls
MFC Database
MFC Debugging
MFC DLLs
MFC OLE
MFC Resources
MFC Internet