오후 12:23 00-02-15
조경민 bro@shinbiro.com

ATLCOM 객체에서 두번째 스레드에서 Fire하기 에러
http://support.microsoft.com/support/kb/articles/q196/0/26.asp
==============================================================================
PRB: Firing Event in Second Thread Causes IPF or GPF

--------------------------------------------------------------------------------
The information in this article applies to:

Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, version 6.0

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


SYMPTOMS
In Visual Basic, an event fired by an ActiveX component from a thread other than the main thread will sometimes cause an Invalid Page Fault (IPF) or General Protection Fault (GPF). Usually it seems to work fine in the Visual Basic IDE but fails when it runs as a standalone EXE.



CAUSE
Visual Basic uses an apartment threading model. Cross-thread function calls need to be marshalled. Visual Basic does not support events fired directly from any thread other than the main thread created by a Visual Basic project without marshalling.



RESOLUTION

Method 1
Marshall the event firing code:
The thread being spun off needs to call CoInitialize/CoUnInitialize.


The sink interface being called back on in the thread must be marshalled over to that thread with CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream.


Method 2
Instead of firing an event from a secondary thread, post a message back to the main thread and fire the event there. An example of this method is given later in this article.



STATUS
This behavior is by design.



MORE INFORMATION

Steps to Reproduce Behavior
The following example first creates an ATL project, then a Visual Basic 6.0 project. It then uses these to demonstrate the proper way to have a secondary thread request that the primary thread fire an event.
Steps to Create an ATL Project with Visual C++
Create a new ATL COM AppWizard Project named MyAtlDll and keep the default settings.


In the class view, right-click the project name and select New ATL Object. Select Simple Object in the displayed dialog box and click Next. Type in "MyAtl" as the C++ short name. Click the Attribute tab and select Support Connection Points. Click OK and a new ATL object is added.


In the class view, right-click the IMyAtl and choose Add Method. Type "InitTask" in the Method Name text box and "[in] long number" in the parameters text box. Click OK.


In the class view, right-click _IMyAtlEvents and select Add Method. Select "void" from return type drop-down list box. Type "TaskFinished" in the Method Name text box and "[in] long result" in the parameters text box. Click OK.


In the class view, right-click CMyAtl and choose Add member variable. Type "long" in the variable type text box and "m_number" in the variable name text box.


Build the project to generate the type library needed for step 10.


In the class view, expand and double-click CMyAtl -> IMyAtl -> InitTask. Edit the InitTask function to make it appear as follows:


      STDMETHODIMP CMyAtl::InitTask(long number)
      {
         HANDLE hThrd;
         DWORD tid;

         m_number = number;
         if((hThrd = CreateThread(
                 0,
                 0,
                 (LPTHREAD_START_ROUTINE)justDoIt,
                 (void *)this,
                 0,
                 &tid)) == NULL)
         {
            //error handling here
         }
         CloseHandle(hThrd);
         return S_OK;
      }

Add the following code to the MyAtl.cpp file:


      DWORD WINAPI justDoIt(LPVOID lpParameter)
      {
         CMyAtl *myAtl = (CMyAtl*)lpParameter;

         long result;
         for (int i = 1; i <= myAtl->m_number; ++i)
            result = i * 2;

         myAtl->Fire_TaskFinished(result);

         return 0;
      }

Add the following code right above the line "#endif //__MYATL_H_" in MyAtl.h file:


      DWORD WINAPI justDoIt(LPVOID lpParameter);

In the class view, right-click CMyAtl and select Implement Connection Point. Select _IMyAt-Events in the displayed dialog box. Click OK.


Build the ATL project and the control will be registered automatically.


Steps to Create the Visual Basic 6.0 Project
Create a new Standard EXE project. Form1 is created by default.


Choose References from the Project menu, select "MyAtlDll 1.0 Type library," and click OK.


Add a CommandButton to the form and keep the default name (command1).


Add the following code to the code window of form1:


      Option Explicit
      Private WithEvents vbATL As MYATLDLLLib.MyAtl

      Private Sub Command1_Click()
         vbATL.InitTask 11111
      End Sub

      Private Sub Form_Load()
         Set vbATL = New MYATLDLLLib.MyAtl
      End Sub

      Private Sub vbATL_TaskFinished(ByVal result As Long)
         MsgBox result
      End Sub

Press the F5 key to run the project. Click Command1 and you will get 22222 in a message box.


Build the project to be an EXE and run the EXE outside the IDE. You will get an error message when you click the CommandButton.

Note that it may work sometimes, but it does not work consistently.

To fix the problem in this specific sample, you need to derive the CMyAtl from CWindowImpl, and add a message map. Make sure the window is hidden. Now, instead of firing an event from the secondary thread, you can post a message to the main thread, and fire the event in the message handler.
Step by Step Example
Using the ATL project created in the steps above, add the following line:
      #include <atlwin.h>

after the line:
      #define _MYATL_H

in the MyAtl.h file.


Add the line:
      #define WM_TASK_FINISH  (WM_USER + 101)

before the line:
      class ATL_NO_VTABLE CMyAtl :

