개요
이곳에서는 Visual C++의 MFC를 이용한 GOM 프로그래밍에 대해 알아 보겠습니다.
OLE/COM Object Viewer
서버 객체 타입 정보가 있는 tlb 파일을 보기 위해 Visual Studio에 있는 OLE/COM Object Viewer Utility를 사용하면 아래 그림과 같이 모든 class의 정보를 보실 수 있습니다. Visual C++에서 Tool 메뉴에서 OLE/COM Object Viewer를 선택합니다.

IUnknown과 IDispatch
인터페이스는 관련 함수의 주소의 테이블 입니다. 사용자가 인터페이스의 주소를 얻으면 사용자는 인터페이스의 함수를 접근 할 수 있습니다. IUnknown, IDispatch는 Automation 의 핵심 인터페이스 입니다. 모든 COM 객체는 IUnknown 인터페이스를 상속 받습니다. IUnknown 인터페이스는 COM 객체의 수명 관리와 사용자에게 객체가 지원하는 다른 인터페이스를 접근케 합니다. IUnknown 인터페이스를 COM 객체 가 지원을 해야 하는 세 개의 함수를 가지고 있습니다. 그 함수들은 다음과 같습니다.

IUnknown 인터페이스
 
IUnknown :: QueryInterface()
객체가 지원하는 인터페이스를 식별하고 접근하는데 호출 됩니다.
IUnknown :: AddRef() 클라이언트가 인터페이스에 요청을 하였을 때 호출 됩니다.
IUnknown :: Release() 클라이언트가 인터페이스를 제거 할 때 호출 됩니다.

COM 객체는 AddRef와 Release를 사용하여 클라이언트에 의해 다뤄지는 인터페이스 수를 추적해야 합니다. 이를 'reference count' 라고 합니다. reference count가 0이 되면 객체는 메모리에서 제거 됩니다. automation 객체는 IDispatch 인터페이스도 구현을 해야 합니다. IDispatch 인터페이스는 4 개의 함수를 제공합니다. 이는 다음과 같습니다.

IDispatch 인터페이스
 
IDispatch :: GetTypeInfoCount() 타입 정보가 이용 가능하는지 결정할 때 호출됩니다.
IDispatch :: GetTypeInfo() 타입 정보를 얻고자 할 때 호출 됩니다.
IDispatch :: GetIDsOfNames() 속성 이름이나 메소드 이름으로 부터 DISPID를 얻고자 할 때 호출 됩니다.
IDispatch :: Invoke() 메소드나 속성을 요청할 때 호출 됩니다.

GOM을 사용하기 위해선 실행되고 있는 최상위 객체를 얻거나 최상위 객체를 생성해야 합니다. 객체을 생성 하기 위해서 ::CoCreateInstance를 사용해서 IUnknown 인터페이스를 얻어야 합니다. IUnknown의 QueryInterface를 사용 IDispatch 인터페이스를 얻어야 합니다. 이 IDispatch를 사용하여 객체가 공개하는 메소드와 속성을 Invoke를 이용하여 사용할 수 있습니다.
MFC 를 이용한 GOM 프로그래밍 1
GOM의 GxAccount를 사용하는 MFC 클라이언트를 작성합니다.

소스 : MFC GxAccount 샘플
프로젝트 시작
프로젝트를 시작합니다. 프로젝트명은 GxAccounts 이고 Dialog Base 로 하였습니다. 주의할 점은 MFC AppWizard 중 아래 그림과 같이 Automation 체크 박스를 선택 해야 합니다. 선택 시 Automation 클라이언트가 해야 되는 AfxOleInit코드를 작성 합니다.

Import Type Libray와 초기 작업
ClassWizard를 실행 합니다.

Add Class 버튼을 누르고 아래로 나타나는 From a Type Libarary 를 선택 합니다.

GOM tlb 파일을 찾아 선택 합니다.

그림과 같이 GOM 인터페이스들이 나타납니다.

