C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[950] [VCL] Dll에서 Thread의 Synchronize 사용시 주의할점
장성호 [nasilso] 16014 읽음    2010-02-01 11:48
Q&A게시판에
BCB로 만든 DLL을 VC++에서 사용할때
DLL에서  TThread의 Synchronize함수를 쓸때의 문제점에 대해
질문에 답해주고 자세한 설명을 팁/강좌 게시판에 올린다고 했는데요..
http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_qna&no=60086

찾아보니 제 카페(http://cafe.naver.com/bcbmaster/)에 이미 예전에 올려놓은 글이 있네요

http://cafe.naver.com/bcbmaster/1297
카페에 올린 글 원문을 그대로 copy-paste합니다.
//-----------------------------------------------------------------------------------------
가끔 모듈을 분리하기 위해  Dll로 만드는 경우가 있다.

Dll에서 VCL을 사용할경우 주의할점이 여러가지가 있는데...

그중에 하나 Dll내의 Thread에서 Synchronize함수 사용시
Synchronize 함수가 제대로 동작하지 않는 경우에 대해 얘기하고자 한다.


오늘 델마당에 Dll에서 CPort사용시 RxChar 이벤트가 발생하지 않는다는 질문이 올라왔었다.
참조 http://www.delmadang.com/community/bbs_view.asp?bbsNo=17&bbsCat=0&st=&keyword=&indx=415729&keyword1=&keyword2=&page=1

관련하여 원인을 찾아본 결과
원인은  Dll내의 Thread에서 Synchronize함수에 있었다.


[Synchronize 함수 동작 원리]
  먼저 Synchronize함수가 어떻게 동작하는지 살펴 보게습니다.

   다음은  TThread의 Synchronize 함수입니다.

  procedure TThread.Synchronize(Method: TThreadMethod);
  class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
  

   1 . 먼저 TThreadMethod를 파라메터로 받는 Synchronize 함수에서
      SynchronizeRecord 구조체게 관련 parameter를 설정한후에   

   2.  class procedure 인  TThread.Synchronize 를 호출합니다.
  
  
class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord); 
var 
  SyncProc: TSyncProc; 
begin 
  if GetCurrentThreadID = MainThreadID then 
    ASyncRec.FMethod 
  else 
  begin 
{$IFDEF MSWINDOWS} 
    SyncProc.Signal := CreateEvent(nil, True, False, nil); 
    try 
{$ENDIF} 
{$IFDEF LINUX} 
      FillChar(SyncProc, SizeOf(SyncProc), 0);  // This also initializes the cond_var 
{$ENDIF} 
      EnterCriticalSection(ThreadLock); 
      try 
        if SyncList = nil then 
          SyncList := TList.Create; 
        SyncProc.SyncRec := ASyncRec; 
        SyncList.Add(@SyncProc); 
        SignalSyncEvent; 
        if Assigned(WakeMainThread) then 
          WakeMainThread(SyncProc.SyncRec.FThread); //여기서PostMessage(Application.Handle,WM_NULL... 
{$IFDEF MSWINDOWS} 
        LeaveCriticalSection(ThreadLock); 
        try 
          WaitForSingleObject(SyncProc.Signal, INFINITE); 
        finally 
          EnterCriticalSection(ThreadLock); 
        end; 
{$ENDIF} 
{$IFDEF LINUX} 
        pthread_cond_wait(SyncProc.Signal, ThreadLock); 
{$ENDIF} 
      finally 
        LeaveCriticalSection(ThreadLock); 
      end; 
{$IFDEF MSWINDOWS} 
    finally 
      CloseHandle(SyncProc.Signal); 
    end; 
{$ENDIF} 
    if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException; 
  end; 
end; 

procedure TThread.Synchronize(Method: TThreadMethod); 
begin 
  FSynchronize.FThread := Self; 
  FSynchronize.FSynchronizeException := nil; 
  FSynchronize.FMethod := Method; 
  Synchronize(@FSynchronize); 
end;        



3. 위 코드에 보면 WakeMainThread 함수에서
   Application.Handle에 PostMessage로 WM_NULL을 보낸다.
  
    WakeMainThread(SyncProc.SyncRec.FThread); //여기서PostMessage(Application.Handle,WM_NULL... 


4.  Application의 WindowProc에서 WM_NULL메세지를 받는 부분을 살펴보면...

   아래는  TApplication.WndProc 함수 코드이다.
   대분의 코드를 생략하고 WM_NULL 메세지가 올경우 CheckSynchronize 라는 함수를 호출하였다.

    
procedure TApplication.WndProc(var Message: TMessage); 

  ....
begin 
  try 
    with Message do 
      case Msg of 
          .....


        WM_NULL: 
          CheckSynchronize;  
      else 
        Default; 
      end; 
  except 
    HandleException(Self); 
  end; 
end; 



5. 다음은 CheckSynchronize 함수이다.
   LocalSyncList 리스트에 있는 Synchronize 함수 정보를 하나씩 가져와서 실행하고 잇다.
   실행하고 난뒤에 SetEvent(SyncProc.signal); 로 Synchronize를 호출한 Thread를 wake시켜 준다.

 
function CheckSynchronize(Timeout: Integer = 0): Boolean; 
var 
  SyncProc: PSyncProc; 
  LocalSyncList: TList; 