in the MyAtl.h file.


Add CWindowImpl to one of the parent classes of CMyAtl by adding the following line:
      public CWindowImpl<CMyAtl>,

right after the line:
      class ATL_NO_VTABLE CMyAtl :

in the file MyAtl.h.


Add the following code to the CMyAtl definition part of the MyAtl.h file:
      public:
         DECLARE_WND_CLASS("MyAtl")

         BEGIN_MSG_MAP(CMyAtl)
            MESSAGE_HANDLER(WM_TASK_FINISH, OnTaskFinished)
         END_MSG_MAP()
      public:
         LRESULT OnTaskFinished(UINT uMsg, WPARAM wParam,
                        LPARAM lParam, BOOL& bHandled)
         {
            Fire_TaskFinished((long)wParam);
            return 0;
         }

        HRESULT FinalConstruct()
        {
           RECT rect;
           rect.left=0;
           rect.right=100;
           rect.top=0;
           rect.bottom=100;

           HWND hwnd = Create( NULL, rect, "MyAtlWindow", WS_POPUP);

           if (hwnd)
               return S_OK;
           else
               return HRESULT_FROM_WIN32(GetLastError());
        }

        void FinalRelease()
        {
           if (m_hWnd != NULL)
               DestroyWindow();
        }




Change the following line:
      myAtl->Fire_TaskFinished(result);

in the function
      DWORD WINAPI justDoIt(LPVOID lpParameter)

in the file MyAtl.cpp, to be:
      myAtl->PostMessage(WM_TASK_FINISH,result,0);




Rebuild the ATL project.


Run the Visual Basic project from the IDE and as an EXE. Note that the event fires just fine either way.





REFERENCES
For information on firing an event from a second thread and information on why the PostMessage method is recommended, please see the following article in the Microsoft Knowledge Base:




Q157437 : FILE: Fireev.exe Fires Events from a Second Thread



Additional query words:

Keywords : kbActivexEvents kbATL kbThread kbVBp kbVBp600 kbVC600 kbGrpVB kbCodeSam
Version : WINDOWS:6.0
Platform : WINDOWS
Issue type : kbprb



Last Reviewed: January 5, 2000
ⓒ 2000 Microsoft Corporation. All rights reserved. Terms of Use.


  

Article ID: Q196026

Last Reviewed:
January 5, 2000

Provided by Microsoft Product Support Services.




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

      Did the information in this article help answer your question?
Yes
No
Did not apply

Please provide additional comments about this information. If you require a response or technical support, please click Contact Us.
(255 character max)


두번째 스레드시 왜 윈도우 디라이브드한 postmessage방식이 좋은가
및 예제
http://support.microsoft.com/support/kb/articles/Q157/4/37.ASP
========================================================================================


Fireev.exe Fires Events from a Second Thread

--------------------------------------------------------------------------------
The information in this article applies to:

Microsoft Visual C++, 32-bit Editions, versions 4.0, 4.1, 4.2, 5.0, 6.0

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


SUMMARY
MFC-based ActiveX controls typically fire their events from the same thread that implements the sink interface of the container that the events are being fired to.

Sometimes, it is desirable to start a second thread in an ActiveX control that will fire events to the container. Because MFC ActiveX controls use the Apartment-threading model, special consideration must be taken when firing events from a secondary thread.



MORE INFORMATION
The following files are available for download from the Microsoft Download Center. Click the file names below to download the files:


Fireev.exe
http://download.microsoft.com/download/vc60pro/File/1/NT4/EN-US/Fireev.exe

For more information about how to download files from the Microsoft Download Center, please visit the Download Center at the following Web address

http://www.microsoft.com/downloads/search.asp
and then click How to use the Microsoft Download Center.

NOTE: Use the -d option when running Fireev.exe to decompress the file and Re-create the proper directory structure.

An MFC-based ActiveX control supports events by implementing the IConnectionPointContainer and IConnectionPoint interfaces, as well as supplying information about its event interface in its type information. When an MFC-based ActiveX control is embedded in a container that supports events, that container will dynamically construct a sink interface that has all of the methods specified in the control's type information for its event interface. Once the container constructs its sink interface, it will pass a pointer to that interface to the ActiveX control. The ActiveX control will use its implementation of IConnectionPoint to communicate through the now-hooked-up sink interface that was constructed by the container. This sample will demonstrate how to call methods of the container's sink interface from a second thread.

The two most important things to consider when starting a new thread to fire events from an ActiveX control are:


MFC-based ActiveX controls are in-process objects (implemented in a DLL).


MFC-based ActiveX controls use the Apartment-threading model.


The Apartment-threading model specifies that all threads that want to use OLE services must initialize OLE in their thread prior to using OLE services. Also, if a thread wants to use a pointer to an interface that is either implemented by a different thread of the same process or has been previously marshaled to a different thread of the same process, that pointer must be marshaled to the requesting thread. In the Apartment- threading model, hidden windows are created to synchronize requests from other threads to the thread being called. This means that all communication between threads will be done by using hidden windows and Windows messages in the Apartment model.