리스트에서 선택한 인터페이스만 프로젝트에 포함 되므로 모든 인터페이스를 선택한 후 OK버튼을 누릅니다. 대화창 아래에 'Header file:', 'Implementation file:' 이라고 나오는데 OK 버튼을 누르면 GOM 인터페이스의 wrapper Class가 'Header file:', 'Implementation file:'에 지정한 파일에 자동으로 작성이 됩니다.

WorkSpace 창을 보면 여러 GOM 인터페이스들이 추가 되어 있는 것을 볼 수 있습니다.

CGxAccountDlg 에서 이들 인터페이스 wrapper 들을 사용하기 위해 P2.h를 include 합니다.

우선 GOM 최상위 객체의 인터페이스 IGxServer를 얻어야 합니다. CGxAccountDlg 클래스에 IGxServ 타입의 멤버 변수 m_Serv를 선언 합니다.

다음은 IGxServ 의 Dispatch 를 생성 해야 하는데 창이 완전히 로딩되기 전에 콤보박스에 계좌번호가 로딩이 되어야 하므로 아래 그림과 같이 InitDialog()에서 m_Serv의 Dispatch를 생성 합니다.

// -- 서버 Dispatch 생성
if (!m_lpserver.Createdispatch("P2.GxServer"))
{
AfxMessageBox("서버 실행을 실패 하였습니다. 종료 합니다.");
EndDialog(-1);
}

// -- IGxAccounts 를 이용 계좌 콤보박스 로딩
IGxTradeStore lpTradeStore;
IGxAccount lpAccount;

lpTradeStore = m_lpServer.GetTradeStore();
m_lpAccounts = lpTradeStore.GetAccounts();

// -- GxAccounts Collction Object에 접근
for (int i=1;i<=m_lpAccounts.GetCount();i++)
{
lpAccount = m_lpAccounts.GetItem(COleVareant((long)i));
m_cboAccounts.AddString(lpAccount.GetCode());
}

계좌 콤보박스에서 계좌 선택시 계좌의 내용을 보여 주여야 하므로 이에 대한 이벤트 핸들러를 작성 합니다.

이벤트 핸들러에서는 선택 계좌 코드를 이용 IGxAccounts 인터페이스에서 해당 IGxAccount를 찾아 멤버 변수에 저장하고 각 프로퍼티를 호출하여 화면을 갱신합니다.
MFC GOM Event 수신
계좌 이벤트를 수신받기 위해서는 Event Sink Class를 작성해서 서버 객체에 Advise() 해야 합니다.

그리고 서버의 객체나 인터페이스의 GUID가 필요합니다. 이는 직접 GUID를 GOM 도움말을 찾아서 GUID를 직접 설정하는 방법도 있지만 저희나 사용자분들이 idl 파일을 컴파일하여 만들어진 p2_i.c 파일을 include하여 사용하면 됩니다. p2_i.c 파일을 보면 서버의 각종 GUID 들이 선언되어 있는 것을 볼 수 있습니다.

const IID LIBID_P2 = { 0x746AB4B8, 0x598E, 0x4B9B, { 0xAB,0x69,0x1F,0x2F,0xE0,0x86,0xO1,0x50 } };
const IID IID_IGxServer = { 0x3FD18384, 0x29E7, 0x4D5A, { 0xAF,0x7D,0x24,0x4A,0x10,0xCA,0x29,0xC4 } };
const CLSID CLSID_GxServer = { 0x9D317706, 0x9BED, 0x4699, { 0x8F,0x30,0x78,0x1D,0xBC,0xBD,0x27,0xA8 } };
const IID IID_IGxServerInfo = { 0x86356355, 0xB0F7, 0x4922, { 0x97,0x94,0x09,0xE5,0x37,0x75,0xE2,0xC7 } };
const IID DIID_IGxServerInfoEvents = { 0xA0DB5BA1, 0x7F32, 0x464B, { 0x9F,0x0F,0x0B,0x16,0x0E,0xEF,0xAC,0xB3 } };


