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

C++빌더 FAQ
C++Builder Programming FAQ
[52] [^ㅅ^] 이벤트 싱크에 대해서
에보니.$ㅅ$ [mortalpain] 18838 읽음    2002-08-12 22:15
tlb.jpg 24.8KB 디벨로퍼가이드의 예제소스 타입라이브러리
이 질문은 포럼의 어떤 분이 게으르고 무책임하고 허접한데 한자리 꿰차고 있는 =ㅅ= 에게 물은

내용인데 =ㅅ= 가 돈내놔하며 답변한 것입니다.... =ㅅ=;;


^0^ 님의 말 :

=ㅅ= 야 이벤트 싱크가 모야?


=ㅅ=;; 의 말 :

쉽게 생각해서 이벤트 싱크란 넘은 서버가 클라한테 보내는 일종의 신호를 처리하는 이벤트 인터페

이스를 가리킵니다. ㅡ0ㅡ

기본원리는 클라가 서버 객체(여기선 타입라이브러리가 되겠져 --- 클라도 가지고 서버도 가져야

됨 왜? 일종의 객체구조고 당근 두놈다 알아야 통신이 되니 일종의 프로토콜 역할을 하는거져)를

생성해서 원하는 메소드를 호출하면(당연 이것은 어떤 일을 하는 기본 원리져) 그것이 프로세스던

머신이던 어떤 경계를 넘어서 서버에 가게됩니다 쉽게 말해 RPC 라고 부르는 것을 통해서 어떤 일

을 하게 되겠죠.

하지만 좀더 자세하게 말해 클라와 서버 사이에 어떤 경계가 존재한다면 클라에서는 저 경계 넘어

에 있는 서버와 통신할 일종의 가짜 서버의 역할을 하는 프록시나 스텁을 생각해 볼 수 있고 서버에

서는 원격에 있는 클라의 역할을 하는 스켈레톤이 있습니다. 이것에 대한 좀더 자세한 사항을 여기

선 설명할 순 없고 나중에 책을 사셔서 공부공부

암튼 클라의 호출을 받은 서버는 무슨 일을 해주고 나 일 다했다 던지 야 일 실패다~ 배째하는 반응

을 클라에게 던져 줘야 합니다.

(하지만 여기서 중요한 것은 클라에서 받을 필요가 없다면 굳이 클라에게 던져줄 필요가 없겠죠?

이 때는 이벤트 싱크가 아무짝에도 쓸모가 없슴돠..... =ㅅ=;;)

암튼 이벤트 싱크란 넘은 클라에 위치해 있다가 서버가 던져주는 어떤 메소드의 호출 후의 콜백을

처리하는 일을 하는데~ 클라의 입장에서 본다면 이놈들은 당연히 이벤트 함수의 형태가 되야 되겠

져? 서버가 어떤 일이 준비되었다던지 일을 끝냈다던지...

그리고 이놈들은 어떻게 만들어 주냐하면 클라와 서버가 사이좋게 하나씩 갖는 타입라이브러리를 맹

글때 미리 만들어줘야 됩니다. 주로 타입 라이브러리 상에서는 파란색 다이아몬드 결정의 모양을 하

고 그아래에 On ~ 무시기로 시작되는 함수덜이 바로 그넘덜입니다. 일반적으로 듀얼 인터페수 -- 설

명하면 한참 걸리니 걍 타입 라이브러리로 마샬링이 가능한 대신 디따 느린 웹하는 분들이 사용하시

는 액티브 엑수 디엘엘 들의 메소드 인터페수 로 알아두고 --- 를 지원하는 서버 객체를 맹글때는

주로 다이아몬드 =ㅅ=;; --- 정식용어는 디시핀터페수 --- 에서 뉴메소드하고 On 무시기로 시작하

는 넘들을 만들고 걍 빠른 커스텀 인터페수를 지원하는 서버객체를 맹글때는 --- 일반 어플리케이션

에서 사용한다. 이때는 프록시 스텁을 안맹글면 서로 못알아 듣는다 --- 피꼬치 핫도그 =ㅅ=;; ---

정식용어는 인터페이스 --- 에서 뉴 메소드하고 On 무시기 함수를 맹글면 됩니다.

그래서 클라에서 메소드를 호출하기 위해 --- 어떤 일을 하기위해선 메소드를 호출하져 --- 서버와

나눠가진 객체를 생성했을때 미리 클라에서 맹근 타입라이브러리에 있는 같은 형의 이벤트 함수를