There are two possible ways to fire events from a second thread in an ActiveX control (or any other in-proc server that implements connection points) under the Apartment-threading model. The first is to make the interface call from the second thread by calling the event sink's method from the second thread. The second is to have the second thread post a message to the first thread when it is ready to fire the event, and have the first thread fire the event.

The first method mentioned above is not the optimal way to fire an event from a second thread: For the second thread to fire the event, it must make a call on an interface pointer that is held by the thread that initialized the control. Therefore, the interface pointer that will be used to fire the event must be marshaled to the second thread that will cause OLE to set up hidden windows to communicate between the threads. Windows messages will be used to communicate between the threads.

The MFC ActiveX control framework is not set up to fire events from a second thread easily. It is possible to override the default MFC code to marshal the sink interface pointers to the second thread, but this is not recommended because Windows is going to create hidden windows and use PostMessage to send messages between threads anyway. It makes more sense for the second thread to post its own messages to the first thread and have that thread fire the event. This code can be set up easily in an MFC ActiveX control. Use the following steps to add a second thread that fires events to the container in an MFC ActiveX control:


Create your control project.


Using ClassWizard, add a method that will start a second thread and return. The following code shows a method that starts a second thread and returns immediately in an MFC ActiveX control. A global function to serve as the second thread's work function is also declared:
   LONG ThreadProc(LPVOID pParam);

   void CFireeventCtrl::StartLengthyProcess()
   {
     DWORD dwID;
     HANDLE threadHandle = CreateThread(NULL,NULL,
                           (LPTHREAD_START_ROUTINE)ThreadProc,
                           (LPVOID)this, NULL, &dwID);
     TRACE("Started the thread %x\n",dwID);
   }



Add any events you wish to fire from the second thread using ClassWizard.


Define a custom message to be sent from the second thread. Also, add a message map entry to the control's message map that will call the message-handling function when the custom message is received. This message handler will fire the desired event. A sample of how to do this in an MFC ActiveX control follows:
      //define a custom message:
      #define WM_THREADFIREEVENT WM_USER+101

      //add an entry for the message to the message map of the control
      BEGIN_MESSAGE_MAP(CFireeventCtrl, COleControl)
      //{{AFX_MSG_MAP(CFireeventCtrl)
      //}}AFX_MSG_MAP
      ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
      ON_MESSAGE(WM_THREADFIREEVENT,OnFireEventForThread) //custom handler
      END_MESSAGE_MAP()

      //add a handler for the custom message that will fire our event
      LRESULT CFireeventCtrl::OnFireEventForThread(WPARAM wParam,
          LPARAM lParam)
      {
        FireLengthyProcessDone();
        return TRUE;
      }



In the thread procedure for the second thread, when it's time for the second thread to fire the event, post the custom message defined in step 3 back to the main thread. The event will be fired. The following code demonstrates:
      LONG ThreadProc(LPVOID pParam)
      {
        Sleep(2000); //simulate lengthy processing
        CFireeventCtrl *pCtrl = (CFireeventCtrl*)pParam;
        PostMessage(pCtrl->m_hWnd,
                    WM_THREADFIREEVENT,
                    (WPARAM)NULL,
                    (LPARAM)NULL);
        return TRUE;
      }



Notice in the sample code above that the window handle of the ActiveX control is used as the target to which the message from the second thread will be posted. In most cases, an MFC based ActiveX control will be in-place active when its methods are called and will have a window handle. It is possible, however, for an ActiveX control to not have a window handle, such as in the case of a window-less control. One way to work around this is to create a hidden window that could be used to communicate between threads. That window could then be destroyed when the thread terminated. The FIREEV sample has code that is commented out in its StartLengthyProcess method and ThreadProc thread work function that demonstrates creating a window wrapped by the CMyWindow class that serves this purpose. Also notice that PostMessage is used instead of PostThreadMessage. MFC's message maps are set up to intercept thread messages in CWinThread derived classes only. Because MFC ActiveX controls are derived from CWnd, they will not have messages sent with PostThreadMessage routed to them. Messages sent with PostThreadMessage will have a NULL hWnd.

Additional query words: ActiveX OCX

Keywords : kbfile kbole kbCtrl kbMFC kbVC kbVC400 kbVC410 kbVC420 kbVC500 kbVC600 OCX
Version : winnt:4.0,4.1,4.2,5.0,6.0
Platform : winnt
Issue type :



Last Reviewed: February 4, 2000
ⓒ 2000 Microsoft Corporation. All rights reserved. Terms of Use.


  

Article ID: Q157437

Last Reviewed:
February 4, 2000

Provided by Microsoft Product Support Services.




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

      Did the information in this article help answer your question?
Yes
No
Did not apply

Please provide additional comments about this information. If you require a response or technical support, please click Contact Us.
(255 character max)

'KB > MFC/Win32' 카테고리의 다른 글

#pragma  (0) 2004.03.19
AUTOMATION 자동화  (0) 2004.03.19
ATL COM 반환값 만들기  (0) 2004.03.19
ATL 객체포인터 참조하기  (0) 2004.03.19
argv 얻어오기  (0) 2004.03.19

+ Recent posts