New Class를 통해 새로운 Class 를 작성합니다. 클래스 이름은 CEventSink 로 하겠습니다. 선언과 구현은 EventSink.h 에서 하므로 EventSink.cpp는 프로젝트에서 삭제 합니다.

CEventSink에 IDispatch를 상속케 합니다.

CEventSink에 Constructor, Destructor, IUnknown의 AddRef, Release, QueryInterface IDispatch의 GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke를 구현해야 합니다. 각 Event Sink마다 거의 구현이 비슷하기 때문에 예제의 구현을 복사하면 됩니다. 그리고 약간의 수정이 필요 합니다.

우선 QueryInterface의 IsEqualGUID를 사용하여 GUID 비교하는 부분이 나타납니다. IDispatch, IUnknown 은 그대로 뒤고 세번째에 자신이 이벤트를 받고자 하는 객체의 GUID 비교부분을 추가 합니다.

그 다음 수정을 하는 부분은 Invoke 함수인데 이것은 이벤트가 발생할 때 호출되는 함수 입니다. 그러므로 이 부분에서 이벤트 발생시의 클라이언트 내 작업을 작성하여야 합니다.

예제에서는 이 부분에서 Dispatch ID가 50 일 경우(OnAccountUpdated) 메인윈도우에 특정 윈도우 메시지를 전달하는 부분을 추가 하였습니다.

다음은 작성한 EventSink 클래스를 인스턴스화하여 서버 객체에 Advise()하는 단계 입니다.

이벤트 요청 시점은 폼이 초기화 할 때 이벤트 종료 요청 시점은 폼이 종료 될 때로 하겠습니다.

우선 CEventSink를 CGxAccountsDlg 의 멤버 변수로 추가하여 인스턴스화 합니다.

OnInitDialog 에 앞 단계에서 서버 Dispatch 를 생성한 부분을 찾습니다. 이 부분뒤로 다음과 같은 코드를 작성 합니다. 우선 우리가 이벤트를 요청할 객체에 QueryInterface()를 사용하여 IConnectionPointContainer 인터페이스를 찾습니다. 찾은 IConnectionPointContainer에 FindConnectionPoint()를 사용하여 IGxTradeStoreEvents에 해당하는 IConnectionPoint 을 찾고 멤버 변수로 저장합니다. 찾은 IConnectionPoint에 Advise()를 호출합니다. Advise()호출시 우리가 작성한 Event Sink 객체의 포인터를 파라미터로 추가 해야 합니다. 그리고 Cookie값을 Event Sink 객체와 같이 입력 해야 하는데 이 때 얻은 쿠키는 멤버 변수에 꼭 저장하여 후에 UnAdvise()할 때 사용해야 합니다.

// -- Event 요청 작업
IConnectionPointContainer *lpCpc = NULL;
HRESULT hr;

// -- 서버 Object의 IConnectionPointContainer 인터페이스를 찾음.
hr = m_lpAccounts.m_lpDispatch->QueryInterface(IID_IConnectionPointContainer,(void **)&lpCpc);

if FAILED(hr)
{
AfxMessageBox("IConnectionPointContainer를 찾지 못했습니다.");
EndDialog(-1);
}

// -- IConnectionPoint 인터페이스를 찾음.
hr = lpCpc->FindConnectionPoint(DIID_IGxAccountEvents, &m_lpCp);

if FAILED(hr)
{
AfxMessageBox("FindConnectionPoint 에 실패 하였습니다.");
EndDialog(-1);
}

// -- 이벤트 요청
hr = m_lpCp->Advise(&m_Sink, &m_dwCookie);


프로그램 종료 때의 이벤트 종료 요청은 전 단계에서 저장한 IConnectionPoint에 쿠키를 사용하여 UnAdvise()를 호출합니다.

C++, MFC 등에서는 안정적인 서버, 클라이언트를 위해 UnAdvise()를 하는 것이 좋습니다.