이벤트 싱크에 쳐 넣어 버리는 거죠 =ㅅ=;; --- 음 정식용어를 머라 하더라 함수 트리거링이라 해두

자 (지맘대로 용어를 맹그는 허접 =ㅅ=;;)

위에서 말한 내용은 첨부하는 그림을 자세히 보시면 On 무시기로 시작되는 서버 객체의 메소드들이

랑 이름이 같은 넘들이 있는 것을 볼 수 있습니다.... =ㅅ=;;

그러니까 디벨로퍼 가이드의 예제 소스를 빌리면 클라이언트에서

타입라이브러리 만들때 선언했던 함수 --- 서버 객체의 함수가 되겠죠. 실제로 이넘이 서버에서

호출되어 작업이 수행됩니다 --- 와 같은 함수를 하나 맹급니다.

예를들면

void __fastcall TMainForm::OnZodiacSignReady(BSTR Sign)
{
    .............................................
}

void __fastcall TMainForm::OnDetailedZodiacSignReady(
                    TDetailedZodiacSign& DetailedSign)
{
    .............................................
}

이렇게요 분명 클라의 메인폼의 멤버함수들임을 볼 수 있습니다.

자 이렇게 선언과 바디를 완성한 후에 이 놈을 클라의 이벤트 싱크에 붙여야 겠져.

이쯤되면 당연히 클라에선 서버와 똑같은 타입라이브러리가 기들어 가야되고

그 타입라이브러리의 객체 형을 가지는 클래스 자료형이 적어도 하나 존재한다는 말이됩니다.

예를 들어 다음의 헤더처럼여...