begin 
  if GetCurrentThreadID <> MainThreadID then 
    raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]); 
  if Timeout > 0 then 
    WaitForSyncEvent(Timeout) 
  else 
    ResetSyncEvent; 
  LocalSyncList := nil; 
  EnterCriticalSection(ThreadLock); 
  try 
    Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList)); 
    try 
      Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0); 
      if Result then 
      begin 
        while LocalSyncList.Count > 0 do 
        begin 
          SyncProc := LocalSyncList[0]; 
          LocalSyncList.Delete(0); 
          LeaveCriticalSection(ThreadLock); 
          try 
            try 
              SyncProc.SyncRec.FMethod; //실제 Synchronize함수 실행
            except 
              SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject; 
            end; 
          finally 
            EnterCriticalSection(ThreadLock); 
          end; 
{$IFDEF MSWINDOWS} 
          SetEvent(SyncProc.signal); 
{$ENDIF} 
{$IFDEF LINUX} 
          pthread_cond_signal(SyncProc.Signal); 
{$ENDIF} 
        end; 
      end; 
    finally 
      LocalSyncList.Free; 
    end; 
  finally 
    LeaveCriticalSection(ThreadLock); 
  end; 
end;        



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



[Dll에 쓰레드에서 Synchronize함수 사용의 문제 되는 경우는?]


   dll에 있는  쓰레드에서 Synchronize함수 사용에 문제가 되는 경우가 어떤경우 일까?

   그것은 다름아닌 

    첫째 . Delphi 또는 CBuilder로 만든 Dll을  같은 버젼의 Delphi나 CBuilder로 만든 exe에서 사용하지 않는경우


     둘째 . Dll이나 Exe모두 같은버젼의 Delphi나 CBuilder로 만든 경우라 하더라도
              컴파일시 Runtime-Package를 사용하지 않은 경우 등이 있다.

     실례)

         1) 델마당의 질문에서와 같이 CPort같은 serial통신 수신 이벤트에 문제가 생긴다.

         2) 또는 delphi/cbuilder 의 기본 tcp/ip socket인 TServerSocket/TClientsocket의 수신이벤트에도 문제가 생긴다.
          TServerSocket/TClientsocket 은 non-blocking소켓으로 data수신시  Synchronize 함수로
          메인쓰레드에서 이벤트를 발생시키기 때문이다.

         3) 기타 위와 비슷한경우는 이루 수도없이 많을것이다.


[문제의 원인]

   이제 문제의 원인을 살펴보자

   앞에 Synchronize의 동작원리를  자세히 설명한 이유도 여기에 있다.  

   위에 Synchronize의 동작원리를 보면  

   WakeMainThread함수에서 다음과 같이 Application.Handle로 WM_NULL 메세지를 PostMessage로 보낸다.

procedure TApplication.WakeMainThread(Sender: TObject); 
begin 
  PostMessage(Handle, WM_NULL, 0, 0); 
end;        


   그런데 DLL의 경우에 Application.Handle이 NULL 또는 nil 이라는데 있다.

    다음은 TApplication클래스의 생성자 함수이다.

    
constructor TApplication.Create(AOwner: TComponent); 
...  생략
begin 
  inherited Create(AOwner); 
   ....   생략


  if not IsLibrary then CreateHandle; 
  

   ...  생략

 
end; 





위 코드르 보면 not IsLibrary  즉  DLL이 아닌 EXE인 경우에만 CreateHandle을 호출한다. 
  즉 Dll에서는 CreateHandle 함수가 호출되지 않고 Application.Handle이 NULL이다.
  Application.Handle이 NULL이니 아무리 PostMessage로 보내도 메세지를 받은 Handle이 없으니

  Synchronize 가 동작하지 않는 것이다.


[해결방법]



   첫째 . dll 및 exe둘다 Runtime-Package 를 사용한다.
         즉 Dll 및 exe둘다 runtime-package를 사용한다는 것은?
         runtime-package를 사용하지 않으면
         exe에서 사용하는 Application객체와 dll에서 사용하는 Application객체가 다른 놈이 된다.
         exe/dll둘다  runtime-package를 사용한다는 뜻은
          VCLxx.bpl 을   동적으로 LoadPakage하여서 dll과 exe에서 같은 vclxx.bpl을 사용하게 된다.
         하지만 만약 둘중에 한쪽이라도 runtime-package를 사용하지 않게되면
         둘중 한쪽은 static-link된   vcl -lib를 사용하게 되게 된다.

         이경우  dll에 있는  쓰레드에서 Synchronize 문제 뿐만 아니라 매우 많은 문제를 야기 시킨다.


   둘째 . Dll에서 Application->CreateHandle(); 을 명시적으로 호출해준다.

        위에 설명했듯이 Application->Handle은  Application->CreateHandle();  함수에서 생성된다.
        명시적으로  Application->CreateHandle(); 을 호출해줄경우 PostMessage로 메세지를 보내고 받을
       윈도우 Handle이 생성되므로 문제가 해결되는 것이다.

   셋째. delphi나 CBuilder로만 project를 할경우 DLL 대시 BPL을 사용하는 방법도 있다.

         BPL은 delphi에서 사용하는 dll로써   bpl로 모듈을 만들면 Runtim-package및 static-link 시에 알아서
        링크해준다. 그래서 exe나 bpl에서 같은 vclxx.bpl 을 사용하게 되믄로 문제가 해결된다.


    만약  Dll이 같은 버젼의 delphi나 CBuilder가 아닌 프로그램으로 만든 exe에서 사용할 목적이라면
    두번째 방법 Application->CreateHandle();을 사용하길 권한다.


그럼...
Gromit [montecristo]   2010-02-01 20:03 X
정말 감사합니다. 많은 도움이 되었습니다.

+ -

관련 글 리스트
950 [VCL] Dll에서 Thread의 Synchronize 사용시 주의할점 장성호 16014 2010/02/01
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.