Event Sink를 이용하여 Advise()를 하면 서버에서 이벤트 발생시 CEventSink 객체의 Invoke() 메소드가 호출 됩니다. 앞서 우리는 Invoke() 함수 호출 시 윈도우 메시지를 메인원도우에 발생 시켰습니다. 예제에서는 이 메시지를 수신하는 WindowProc에서 계좌의 값들을 갱신 시키는 작업을 합니다.
MFC 를 이용한 GOM 프로그래밍 2
VC++을 이용하여 GOM의 종목 객체를 이용 지정 종목의 호가, 현재가 등을 보여주는 Dialog Base의 샘플을 작성합니다.

소스 : MFC GxSymbol 샘플
샘플의 소스코드와 아래 설명에서 예시하는 코드는 약간 틀릴 수가 있습니다.
프로젝트 시작

<그림>완성된 예제 화면

1. Visual Studio 를 실행합니다.(이 예제에서는 Visual Studio 6.0를 사용하였습니다.)

2. MFC AppWizard(exe)을 이용하여 프로젝트를 새로 시작합니다.

MFC AppWizard 중 'Dialog based'를 선택하고 반드시 'Automation'을 체크합니다.
프로젝트명은 'PriceSample'로 하였습니다.

기본 작업
3. 화면을 설계합니다.

종목의 호가를 보여주기 위해 MSFlexGrid를 이용합니다.
MSFlexGrid는 기본 컨트롤이 아니므로 Project>Add to Project>Components and Controls를 통하여 MSFlexGrid를 import해야 합니다.

화면 상단에 종목 코드를 입력할 수 있는 EditBox와 Push Button을 배치합니다. 그리고 종목의 호가를 보여주는 MSFlexGrid를 배치하고 화면 오른쪽에 현재가, 거래량, 미결제를 보여주는 EditBox를 배치 합니다.

<그림>화면 디자인

4.GOM 타입 라이브러리를 import합니다.

ClassWizard를 연 후 'Add Class'를 눌러 'From a TypeLibrary'를 선택합니다.

선택 후 타입 라이브러리 파일 선택 창이 나타나는데 GOM 자료실에서 다운받은 p2.tlb 파일을 선택합니다.

선택 후 'Confirm Classes' 창이 GOM 의 인터페이스들과 함께 나타나는데 이 들을 모두 선택한 후 'OK' 버튼을 누릅니다.

다시 나타나는 ClassWizard창에서 'OK' 버튼을 눌러 GOM 타입 라이브러리를 import를 완료 합니다.

import된 GOM 인터페이스들은 p2.h 파일에 자동으로 추가가 됩니다.

5.GOM 초기화/종료 작업을 합니다.

화면이 초기화 할 때 GOM을 초기화 하고 화면이 종료 될 때 GOM 마무리 작업을 합니다.

GOM 초기화 작업은 최상위 인터페이스인 IGxServer를 얻어 클라이언트에 저장하는 작업이고 GOM 종료 작업은 초기화 때 얻은 IGxServer 인터페이스를 다시 돌려주는 작업 입니다.

우선 IGxServer Dialog 클래스인 CPriceSampleDlg의 멤버로 IGxServer 타입으로 m_Server를 선언합니다.

이 멤버에 클라이언트가 실행되는 동안 최상위 인터페이스를 보관합니다. 선언 전 IGxServer 타입이 있는 p2.h를 import 해야 합니다.

다음은 최상위 인터페이스를 얻는 작업 입니다.

CPriceSampleDlg::OnInitDialog() 에서 m_Server.CreateDispatch를 호출합니다.

입력 파라미터는 GOM의 PROGID인 P2.GxServer를 입력하면 됩니다.

다음은 GOM 종료 작업입니다.
화면이 닫힐 때 실행되는 CPriceSampleDlg::OnCancel()에 m_Server.ReleaseDispatch를 호출합니다.
추가로 현재 선택된 종목의 인터페이스를 저장하는 IGxServer타입의 m_Symbol을 m_Server와 함께 선언합니다.

GxSymbol의 사용
6. pshExcute 이벤트 핸들러를 작성합니다.