#include "ZodiacSink.h"
#include "ZodiacCustomSink.h"
#include "ZodiacServer_TLB.h"
//---------------------------------------------------------------------------
class TMainForm : public TForm
{
__published:    // IDE-managed Components

. 이부분은 게으른 =ㅅ= 의 소환수인 빌더가 만든다.

private:    // User declarations
  TCOMIZodiac FZodiac;
  TZodiacSink FZodiacSink;
  TZodiacCustomSink FZodiacCustomSink;
protected:
  void __fastcall OnZodiacSignReady(BSTR Sign);
  void __fastcall OnDetailedZodiacSignReady(TDetailedZodiacSign& DetailedSign);

public:        // User declarations
  __fastcall TMainForm(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TMainForm *MainForm;

이때 인클루드해서 기들어간 헤더들 중에는 각각의 인터페이스 종류에 따라 듀얼 인터페수

이벤트 싱크, 커스텀 인터페수 이벤트 싱크가 기들어 간게 보이는데 잠깐 이넘덜을 잡아서

배째보면... =ㅅ=;;

#if !defined(ZODIACSINK_H__)
#define ZODIACSINK_H__

#include <atlvcl.h>
#include <atlbase.h>
#include <atlcom.h>
#include <ComObj.HPP>
#include <utilcls.h>
#include "ZodiacServer_TLB.h"

typedef void __fastcall (__closure * TZodiacSignReadyEvent)(BSTR Sign);

//---------------------------------------------------------------------------
// Create a class that handle IZodiacEvents
class TZodiacSink :
   public TEventDispatcher<TZodiacSink, &DIID_IZodiacEvents>
{
protected:
  // Event field
  TZodiacSignReadyEvent FOnZodiacSignReady;

  // Event dispatcher
  HRESULT InvokeEvent(DISPID id, TVariant* params)
  {
      if ((id == 1) && (FOnZodiacSignReady != NULL)) // OnZodiacSignReady
        FOnZodiacSignReady(params[0]);
      return S_OK;
  }

  // Reference to the event sender
  CComPtr<IUnknown> m_pSender;

public:
  __property TZodiacSignReadyEvent OnZodiacSignReady =
    { read = FOnZodiacSignReady, write = FOnZodiacSignReady };

public:
  TZodiacSink() :
     m_pSender(NULL),
     FOnZodiacSignReady(NULL)
  {
  }

  virtual ~TZodiacSink()
  {
     Disconnect();
  }

  // Connect to Server
  void Connect(IUnknown* pSender)
  {
     if (pSender != m_pSender)
        m_pSender = pSender;
     if (NULL != m_pSender)
       ConnectEvents(m_pSender);
  }

  // Disconnect from Server
  void Disconnect()
  {
    if (NULL != m_pSender)
    {
      DisconnectEvents(m_pSender);
      m_pSender = NULL;
    }
  }
};

#endif //ZODIACSINK_H__

걍 타입라이브러리 끌어다 이벤트 연결하고 끊어주는 별다를게 없는 클래스 인것을

알수 있슴돠. 이벤트를 인보크하는 넘이 있다는 사실이 중요하져,....

요거이 걍 듀얼 인터페수 이벤트 싱크구여. 커스텀 이벤트 싱크를 함 배째보면

#if !defined(ZODIACCUSTOMSINK_H__)
#define ZODIACCUSTOMSINK_H__

#include <atlvcl.h>
#include <atlbase.h>
#include <atlcom.h>
#include <ComObj.HPP>
#include <utilcls.h>
#include "CustomSinks.h"
#include "ZodiacServer_TLB.h"

typedef void __fastcall (__closure * TDetailedZodiacSignReadyEvent)
    (TDetailedZodiacSign& DetailedSign);

//---------------------------------------------------------------------------
// Create a class that receive IDetailedZodiacEvents
class ATL_NO_VTABLE TDetailedZodiacSinkImpl :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<TDetailedZodiacSinkImpl, &CLSID_NULL>,
  public IDetailedZodiacEvents
{
public:
  TDetailedZodiacSinkImpl() :
    FOnDetailedZodiacSign(NULL)
  {
  }

  DECLARE_THREADING_MODEL(otApartment);

BEGIN_COM_MAP(TDetailedZodiacSinkImpl)
  COM_INTERFACE_ENTRY(IDetailedZodiacEvents)
END_COM_MAP()

protected:
  // Event field
  TDetailedZodiacSignReadyEvent FOnDetailedZodiacSign;

public:
  __property TDetailedZodiacSignReadyEvent OnDetailedZodiacSign =
    { read = FOnDetailedZodiacSign, write = FOnDetailedZodiacSign };

// IDetailedZodiacEvents
public:
  STDMETHOD(OnDetailedZodiacSignReady(TDetailedZodiacSign* DetailedSign))
  {
    if (FOnDetailedZodiacSign != NULL)
      FOnDetailedZodiacSign(*DetailedSign);
    return S_OK;
  }

};

typedef TCustomSink<TDetailedZodiacSinkImpl,
    IDetailedZodiacEvents, &IID_IDetailedZodiacEvents>
      TZodiacCustomSink;

#endif //ZODIACCUSTOMSINK_H__

이넘은 이렇게 생겼는데.... 자세하게 볼 것은 듀얼인터페수의 경우

이벤트 싱크는 이벤트 디스패쳐 클래스에서 상속을 받고

커스텀 인터페수의 경우 이벤트 싱크는 직접 타입라이브러리에서 선언된

이벤트와 컴 객체 클래스에서 직접 상속을 받으며 일종의 메소드(함수) 포인터 팩인

VTable의 클래스로 선언된다는 것입니다.

그리고 서버객체의 인터페이스 엔트리 위에다가 이벤트 싱크를 맵시켜서 쳐며거 버리네요~ =ㅅ=;;

코드가 복잡하고 길고 이상하다고 쫄면 절대 안됩니다. 일단 맞장은 충분히 뜹시당

글고 안되면 빠떼루 줘야 합니당.

또 한가지는 커스텀 싱크 헤더를 인클루드 하고 있는데 이넘도 잡아서 배째 봅니당

template <class Base, class Interface, const IID* piid = &__uuidof(Interface)>
class TCustomSink : public Base
{
private:
  CComPtr<IUnknown> m_ptrSender; // Events sender
  DWORD m_dwCookie;              // Connection cookie

public:
  TCustomSink() :
    m_dwCookie(0) { }

  virtual ~TCustomSink()  { Disconnect(); }

  // IUnknown implementation:
  STDMETHOD_(ULONG, AddRef)() { return 1; }
  STDMETHOD_(ULONG, Release)() { return 1; }
  STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
    { return _InternalQueryInterface(iid, ppvObject); }

public:
  // Methods for connecting/disconnecting from the event sender
  HRESULT __fastcall Connect(IUnknown* pSender)
  {
    HRESULT hr = S_FALSE;

    if (pSender != m_ptrSender)
    {
       m_ptrSender = pSender;
       if (m_ptrSender != NULL)
       {
         CComPtr<IUnknown> ptrUnk;
         QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&ptrUnk));
         hr = AtlAdvise(m_ptrSender, ptrUnk, *piid, &m_dwCookie);
       }
    }

    return hr;
  }

