오후 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();
   }

+ Recent posts