pshExcute 버튼을 클릭할 때 지정 종목코드에 대한 종목 인터페이스를 찾습니다.

우선 edtCode Edit Box와 대응되는 Value 형 변수를 m_strCode 라고 선언합니다.

그리고 pshExcute에 대한 Click 이벤트 핸들러를 작성합니다.

이벤트 핸들러는 다음과 같이 작성 합니다.

UpdateData를 호출하여 사용자가 입력한 종목코드를 변수에 저장하고 SymbolStore의 'Item' 속성을 이용하여 종목을 찾아 이전에 CPriceSampleDlg에 멤버로 선언한 m_Server에 저장합니다.

MFC가 자동으로 작성한 GOM 랩퍼 클래스의 속성을 호출시 실제 속성 명 앞에 읽기속성을 이용하면 'Get'이 붙고 쓰기 속성을 이용하면 'Set'이 붙습니다.
코드는 다음과 같습니다.

void CPriceSampleDlg::OnpshExcute()
{

...

UpdateData(TRUE);

...

if (m_Server != NULL)
{
SymbolStore = m_Server.GetSymbolStore();

if (SymbolStore != NULL)
{
m_Symbol = SymbolStore.GetItem(COleVariant(m_strCode));

if (m_Symbol == NULL)
{
AfxMessageBox("종목을 찾지 못했습니다.");
...
}
else
{
RefreshQuote();
RefreshPrice();
}

}
}
}

7.다음은 m_Symbol에 해당하는 종목의 호가를 화면에 표시하는 RefreshQuote, 현재가/거래량을 표시하는 RefreshPrice등의 CPriceSampleDlg의 멤버 함수를 작성합니다.
우선 상대적으로 쉬운 RefreshPrice를 작성합니다.

우선 edtClose, editVolume, editOpenInterest Edit Box 컨트롤에 해당하는 Value형 CString 타입의 변수 m_strClose, m_strVolume, m_strOpenInterest를 추가 합니다.
RefreshPrice에서는 m_Symbol의 현재가, 미결제, 거래량 속성을 통해 값을 얻고 이 들을 Value형 멤버 변수에 저장하고 화면에 동기화 합니다.

void CPriceSampleDlg::RefreshPrice()
{
if (m_Symbol == NULL) return;

m_strClose.Format("%5.2f", m_Symbol.GetClose());
m_strOpenInterest.Format("%d", m_Symbol.GetOpenInterest());
m_strVolume.Format("%d", m_Symbol.GetAccVolume());

UpdateData(FALSE);
}


다음은 RefreshQuote를 작성합니다.
GOM에서는 5단계 호가를 얻기 위해 array로 접근하는 방법과 array를 사용하지 않는 접근방법이 있습니다.
그러나 array가 빠른 접근 방법을 제공하므로 이 예제에서는 array를 사용하여 진행합니다.
GxQuote 객체에는 VQuotes, VQtys, VPrices, VCnts 등을 제공하는데 VQtys, VPrices, VCnts 등은 각각 수량, 호가, 건수 만 각각 제공하지만 VQuotes 는 앞의 3가지를 통합하여 제공합니다. 우리는 호가, 수량, 건수 모두 필요 하므로 VQuotes를 사용합니다.
호가 array에서 값을 뽑아 내는 과정은 다음과 같습니다.