  HRESULT __fastcall Disconnect()
  {
    HRESULT hr = S_FALSE;

    if ( (m_ptrSender != NULL) &&
           (0 != m_dwCookie) )
    {
      hr = AtlUnadvise(m_ptrSender, *piid, m_dwCookie);
      m_dwCookie = 0;
      m_ptrSender = NULL;
    }

    return hr;
  }
};

뭐 가장 기본적인 인터페이스들 --- 위의 퍼블릭의 3마리 --- 과 듀얼 인터페이스의

이벤트 싱크에서 봤던 연결/끊기 함수들 밖엔 없져?

그런데 이것들은 모양이 좀 다르고 COM 의 가장 기본적인 인터페수로 연결을 얻고

해제하는 과정을 보여줍니다. 듀얼 인터페수의 경우엔 이것들이 밖으로 안나와 있었죠?

=ㅅ=;; 같은 썩은 돌대가리덜을 위해 저절로 다 알아서 해줄라고 감춘 겁니다... =ㅅ=;;

하하 머 굳이 커스텀 인터페수와 구분해 말하면 쌍불알의 외xx 와 외불알의 쌍xx 의 차이라고나

할까요? 오르가즘의 차이입니다. 암래도 후자가 오르가즘의 효율을 극대화할 겁니다.

음 비유가 좀 거시기 했군.... =ㅅ=;;

암튼 배짼넘들을 다시 집어주고 활동하게 방생을 해줍니다. 그래도 그넘들은 잘 살겁니다... =ㅅ=;;

암튼 열역학 제 2법칙을 어겨가며 =ㅅ= 가 얻은 것은 이벤트 싱크 그넘도 단지 함수 포인터를 가지

고 프로세스 경계를 넘어 벌렸다 닫았다 그리고 왔다갔다질을 잘하는 클래스란 것입니다... =ㅅ=;;

자 그럼 이넘들을 배째기 전으로 돌아가서 헤더 선언을 보면 분명히 클라의 헤더 선언에는

이벤트 객체형의 private 에 자료멤버 형이 기들어가 있는 것을 볼 수 있져

private:    // User declarations
  TCOMIZodiac FZodiac;    <--- 이놈은 타입라이브러리 상의 서버객체
  TZodiacSink FZodiacSink;
  TZodiacCustomSink FZodiacCustomSink;

자 그럼 미리 만들어 놓은 클라의 클래스 멤버 함수를 이벤트 싱크에 쳐 갔다 넣고

연결 시키라고 외쳐주면 됩니다.... =ㅅ=;;

void __fastcall TMainForm::FormCreate(TObject *Sender)
{
  FZodiac = CoZodiac::Create();

  // Connect dispatch events:
  FZodiacSink.OnZodiacSignReady = OnZodiacSignReady;
  FZodiacSink.Connect(FZodiac);

  // Connect custom events:
  FZodiacCustomSink.OnDetailedZodiacSign = OnDetailedZodiacSignReady;
  FZodiacCustomSink.Connect(FZodiac);
}

이러면 클라의 메인 폼에는 분명히 이벤트 함수가 하나 더 생기는 셈이 됩니다.

물론 서버 객체와 상호작용해서 서버가 어떤 반응을 보내면 그에 맞는 적절한

루틴이 돌아가게 되는 거지요

쓰다보니 길어 졌는데 여기서 누락된 내용은 프록시/스텁을 작성하는 문제입니다.

커스텀 인터페이스로 서버객체를 구현하는 경우 반드시 이것을 만들어 줘야 됩니다.

그러나 이벤트 싱크 클래스의 경우 클래스 네임만 달라질 뿐 나머진 같으니

=ㅅ=;; 가 올린 소스를 바꿔서 작성하셔도 될겁니다.

저도 맨땅에 헤딩할때 디벨로퍼 가이드 아자씨덜 꺼 갔다가 조물락거렸는데

뭐 별문제 없더군요~ =ㅅ=;;

그럼 허접 날림 답변이어씀돠. =ㅅ=;;
peccato [peccato]   2002-08-13 14:29 X
윽.. 눈돌아가는 설명임다. @.,@;
에보니.^0^m [mortalpain]   2002-08-13 17:03 X
=ㅅ=;;
왕대박 [emrwo]   2002-08-20 09:08 X
짝~~ 짝~~ 짝~~~ 멋진 설명 감사합니다.

+ -

관련 글 리스트
52 [^ㅅ^] 이벤트 싱크에 대해서 에보니.$ㅅ$ 18838 2002/08/12
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.