void CPriceSampleDlg::RefreshQuote()
{

VARIANT vntQuotes;
SAFEARRAY *sa;
SAFEARRAYBOUND saDims[1]; //-- 1차원 array 지정
long lIndex, lElement;
CString strElement;

IGxQuote Quote;

if (m_Symbol == NULL) return;

Quote = m_Symbol.GetQuote();

if (Quote == NULL) return;

//-- safearray의 처음과 최종 인덱스 지정
saDims[0].lLbound = 0;
saDims[0].cElements = 34;

vntQuotes = Quote.GetVQuotes();
sa = vntQuotes.parray;
//-- 매도 호가 (0 ~ 4)
for(int i=0; i<5; i++)
{
lIndex = i;
SafeArrayGetElement(sa, &lIndex, &lElement);
strElement.Format("%5.2f", static_cast(lElement) / 100);
m_msgQuote.SetTextMatrix(5-i, 2, LPSTR(LPCTSTR(strElement)));
}
//-- 매수 호가 (5 ~ 9)
for(i=5; i<10; i++)
{
lIndex = i;
SafeArrayGetElement(sa, &lIndex, &lElement);
strElement.Format("%5.2f", static_cast(lElement) / 100);
m_msgQuote.SetTextMatrix(i+1, 2, LPSTR(LPCTSTR(strElement)));
}
....
//-- 매수 건수 (29 ~ 33)
for(i=5; i<10; i++)
{
lIndex = i + 24;
SafeArrayGetElement(sa, &lIndex, &lElement);
strElement.Format("%d", lElement);
m_msgQuote.SetTextMatrix(i+1, 4, LPSTR(LPCTSTR(strElement)));
}
}
RefreshPrice, RefreshQuote 가 완성되면 앞서의 pshExcute 버튼의 이벤트 핸들러에 이 두 함수를 추가합니다.
EventSink 작성
8. 다음 단계는 이벤트를 수신하기 위한 EventSink 객체를 만드는 작업입니다.

'File>New...' 메뉴를 통하여 'C/C++ Header File' 를 통하여 파일명이 EventSink.h인 새로운 Header 파일을 만듭니다.
EventSink 객체는 클라이언트 측 COM 객체이므로 IUnknown 인터페이스 의 AddRef, Release, QueryInterface, IDispatch 인터페이스의 GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke 등을 구현해야 합니다. 이는 다음과 같은 예제 코드를 참조하여 작성하도록 합니다.

class CEventSink : public IDispatch
{
public:
ULONG refCount;

CEventSink::CEventSink() {
refCount = 1;
}
CEventSink::~CEventSink() {
}

// IUnknown methods.
virtual HRESULT __stdcall QueryInterface(
REFIID riid, void **ppvObject) {
if( IsEqualGUID(riid, IID_IDispatch) ||
IsEqualGUID(riid, IID_IUnknown) ||
IsEqualGUID(riid, DIID_IGxSymbolEvents) ||
IsEqualGUID(riid, DIID_IGxQuoteEvents)
) {
this->AddRef();
*ppvObject = this;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}

virtual ULONG _stdcall AddRef(void) {
return ++refCount;
}

virtual ULONG _stdcall Release(void) {
if(--refCount <= 0) {
//Delete this;
return 0;
}
return refCount;
}

// IDispatch methods.
virtual HRESULT _stdcall GetTypeInfoCount(UINT *pctinfo) {
if(pctinfo) *pctinfo = 0;
return E_NOTIMPL;
}

virtual HRESULT _stdcall GetTypeInfo(
UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) {
return E_NOTIMPL;
}

virtual HRESULT _stdcall GetIDsOfNames(
REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid,
DISPID *rgDispId) {
return E_NOTIMPL;
}

virtual HRESULT _stdcall Invoke(
DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pDispParams, VARIANT *pVarResult,
EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
//--
}

위의 EventSink 객체 코드는 다른 GOM 객체들을 사용할 때 Copy를 해서 사용해도 됩니다.
단 QueryInterface의 경우는 서버에서 클라이언트가 작성한 이 EventSink 객체가 어떤 GOM Event를 취하는지를 알기 위해 QueryInterface를 호출하므로 EventSink 객체의 용도에 따라 IsEqualGUID(...) 부분을 추가하거나 삭제 하시면 됩니다. 그리고 Invoke(...)의 경우 서버가 이벤트를 발생시키면 클라이언트의 EventSink 객체의 Invoke(...)가 호출 되므로 이벤트 시 클라이언트 동작은 Invoke(...)에 추가 하면 됩니다.

이 예제에서는 종목 현재가, 종목 호가 이벤트를 수신하기 위한 EventSink 이므로 IsEqualGUID(...)에 IGxSymbolEvents, IGxSymbolQuoteEvents를 추가 하였고 이를 위해 IGxSymbolEvent등을 정의해 놓은 자료실의 P2_i.c를 다운로드 하여 이를 include 하였습니다.
그리고 EventSink 가 호가 이벤트를 받는 것인지 현재가 이벤트를 받는 것인지를 표시하기 위해 CEventSink에 bool형의 m_bPriceReceived 멤버를 추가 하였습니다.

그래서 Invoke(...)에서 m_bPriceReceived 가 True이면 사용자 정의 메시지인 WM_PRICE_CHANGED 이벤트를 CPriceSampleDlg에 전달하고 False 이면 마찬가지로 사용자 메시지인 WM_QUOTE_CHANGED 이벤트를 CPriceSampleDlg에 전달합니다.

추가로 CPriceSampleDlg의 윈도우 메시지를 받는 WindowProc에 WM_PRICE_CHANGED 이벤트를 수신하면 RefreshPrice를 WM_QUOTE_CHANGED 이벤트를 수신하려면 RefreshQuote를 실행하도록 작성합니다. 이 때 CPriceSampleDlg 에 EventSink.h 를 include 하도록 합니다.

LRESULT CPriceSampleDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
switch (message)
{
case WM_PRICE_CHANGED :
RefreshPrice();
break;

case WM_QUOTE_CHANGED :
RefreshQuote();
break;
}

return CDialog::WindowProc(message, wParam, lParam);
}

9. 다음 작업은 Event 요청 및 해지 작업입니다. 앞 단계에서 작성한 EventSink 객체는 작성만 상태이지 아직 사용하지 않는 상태 입니다. 이제 EventSink를 통하여 이벤트를 구독, 취소 하는 작업을 진행합니다. CPriceSampleDlg 에 멤버로 CEventSink 타입으로 m_PriceSink, m_QuoteSink 를 선언합니다. 그리고 IConnectionPoint 포인터 타입으로 m_lpQuoteCP, m_lpPriceCP를 선언합니다. 마지막으로 DWORD 타입으로 m_dwQuoteCookie, m_dwPriceCookie 를 선언합니다. m_PriceSink, m_QuoteSinks는 이벤트를 수신하는 EventSink이고 m_lpQuoteCP등은 서버 객체에 이벤트 구독, 해지를 위한 인터페이스인 IConnectionPoint를 저장하는 변수 입니다. 마지막으로 이벤트 해지를 위해 사용될 값을 저장하는 변수가 m_dwQuoteCookie등 입니다. CPriceSampleDlg::OnInitDialog()로 돌아가서 m_PriceSink, mQuoteSink 등에 m_bPriceReceived 멤버를 용도에 맞게 설정합니다. 멤버 변수 선언 후 종목 선택 시 마다 이벤트 구독, 해지를 해야 하므로 pshExcute 버튼 이벤트 핸들러로 돌아가서 RefreshPrice 이 후에 이벤트 구독을 하는 다음과 같은 코드를 작성 합니다.

//-- 현재가 이벤트 구독
hr = m_Symbol.m_lpDispatch->QueryInterface(IID_IConnectionPointContainer, (void **)&lpCPC);

if FAILED(hr)
{
AfxMessageBox("IConnectionPointContainer를 찾지 못했습니다.");
return;
}

hr = lpCPC->FindConnectionPoint(DIID_IGxSymbolEvents, &m_lpPriceCP);

if FAILED(hr)
{
AfxMessageBox("IConnectionPoint를 찾지 못했습니다");
return;
}

hr = m_lpPriceCP->Advise(&m_PriceSink, &m_dwPriceCookie);

if FAILED(hr)
{
AfxMessageBox("Advise에 실패 하였습니다");
return;
}

Quote = m_Symbol.GetQuote();

if (Quote == NULL)
{
AfxMessageBox("서버에 문제가 발생하였습니다");
return;
}

//-- 호가 이벤트 구독
hr = Quote.m_lpDispatch->QueryInterface(IID_IConnectionPointContainer, (void **)&lpCPC);

if FAILED(hr)
{
AfxMessageBox("IConnectionPointContainer를 찾지 못했습니다.");
return;
}

hr = lpCPC->FindConnectionPoint(DIID_IGxQuoteEvents, &m_lpQuoteCP);

if FAILED(hr)
{
AfxMessageBox("IConnectionPoint를 찾지 못했습니다");
return;
}

hr = m_lpQuoteCP->Advise(&m_QuoteSink, &m_dwQuoteCookie);

if FAILED(hr)
{
AfxMessageBox("Advise에 실패 하였습니다");
return;
}


이벤트 구독 작업을 완료 했으면 다음은 이벤트 구독 작업 해지를 진행합니다. 이벤트 해지 작업 부분의 코드는 다음과 같습니다.

try
{
if (m_lpPriceCP != NULL)
{
m_lpPriceCP->Unadvise(m_dwPriceCookie);
m_lpPriceCP->Release();
}

if (m_lpQuoteCP != NULL)
{
m_lpQuoteCP->Unadvise(m_dwQuoteCookie);
m_lpQuoteCP->Release();
}
}
catch(CException e)
{
}

이 코드는 pshExcute 이벤트 핸들러의 이벤트 구독 이전에 추가 되어야 하고 프로그램의 종료시에도 이 작업이 필요하므로 CPriceSampleDlg::OnCancel()에도 이 코드를 추가합니다.
MFC GOM 프로그래밍
우선 프로젝트 시작 시 Automation 옵션을 꼭 체크 합니다. 체크 할 시 Automation 초기화 작업을 자동으로 해 주기 때문입니다. 그리고 ClassWizard 를 통해 Add Class > From a TypeLibray 에 통해 tlb 파일을 import 합니다. 그러면 자동으로 VC는 자동으로 GOM wrapper 를 만들어 줌니다. 사용자는 이 wrapper에 선언된 메소드를 호출하여 사용하시면 됩니다.
MFC GOM 객체 사용
ClassWizard를 통하여 wrapper를 생성 후 최상위 인터페이스를 멤버 변수로 선언하고 이에 대한 Dispatch를 생성해야 합니다. CreateDispatch("P2.GxServer")를 통해 Dispatch 를 생성 후 하위 객체들을 wrapper를 통해 사용합니다.
MFC GOM Event 수신
사용자는 Event Sink 객체를 직접 생성해야 합니다. Event Sink Class 에는 IUnknown, IDispatch를 구현합니다. 사용 목적에 따라 IDispatch의 Invoke 메소드를 작성 합니다. 서버에서 Event를 발생시키면 클라이언트 쪽에서 이 Invoke 함수가 호출되기 때문 입니다. Event Sink 작성 후 Event Sink을 생성 시키고 IConnectionPointContainer, IConnectionPoint를 이용 서버 객체의 ConnectionPoint를 찾아 Advise을 합니다. Event Sink는 Advise의 파라미터로 입력되어야 합니다. 그리고 Event를 수신을 중지 하려면 앞서 Advise한 ConnectionPoint에 UnAdvise를 합니다. 원활한 프로그램밍을 위해 Advise한 후 종료 시 꼭 UnAdvise 하는 습관을 갖도록 해야 합니다.
예제 파일
경고 : 이 예제는 GOM 사용법을 쉽게 익히기 위해 제공 하는 것으로, 이 예제를 거래에 이용 하는 중 에러나 다른 이유로 발생하는 피해는 당사에서 책임을 지지 않습니다.
2004. 05. 18 (주)델타 익스체인지

C++, MFC 예제
 
GxSymbol 예제<제작 버전:1.2>
GxAccount 예제 <제작 버전:1.0>
Greeks 예제 <제작 버전:1.1>
GxChartStore 예제 <제작 버전:1.23>
MFC를 이용하지 않고 C++ 라이브러리만으로 KOSPI200 현재가를 조회하는 간단한 콘솔 프로그램입니다.<제작 버전:1.1>