3.Automation

본장은 COM을 바탕으로 좀더 발전된 형태로 만들어진 Automation에 대해서 설명합니다. Automation 은 주로 로컬컴퓨터에서 조립형태의 프로그램을 제작할때 사용하는 방법으로 생각하시면 됩니다. Automation에서는 메소드와 프로퍼티의 개념을 이해하시면 됩니다. 메소드와 프로퍼티는 다음장에서 설명하는 Active X에 를 이해하시는데 도움될것입니다.

대상자: 중급

Chapter 등급: 선택

공부방법:

본장의 내용은 그냥 어떤내용이 있는가 읽어보시는것이 좋을듯합니다. 그냥 넘어가지 마시고 한번정도는 읽어보세요.

1.Automation 을 알자

Automation이란 해석 그대로 자동적으로 구동되는 것을 의미합니다. COM 이 좀더 발전된 형태가 된것입니다. COM을 구동하기 위해서는 복잡한 몇가지 절차를 가져야 합니다. 이런 절차를 자동적으로 설정하고 단순하게 프로그램을 제작할수 있도록 하는것이 Automation입니다. Automation은 필요한 객체를 구동할때 IUnknown의 구조체를 상속받아서 사용하는 부분이 아니라 CCmdTarget 내부에 구동하고 있는 Automation을 그대로 이용하자는 의미입니다. Automation은 때로는 DLL를 사용하는것과 같은 느낌을 받습니다. 그러나 DLL과는 많은 부분에서 차이가 납니다. Automation은 DLL처럼 프로그램 내부에 라이브러리로 동작하는 것이 아니라 클라이언트와 서버의 개념으로 동작합니다. 두개의 독립적인 프로그램이 서로 정보를 교환하면서 구동한다는 의미입니다.

<1>메소드(Mehtod)와 프로퍼티(Properties)

COM에서는 IUnknown구조체를 상속받은 클래스를 만들고 이클래스에 구동함수를 만든다음 이함수를 외부에서 호출하였습니다. COM에서 보면 단순하게 함수를 구동하는것이 전부였습니다. Automation에서는 여기에 메소드와 프로터티의 개념을 부과 시켰습니다.

프로퍼티는 객체에 있는 자원을 의미하며 메소드는 이자원을 이용하여 구동되는 하나의 함수를 의미합니다. 객체 지향적인 프로그램에서 객체를 구성시킬때 크게 두개로 나누어 구성시킵니다. 객체안에 있는 자원과 그리고 그자원을 구동시키는 함수입니다. 이것을 C++에서는 맴버 변수와 맴버 함수라고 합니다. 일반적인 개념으로 객체의 맴버는 캡슐화 하여 외부에서 컨트롤 할수 없도록하고 객체의 함수는 외부에서 호출할수 있도록 하는것입니다. 테레비젼을 예로 든다면 테레비젼 내부에 있는 회로나 브라운관 들은 객체의 맴버입니다. 그리고 그자원을 핸들링하는 리모콘이 함수라고 볼수 있습니다. automation은 COM을 발전시킨형태에서 서버프로그램을 구동하고자 할때 자원에 접근하는 프로퍼티와 함수에접근하는 메소드 두개로 분리하였습니다.  automation 은 쉽게보면 자동 제작기계가 같은 것입니다. 이런 기계가 있다고 생각을 해보세요.쌀하고 물하고 기계에 넣고 버튼을 클릭하게 되면 조금있다가 밥이 나오는 기계, 이것이 바로 automation입니다. 쌀과 물은 바로 프로퍼티이며 버튼 클릭이 바로 메소드입니다. 이것에 의해서 결과가 밥이 나오는 것입니다. 이래서 automation이라고 했는지도 모릅니다.

automation은 프로퍼티에 적당한 값을 설정하고 메소드를 이용해서 자동적으로 구동되는 프로그램입니다. 덧셈을 하는 automation이 있다면 이 automation에는 덧셈을 해야할 두개의 프로퍼티가 있으며 이계산을 수행하는 덧셈 메소드가 존재해야 합니다. 이런식으로 많은 automation프로그램을 만들어 놓으면 필요시에 automation의 조작법만 안다면 데이터와 함께 메소드를 호출하는 법만 알면 구동이 가능한게 됩니다. 로컬 컴퓨터에서 조립식 프로그램 제작이 바로 이 automation에서 가능하게 될것이라고 생각한 분들이(?) 있었습니다. 그러나 생각했던것 보다는 이런 방향으로 더디게 움직이고 있습니다. 여기에는 몇가지 벽이 있기 때문입니다. 첫번째는 프로그래머들의 예술적 정신(?) 입니다. 예술적 정신이라고 한이유는 이것 외에 더 좋은 다른 말을 할수가 없어서 입니다. 프로그래머들은 가능한것은 모두 자기가 만들고 싶어하는 맘이 있습니다. 사용자가 생각하기에는 별것도 아닌거을 몇날 몇일을 죽어라고 고생해서 만들어 냅니다. 대부분의 프로그래머들은 이런 마음이 있습니다. 그렇기 때문에 □남이만든거 사용하느니 내가 만들겠다!:□라는 생각이 많아서 automation프로그램을 이용하지 않는 것입니다. 두번째로는 쓸만한 automation프로그램이 아직 많지 않습니다. 즉 급하게 사용하고자 할경우 쓸만한 automation프로그램이 없다는것입니다. automation프로그램들을 보면 □나도 그정도는 만들어!□ 하는것이 많습니다. 첫번째와 두번째는 닭과 계란일것입니다. 사용하는 사람이 적으니까 프로그램을 안만드는것이고 automation프로그램이 많지 않으니까 사용하지 않는것이니까요. 세번째로 보면 automation 프로그램은 내부적으로 동작하는것보다는 속도면에서 느립니다. 1초도 못기다리는 사용자들은 automation이 결합된 프로그램을 사용하는것보다 내부 라이브러리로 결합된 프로그램을 더 선호하기 때문입니다.  다음장에서 설명을 하겠지만 이런 문제를 해결하고자 해서 나온것이 Active X입니다. 엑티브한것중에 X라는 의미이겠지요. 더이상말이 필요없는 단어입니다. (독자여러분이 마음대로 해석하시기 바랍니다) Active X에 대해서는 다음장에서 다시 설명을 하겠습니다.

메소드와 프로퍼티를 좀더 설명하겠습니다. 그림 4-3-1은 계산기를 automation으로 만들었을때의  메소드와 프로퍼티가 구성되는 예입니다.

(그림 파일)

(그림 4-3-1)계산기를 automation으로 만들었을경우

그림 4-3-1의  ⓐ항목과 같은 automation프로그램은 □입력 데이터1□ 과 □입력데이터2□ 에 값을 넣고 □덧셈□ 이라는 메소드를 실행시키면 이결과값을 □계산 데이터3□ 이란곳에 저장하는 기능을 가지고 있는 예입니다. 이때 ⓐ프로그램을 이용해서 결과를 얻고자 한다면 외부에서는 □입력 데이터1□ ,□입력데이터2□ 등의 프로퍼티와  □덧셈□ 이라는 메소드 이름을 사용하지 않습니다. 다만 외부에서는  해당 프로퍼티와 해당 메소드에 설정되어 있는 번호를 할고 이번호를 이용하여  ⓐ 프로그램을 구동시킵니다. 먼저 ①과 ②항목처럼 계산하고자 하는 데이터를 SetProperty라는 함수를 이용하여 프로퍼티에 데이터를 설정하고 InvokeHelper이라는 함수를 이용하여  □덧셈□ 이라는 메소드를 실행하고 그리고 다시 GetProperty라는 함수를 이용하여 생성된 결과를 가져 오는 것입니다. 그림 4-3-1이 autoamation을 제작하고 automation을 이용하는 기본적인 구조입니다. 이구조를 이해하시고 다음 설명을 읽으셨으면 합니다.

<2>automation 프로젝트 만들기

Automation을 만들경우에는 DLL를 이용하여도 되며 EXE파일로 만들어도 됩니다. DLL를 이용하고자 할경우에는 프로젝트를 만들때 AppWizard에서 Automation항목을 체크하시면 됩니다. EXE로 만들경우는 Step1에선 Dialog based로 설정하고 Step2에서 Automation항목을 체크하시면 됩니다. EXE로 만들때 Dialog base로 설정하고 InitInstance함수에서 대화상자가 실행되는 부분을 삭제 합니다. 잃게 하면 Automation 프로그램제작의 기본이 만들어지게 된것입니다. DLL로 Automation 프로그램을 만들었을경우에는 전장에서 만든 RegeAdd라는 프로그램을 이용하여 레지스트리에 등록하여야 합니다. EXE로 만들었을경우에는 해당 프로그램을 한번 실행시킴으로써 레지스트리에 등록됩니다. Automation이나 COM모두 프로그램의 CLSID가 레지스트리에 등록이 되어야 사용할수가 있습니다. 이렇게 프로젝트를 만든다음 하나의 Automation을 만들기위해서는 CCmdTarget 클래스를 상속받은 클래스를 만들어야 합니다. CCmdTarget클래스는 OLE를 가능하게 해주는 가장 기본적인 클래스 이기 때문입니다. 그림 4-3-1의 ⓐ항목과 같은 프로그램을 제작하기 위해서 CCmdTarget클래스를 만들고 이클래스에서 메소드와 프로퍼티를 설정해야 합니다. 메소드와 프로퍼티를 설정할때는 클래스위저드를 이용합니다. 그림 4-3-2는 메소드와 프로퍼티를 설정하는 예입니다.

(그림 파일)

(그림 4-3-2) 서버용 프로젝트에 메소드와 프로퍼티 설정

서버 용 클래스 CCmdTarget를 만든후에 이 클래스에 메소드와 프로퍼티를 설정하고자 할때 이때에도 클래스 위저드를 이용합니다. 클래스위저들을 실행시키고 프로퍼티쉬트 항목에서 Automation을 클릭한후 이곳에서 메소드와 프로퍼티를 설정하면 됩니다. 그림 4-3-2는 CCmdTarget 에서 Automation항목을 선택한 화면입니다. 그림 4-3-2에서 ①번을 클릭하여 메소드를 등록하고 ②번을 클릭하여 프로퍼티를 등록할수가 있습니다.

(1)프로퍼티 만들기

그림 4-3-3은 그림 4-3-2에서 ②번을 클릭하여 프로퍼티를 설정하였을때 나타나는 대화상자입니다.

( 그림 파일)

(그림 4-3-3)새로운 프로퍼티 만들기

이대화상자의 ①번 항목은 외부에서 프로퍼티 데이터에 접근할때 사용하는 이름입니다. ①번은 프로퍼티 데이터 형을 의미합니다. ③번은 내부에서 사용하는 데이터 명입니다. ①, ②,③을 기록한후에 ⑤항목을 선택하면 헤더에는 ⑧번항목과 같이 프로퍼티 명과 이프로퍼티에 데이터가 수정하였을때 발생하는 함수가 설정되며 소스부에는 ⑨번항목과 같이 데이터가 수정하였을대 수행하는 소스가 설정됩니다. 이부분과 함께 중요한 부분이 ⑩번항목입니다. Automation항목을 선택하게 되면 ODL파일이 만들어 집니다. 이 OLD파일에서 property 항목에 ⑩ 항목처럼 번호화 함께 외부에서 사용하는 데이터명이 설정됩니다. 즉 ①,②,③에 설정된 내용이 ⑧,⑨,⑩ 항목처럼 연결된다는 것입니다. ⑤항목은 Get/Set methods라는 의미인데 이의미는 프로퍼티가 변수 형태가 아니라 함수형태로 만들어지는 것입니다. 즉 GetData,SetData라는 함수가 만들어지고 이함수에 값을 설정하면 프로퍼티로 설정되지 않은 일반변수에 값을 설정하도록 하는 것입니다. GetData,SetData로 설정할경우 이함수에 특별한 인자가 필요할경우 ⑦번항목에서 인자를 설정할수가 있습니다.

(2)메소드 만들기

그림 4-3-2에서 메소드 항목을 선택하면 그림 4-3-4와 같은 화면이 나타납니다.

(그림 파일)

(그림 4-3-4) 새로운 메소드 만들기

①항목은 메소드 함수를 외부에서 사용할때 이름이며 ②항목은 내부적으로 사용할때 함수명입니다. ③은 이함수가 수행한후에  결과값을 리턴할때 리턴되는 형입니다.①과 ②,③ 에 설정된 값은 헤더부,소스부,ODL파일에 ④,⑤,⑥과 같이 설정딥니다. ODL파일에 설정되는 값에서 [id(번호)]일때 (번호)는 같은것이 존재하면 안됩니다. 만일 프로퍼티에서 1,2 번을 사용하였을때 이때 메소드를 설정하고자 한다면 이때 번호는 3번이 되어야 합니다. 물론 자동적으로 번호가 설정되나 수정하거나 삭제하게 되면 번호가 달라질수도 있습니다. 이럴경우에는 수동으로 번호를 설정해 주어야 합니다.

2.COleDispatchDriver 클래스

CCmdTarget 함수를 이용하여 Automation 서버를 만들었을경우 이서버를 사용하는 클라이언트 클래스는 COleDispatchDirver 입니다. 서버를 로드하고자 할때는 CCmdTarget에 설정하였던  Automation명을 이용합니다. COlddispatchDriver 클래스의 CreateDispatch  함수에 Automation명을 설정합니다. Automation 이름이 Automation.bnk라고 할경우 다음과 같이 합니다.

COleDispacthDriver::CreateDispatch("Automation.bnk")

위와같이 하면 Automation.bnk라는 Automation 서버와 현재 클라이언트가 연결됩니다. 이렇게 연결하고 서로 프로퍼티와 메소드를 이용하여 통신하는 방법이 그림 4-3-5입니다.

그림 4-3-5는 Automation서버 와 COleDispatchDirver클래스와 연결되는 구성도입니다.

(그림 파일)

(그림 4-3-5)COleDispatchDriver 과 Automation 서버

그림 4-3-5의  ⓑ항목은 전항목에서 예로 만든 CCmdTarget클래스에 설정하였던 메소드와 프로퍼티 입니다. m_Data1과 m_Data2라는 프로퍼티와 이 두값을 더하는 Add라는 메소드가 있습니다. m_Data1과 m_Data2에 값을 쓰거나 읽고자 할때는 GetProperty와 SetProperty 두개의 함수를 이용합니다. 첫번째 인자는 ODL에 설정되어 있는 번호입니다. 두번째 인자는 형에 대한 플러그이며 3번째 인자는 값을 받거나 넣고자할때 사용하는 변수가 됩니다. 즉 프로퍼티에 데이터를 기록하거나 읽고자 할경우에는 GetProperty함수와 SetProperty함수에 ODL에 설정되어 있는 번호를 설정하여 사용하면 됩니다. 메소드를 호출할때는 InvokeHelper 함수를 이용합니다. 이함수의 첫번째 인자는 ODL에 설정되어 있는 번호를 설정합니다. 결국 모든 통신은 ODL에 설정되어 있는 번호를 이용하여 GetProperty와 SetProperty 그리고 InvokeHelper 함수를 이용하면 됩니다.

2.예제 프로그램 Namecard

실질적인 예제 프로그램을 작성해 보겠습니다.

이름과 번호, 전화번호를 기입하는 하나의 명함철 프로그램을 OLE 서버 프로그램으로 작성하고 위 프로그램을 호출하는 클라이언트 프로그램을 작성해 보겠습니다.

<1>프로젝트 제작

NAMECARD 프로그램은 단독 실행이 되지 않고 단지 다른 프로그램에서 호출을 했을 때만 실행됩니다. NAMECARD 프로그램을 만들기 위해서는 프로젝트를 만들 때 Dialogbased형으로 제작합니다.

(그림 4-3-6)AppWizard Step1에서 대화상자형태로 선택

(그림 4-3-7)AppWizard Step2에서 Automation선택

ActiveX를 자동적으로 설치하기 위해서는 AppWizard에서  Automation을 선택해야 합니다. AppWizard에서 그림 4-3-7과 같은 내용이 나타날 때는  Automation을 선택합니다.

위와 같이 선택하여 NAMECARD 프로젝트를 만들고 나면 CNameCardDlg라는 클래스가 있는 헤더(.h)와 소스 파일(.cpp)을 프로젝트에서 제거합니다. 그리고 CNameCardApp 클래스 함수 중 InitInstance() 함수 안에서 아래와 같은 소스를 삭제합니다.

CNAMECARDDlg dlg;

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

// TODO: Place code here to handle when the dialog is

//  dismissed with OK

}

else if (nResponse == IDCANCEL)

{

// TODO: Place code here to handle when the dialog is

//  dismissed with Cancel

}

결국 하나의 애플리케이션이 단지 ActiveX Automation 기능만 들어 있는 빈 프로젝트를 만드는 것입니다.

위와 같이 하면 NAMECARD.CPP와 NAMECARD.ODL과 NAMECARD.RC가 남게 됩니다.

우선 NAMECARD.CPP 안의 InitInstance() 함수를 분석해 봅시다.

BOOL CNameCardApp::InitInstance()

{

// OLE 라이브러리를 초기화한다.

if (!AfxOleInit())

{

AfxMessageBox(IDP_OLE_INIT_FAILED);

return FALSE;

}

// Standard initialization

// If you are not using these features and wish to reduce the size

//  of your final executable, you should remove from the following

//  the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls();   // Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic(); // Call this when linking to MFC statically

#endif

// Parse the command line to see if launched as OLE server

if (RunEmbedded() || RunAutomated())

{

// Register all OLE server (factories) as running.  This enables the

//  OLE libraries to create objects from other applications.

COleTemplateServer::RegisterAll();

// Application was run with /Embedding or /Automation.  Don't show the

//  main window in this case.

return TRUE;

}

// When a server application is launched stand-alone, it is a good idea

//  to update the system registry in case it has been damaged.

COleObjectFactory::UpdateRegistryAll();

AfxMessageBox("명함철이 등록되었습니다.");

// Since the dialog has been closed, return FALSE so that we exit the

//  application, rather than start the application's message pump.

return FALSE;

}

InitInstance 함수 초기에 AfxOleInit() 함수가 실행되면서 ole를 초기화합니다. 끝부분에 UpdateRegistryAll()라는 함수가 실행되면서 현재 프로그램에서 ole로 작성한 함수들을 등록시킵니다.

AfxMessageBox("명함철이 등록되었습니다.");라는 메시지를 출력시키고 본 프로그램은 아무것도 안 하고 종료합니다. ActiveX automation 서버를 윈도에 등록시킬 때 등록 프로그램을 실행시키거나 또는 ActiveX automation 프로그램을 한 번 실행시키면 자동적으로 프로그램이 윈도에 등록됩니다. 프로그램을 제작하여 한 번 실행함으로써 OLE 데이터베이스에 NAMECARD 프로그램이 등록되는 것입니다.

등록된 다음 명함 카드가 실행되는 프로그램을 넣어야 하는데 어떻게 넣어야 할 것인가? 또한 OLE서버 클래스는 어떤 것을 사용하는 것일까? 바로 CCmdTarget 클래스를 이용합니다. CCmdTarget 클래스란  MFC 메시지 구조를 갖는 기본적인 클래스입니다. 메시지를 보내고 받을 수 있는 어떤 기본적인 클래스가 있어야 윈도 프로그램을 제작할 수 있기 때문입니다. 더불어 IDispatch 인터페이스를 제공하는 기능도 가지고 있습니다.

(그림 4-3-8)CCardSever 클래스 만드는 방법

ClassWizard를 실행시키고 CCardSever라는 새로운 클래스를 만듭니다. 이 클래스의 베이직 클래스는 CCmdTarget 클래스입니다. 다음 OLE Automation에서는 Createable by type ID NAMECARD.BANK라고 설정합니다.

<2>메소드와 프로퍼티 만들기

NAME에는 이름, 전화번호, 고유 번호 등 3가지가 필요합니다. 이 3가지 중 고유 번호는 long형으로 사용하면 되지만 이름과 전화번호는 텍스트일 수도 있고 번호일 수도 있고 여러 가지 형으로 가질 수가 있습니다.

이렇게 여러 가지 형으로 전환될 수 있는 형은 VARIANT입니다. Win32 OLE 헤더 파일에 있는 VARIANT 정의를 보겠습니다.

typedef struct FARSTRUCT tagVARIANT VARIANT;

typedef struct FARSTRUCT tagVARIANT VARIANTARG;

typedef struct tagVARIANT  {

VARTYPE vt;

unsigned short wReserved1;

unsigned short wReserved2;

unsigned short wReserved3;

union {

unsigned char bVal;   /* VT_UI1*/

short   iVal;   /* VT_I2*/

long   lVal;   /* VT_I4*/

float   fltVal;  /* VT_R4*/

double   dblVal; /* VT_R8*/

VARIANT_BOOL  bool;  /* VT_BOOL*/

SCODE   scode;  /* VT_ERROR*/

CY    cyVal; /* VT_CY*/

DATE   date;   /* VT_DATE*/

BSTR   bstrVal;  /* VT_BSTR*/

Iunknown   FAR* punkVal;/* VT_UNKNOWN*/

Idispatch  FAR* pdispVal; /* VT_DISPATCH*/

SAFEARRAY  FAR* parray; /* VT_ARRAY|**/

unsigned char FAR *pbVal;  /* VT_BYREF|VT_UI1*/

short   FAR* piVal;  /* VT_BYREF|VT_I2*/

long   FAR* plVal;  /* VT_BYREF|VT_I4 */

float   FAR* pfltVal; /* VT_BYREF|VT_R4 */

double   FAR* pdblVal; /* VT_BYREF|VT_R8 */

VARIANT_BOOL  FAR* pbool;  /* VT_BYREF|VT_BOOL*/

SCODE   FAR* pscode;  /* VT_BYREF|VT_ERROR*/

CY    FAR* pcyVal; /* VT_BYREF|VT_CY */

DATE   FAR* pdate;  /* VT_BYREF|VT_DATE*/

BSTR   FAR* pbstrVal; /* VT_BYREF|VT_BSTR*/

IUnknown FAR* FAR* ppunkVal; /* VT_BYREF|VT_UNKNOWN */

IDispatch FAR* FAR* ppdispVal; /* VT_BYREF|VT_DISPATCH */

SAFEARRAY FAR* FAR* parray;  /* VT_ARRAY|**/

VARIANT   FAR* pvarVal; /* VT_BYREF|VT_VARIANT */

void   FAR* byref;  /* Generic ByRef   */

};

};

VARIANT는 형태 코드를 가지고 있는 union 구조체입니다. 형을 전환시키기 가장 편한 것이 union이니까요.

여기에 VT_형이 있는데 이 형을 조사해 보면 다음과 같습니다.

VT_EMPTY  = 0,   // 정의 안 됨

VT_NULL  = 1,   // NULL

VT_I2  = 2,   // 2바이트 int

VT_I4  = 3,   // 4바이트 long

VT_R4  = 4,   // 4바이트 DOUBLE

VT_R8  = 5,   // 8바이트 float

VT_CY  = 6,   // Currency

VT_DATE  = 7,   // 날짜

VT_BSTR  = 8,   // 스트링

VT_DISPATCH = 9,   // IDispatch FAR*

VT_ERROR  = 10,   // SCODE

VT_BOOL  = 11,   //BOOL

VT_VARIANT = 12,   // VARIANT FAR*

VT_UNKNOWN  = 13,   // IUnknown FAR*

VT_UI1  = 17,   // unsigned char

이제 형을 알았으니 프로퍼티를 등록시켜 봅시다.

ClassWizard를 실행하여 automation을 선택합니다. 다음 Add Property 버튼을 클릭하면 그림 4-3-9과 같은 화면이 뜰 것입니다.

External name이란 클라이언트와 교신을 할 때 어떤 이름으로 교신을 할까 설정하는 것입니다. NumberData라고 설정한 것은 NameCare에서 번호를 설정하는 것입니다. 다음 형태를 나타내는데 번호는 위에서 long이라고 하였으므로 long을 선택합니다.

본 Externaml name이 현재 저장되는 변수를 varialble name이라고 합니다. 현재 설정되어 있는 것은 m_nNumberData입니다.

(그림 4-3-9)AddProperty 화면

위의 데이터가 변환되거나 또는 어떤 Notification 메시지가 발생되었을 경우 실행하는 함수가 필요하겠지요. 이 함수가 Notification 함수입니다. 현재 설정된 함수명은 OnNumberDataChanged입니다.

그림 4-3-9에서 Parameterlist 박스에는 본 함수가 실행될 때 안에 넣어 주는 인자를 말합니다. 예를 든다면 test()란 인자가 없고 test(int a,char b)란 인자가 int형 a, char형 b가 있는 것이지요. NumberData 프로퍼티에는 인자가 필요없으므로 아무것도 등록하지 않습니다.

위와 같이 설정하면  CardSever 클래스의 BEGIN_DISPATCH_MAP에는 다음과 같이 등록됩니다.

DISP_PROPERTY_NOTIFY(CCardSever, "NumberData", m_nNumberData, OnNumberDataChanged, VT_I4)

즉 CCardSever와 통신할 때 NumberData라고 전해 주면 프로퍼티이며 m_nNumberData 안에 값이 저장되고 m_nNumberData는 VT_I4형으로 long을 나타내는 것입니다.

위와 같은 방법으로 이름과 전화번호의 프로퍼티를 만듭니다.

이름	External Name	: NameData

Type	VARIANT

variable name	: m_strNameData

Notification	: OnNameDataChange

function

전화번호	External Name	: TelNumber

Type            VARIANT

variable name	: m_strTelNumber

Notification	: OnTelNumberChanged

function

이제 프로퍼티는 설정되었습니다. CCardSever 헤더를 보면 다음과 같이 설정되어 있을 것입니다.

long m_nNumberData;

afx_msg void OnNumberDataChanged();

VARIANT m_strNameData;

afx_msg void OnNameDataChanged();

VARIANT m_strTelNumber;

afx_msg void OnTelNumberChanged();

다음 메소드를 만들어 봅시다. 메소드는 어떻게 움직이게 할까요. 현재 프로그램은 단지 현 데이터를 화면에 도시하는 방법만 실행됩니다.

메소드는 ClassWizard에서 Add Method 버튼을 클릭하여 만듭니다.

(그림 4-3-10)AddMethod 실행 화면

위와 같이 설정하면 클래스와 헤더에는 다음과 같이 설정됩니다.

클래스

DISP_FUNCTION(CCardSever, "DisplayDialog", DisplayDialog, VT_BOOL, VTS_NONE)

헤더

afx_msg BOOL DisplayDialog();

이제 DisplayDialog 함수를 고치는 방법만 남았군요. DisplayDialog가 실행되면 그림 4-3-11과 같은 프로그램이 실행됩니다. 그림 4-3-11과 같은 Dialog 클래스는 CNameCardDlb로 만들어 놓았습니다. 이 클래스를 실행시키면 되지요.

(그림 4-3-11)CNameCardDlb 대화상자

BOOL CCardSever::DisplayDialog()

{

//temp Map의 메모리를 lock한다.

AfxLockTempMaps();

//현재 활성화되어 있는 윈도의 핸들을 얻는다.

CWnd* pTopWnd = CWnd::FromHandle(::GetTopWindow(NULL));

//그 핸들을 CNameCardDlb에 설정한다.

CNameCardDlb Dlg(pTopWnd);

//이름을 넣고

Dlg.m_strName=m_strNameData.bstrVal;

//번호를 넣고

Dlg.m_nNumber=m_nNumberData;

//전화번호를 넣는다.

Dlg.m_strTel =m_strTelNumber.bstrVal;

// 대화상자를 실행한다.

if(Dlg.DoModal()==IDOK) //IDOK 이면

{

//이름을 클라이언트에 전송하고

m_strNameData = COleVariant(Dlg.m_strName).Detach();

//번호를 클라이언트에 전송하고

m_nNumberData = Dlg.m_nNumber;

//전화번호를 클라이언트에 전송한다.

m_strTelNumber= COleVariant(Dlg.m_strTel).Detach();

}

//temp Map의 메모리를 Unlock한다.

AfxUnlockTempMaps();

return TRUE;

}

<3>NAMECARD.ODL

OLE Automation으로 클래스를 만들면 odl이란 확장자를 가진 파일이 생성됩니다.

이 파일은 컴파일되면서 TLB를 만들고 서버 프로젝트는 TLB 파일로부터 컨트롤러를 생성할 수 있도록 해줍니다. 다음은 NameCard.odl입니다.

// NameCard.odl : type library source for NameCard.exe

// This file will be processed by the Make Type Library (mktyplib) tool to

// produce the type library (NameCard.tlb).

[ uuid(8CE4E3E1-E950-11CF-8CE0-444553540000), version(1.0) ]

library NameCard

{

importlib("stdole32.tlb");

//  Primary dispatch interface for CCardSever

[ uuid(8CE4E3E5-E950-11CF-8CE0-444553540000) ]

dispinterface ICardSever

{

properties:

// NOTE - ClassWizard will maintain property information here.

//    Use extreme caution when editing this section.

//{{AFX_ODL_PROP(CCardSever)

[id(1)] long NumberData;//NumberData는 ID 1번으로 설정

[id(2)] VARIANT NameData;//NameData는 ID 2번으로 설정

[id(3)] VARIANT TelNumber;//TelNumber는 ID 3번으로 설정

//}}AFX_ODL_PROP

methods:

// NOTE - ClassWizard will maintain method information here.

//    Use extreme caution when editing this section.

//{{AFX_ODL_METHOD(CCardSever)

[id(4)] boolean DisplayDialog(long m_nNumber, VARIANT m_strName);

//DisplayDialog는 id 4번으로 설정

//}}AFX_ODL_METHOD

};

//  Class information for CCardSever

[ uuid(8CE4E3E6-E950-11CF-8CE0-444553540000) ]

coclass CCardSever

{

[default] dispinterface ICardSever;

};

//{{AFX_APPEND_ODL}}

};

중요한 것은 프로퍼티와 메소드가 각각 다른 ID 번호를 가지고 있다는 것입니다. 나중에 이 번호를 이용하여 클라이언트가 서버를 조절하게 됩니다.

<4>Namecard 소스

다음은 CCardSever 클래스의 전체 소스입니다. 나머지 소스부분은 제공되는 CD를 참조하시기 바랍니다.

(프로그램 소스)

// CardSever.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CCardSever command target

class CCardSever : public CCmdTarget

{

DECLARE_DYNCREATE(CCardSever)

CCardSever();           // protected constructor used by dynamic creation

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CCardSever)

public:

virtual void OnFinalRelease();

//}}AFX_VIRTUAL

// Implementation

protected:

virtual ~CCardSever();

// Generated message map functions

//{{AFX_MSG(CCardSever)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

DECLARE_OLECREATE(CCardSever)

// Generated OLE dispatch map functions

//{{AFX_DISPATCH(CCardSever)

long m_nNumberData;

afx_msg void OnNumberDataChanged();

VARIANT m_strNameData;

afx_msg void OnNameDataChanged();

VARIANT m_strTelNumber;

afx_msg void OnTelNumberChanged();

afx_msg BOOL DisplayDialog();

//}}AFX_DISPATCH

DECLARE_DISPATCH_MAP()

DECLARE_INTERFACE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

// CardSever.cpp : implementation file

//

#include "stdafx.h"

#include "NameCard.h"

#include "CardSever.h"

#include "namecardDlb.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CCardSever

IMPLEMENT_DYNCREATE(CCardSever, CCmdTarget)

CCardSever::CCardSever()

{

EnableAutomation();

::VariantInit(&m_strNameData); // necessary initializatioin

m_nNumberData=0;

::VariantInit(&m_strTelNumber);

// To keep the application running as long as an OLE automation

//	object is active, the constructor calls AfxOleLockApp.

AfxOleLockApp();

}

CCardSever::~CCardSever()

{

// To terminate the application when all objects created with

//	with OLE automation, the destructor calls AfxOleUnlockApp.

AfxOleUnlockApp();

}

void CCardSever::OnFinalRelease()

{

// When the last reference for an automation object is released

// OnFinalRelease is called.  The base class will automatically

// deletes the object.  Add additional cleanup required for your

// object before calling the base class.

CCmdTarget::OnFinalRelease();

}

BEGIN_MESSAGE_MAP(CCardSever, CCmdTarget)

//{{AFX_MSG_MAP(CCardSever)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

BEGIN_DISPATCH_MAP(CCardSever, CCmdTarget)

//{{AFX_DISPATCH_MAP(CCardSever)

DISP_PROPERTY_NOTIFY(CCardSever, "NumberData", m_nNumberData,	OnNumberDataChanged, VT_I4)

DISP_PROPERTY_NOTIFY(CCardSever, "NameData", m_strNameData,	OnNameDataChanged, VT_VARIANT)

DISP_PROPERTY_NOTIFY(CCardSever, "TelNumber", m_strTelNumber,	OnTelNumberChanged, VT_VARIANT)

DISP_FUNCTION(CCardSever, "DisplayDialog", DisplayDialog, VT_BOOL,	VTS_NONE)

//}}AFX_DISPATCH_MAP

END_DISPATCH_MAP()

// Note: we add support for IID_ICardSever to support typesafe binding

//  from VBA.  This IID must match the GUID that is attached to the

//  dispinterface in the .ODL file.

// {8CE4E3E5-E950-11CF-8CE0-444553540000}

static const IID IID_ICardSever =

{ 0x8ce4e3e5, 0xe950, 0x11cf, { 0x8c, 0xe0, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } };

BEGIN_INTERFACE_MAP(CCardSever, CCmdTarget)

INTERFACE_PART(CCardSever, IID_ICardSever, Dispatch)

END_INTERFACE_MAP()

// {8CE4E3E6-E950-11CF-8CE0-444553540000}

IMPLEMENT_OLECREATE(CCardSever, "NAMECARD.AUTO", 0x8ce4e3e6, 0xe950, 0x11cf, 0x8c, 0xe0, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0)

/////////////////////////////////////////////////////////////////////////////

// CCardSever message handlers

BOOL CCardSever::DisplayDialog()

{

//temp Map의 메모리를 lock한다.

AfxLockTempMaps();

//현제 활성화 되어있는 윈도우의 핸들을 얻는다.

CWnd* pTopWnd = CWnd::FromHandle(::GetTopWindow(NULL));

//그 핸들을 CNameCardDlb에 설정한다.

CNameCardDlb Dlg(pTopWnd);

//이름을 넣고

Dlg.m_strName=m_strNameData.bstrVal;

//번호를 넣고

Dlg.m_nNumber=m_nNumberData;

//전화 번호를 넣는다.

Dlg.m_strTel =m_strTelNumber.bstrVal;

// 대화상자를 실행한다.

if(Dlg.DoModal()==IDOK) //IDOK 이면

{

//이름을 클라이언트에 전송하고

m_strNameData = COleVariant(Dlg.m_strName).Detach();

//번호를 클라이언트에 전송하고

m_nNumberData = Dlg.m_nNumber;

//전화번호를 클라이언트에 전송한다.

m_strTelNumber= COleVariant(Dlg.m_strTel).Detach();

}

//temp Map의 메모리를 Unlock한다.

AfxUnlockTempMaps();

return TRUE;

}

void CCardSever::OnNumberDataChanged()

{

// TODO: Add notification handler code

}

void CCardSever::OnNameDataChanged()

{

// TODO: Add notification handler code

}

void CCardSever::OnTelNumberChanged()

{

// TODO: Add notification handler code

}

(프로그램 소스끝)

3.클라이언트 프로그램 만들기

이제 NAMECARD를 호출하는 클라이언트 프로그램을 만들어 보겠습니다. 이 프로그램은 OleClient입니다. SDI이며 OLE 기능을 넣지 않았습니다. 소스에 코딩을 하여 클라이언트를 넣으려고 하기 때문입니다. AppWizard를 이용하여 클래스를 만든 다음 OliClientApp 클래스 안의 InitInstance  함수 안에

AfxOleInit();

를 설정해 줍니다.

<1>NameCard 클래스 만들기

NAMECARD 프로그램과 통신을 하는 클래스가 필요합니다. OLE 통신을 지원하는 기본 클래스명이 COleDispatchDriver 클래스입니다. COleDispatchDriver를 상속받은 하나의 클래스를 INameCard로 설정합니다. 본클래스는 클래스 위자드를 사용하여 만들 수 없습니다. 따라서 일반 텍스트 에디터로 만든다음 이것을 프로젝트에 삽입하는 방식을 사용하는 것이 좋습니다.

//INameCard.h COleDispatchDriver를 상속받은 클래스

class INameCard : public COleDispatchDriver

{

public:

INameCard(){}//생성자

INameCard(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch){}

INameCard(const INameCard& dispatchSrc) : COleDispatchDriver(dispatchSrc){}

public:

long GetNumberData();//번호를 받는 함수

void SetNumberData(long); //번호를 설정하는 함수

VARIANT GetNameData();//이름을 받는 함수

void SetNameData(const VARIANT&); //이름을 설정하는 함수

VARIANT GetTelData();//전호번호를 받는 함수

void SetTelData(const VARIANT&); //전화번호를 설정하는 함수

public:

BOOL DisplayDialog();//namecard의 DisplayDialog 실행

};

생성자와 함께 NAMECARD 안의 프로퍼티 3개 번호, 이름, 전화번호를 받고 설정하는 함수 한 개씩 NAMECARD 안의 메소드 DisplayDialog 함수를 실행하는 함수 1개가 있습니다.

(1)번호를 받는 함수 만들기

번호는 NAMECARD.OLD에 ID가 1번으로 정해져 있습니다. 어떤 특정 데이터를 받을 때 사용하는 함수가 GetProperty 함수입니다.

GetProperty(0x1,VT_I4,(void*)&result);

ID는 1번이고 형은 long인 number를 result에 기록하라는 말입니다.

(2)번호를 설정하는 함수 만들기

설정할 때 사용하는 함수는 SetProperty입니다.

SetProperty(0x1,VT_I4,propVal);

1번 ID를 가진 long형의 데이터에 propVal을 설정하라는 말이지요.

매우 간단하죠. 마찬가지로 이름과 전화번호를 설정하고 받는 함수를 기술한다면 다음과 같습니다.

//이름을 받는 함수

VARIANT INameCard::GetNameData()

{

VARIANT result;

GetProperty(0x2,VT_VARIANT,(void*)&result);

return result;

}

//이름을 설정하는 함수

void INameCard::SetNameData(const VARIANT& propVal)

{

SetProperty(0x2,VT_VARIANT,&propVal);

}

//전화번호를 받는 함수

void INameCard::SetTelData(const VARIANT& data)

{

SetProperty(0x3,VT_VARIANT,&data);

}

//전화번호를 설정하는 함수

VARIANT INameCard::GetTelData()

{

VARIANT result;

GetProperty(0x3,VT_VARIANT,(void*)&result);

return result;

}

이제 DisplayDialog 함수를 실행하는 것을 보겠습니다.

BOOL INameCard::DisplayDialog()

{

BOOL result;

InvokeHelper(0x4,DISPATCH_METHOD,VT_BOOL,(void*)&result,NULL);

return result;

}

메소드를 실행하는 함수는 InvokeHelper입니다.

NAMECARD.OLD의 ID가 4번인 것은 DisplayDialog 함수이므로 4번을 설정한 것입니다.

<2>통신하기

Ole 클라이언트를 만들어 서버와 통신할 때는 다음과 같습니다.

① OLE를 설정한다.

② 프로그램을 구동한다.

③ OLE를 해제한다.

여기에서 어떻게 클라이언트와 서버간의 통신이 이루어지는지를 보겠습니다. 그림 4-3-5와 같이  namecard.exe에서 CCmdTarget 클래스를 상속받은 CCardSever 클래스의  m_nNumberData, m_strNameData m_strTelData가 OleClient.exe에서 COleDispatchDriver 클래스를 상속받은 INameCard 클래스와 연결되는데 프로퍼티는 GetProperty와 SetProperty로, 메소드는 InvokeHelper로 연결됩니다. 이 두 관계를 이끌어 주는 것이 IDispatch 인터페이스입니다. 간단히 말해서 클라이언트의 COleDispatchDriver와 서버의 CCmdTarget이 서로 통신을 하는 것이지요.

클라이언트(COleDispatchDriver) <--------> 서버(CCmdTarget)

그럼 두 개가 통신을 할 수 있게 설정해 주는 방법이 필요합니다.

COleClientView 의 헤더에

INameCard m_auto;

INameCard의 클래스를 m_auto라는 변수로 정의해 준 다음

명함철 설정이라는 메뉴를 선택하면

m_auto.CreateDispatch("NAMECARD.AUTO");

Ole를 로드하기 위해서는 CreateDispatch 함수를 실행합니다. 이 때  ID를 설정해 주어야 합니다. NAMECARD의 ID는 NAMECARD.AUTO라고 설정했으므로 NAMECARD.AUTO라고 하면 바로 로드됩니다. 로드된 후에 이름과 번호, 전화번호를 설정합니다.

//이름을 설정한다.

m_auto.SetNameData(COleVariant("이상엽"));

//번호를 설정한다.

m_auto.SetNumberData(1);

//전화번호를 설정한다.

m_auto.SetTelData(COleVariant("555-5555"));

다음 메소드인 DisplayDialog를 실행합니다.

m_auto.DisplayDialog();

이제 실행이 끝난 후 설정을 해제합니다.

m_auto.ReleaseDispatch();

ReleaseDispatch 함수는 연결된 OLE를 해제하는 함수입니다.

이와 같은 내용을 메뉴 항목에서는 3단계로 설정하였습니다. 첫 번째 항목은 명함철 설정,두번째 항목은 명함철 실행, 세 번째 항목은 명함철 삭제 입니다.

각각에 해당하는 함수는 다음과 같습니다.

//메뉴에서 명함철 설정 항목

void COleClientView::OnLoadNamecard()

{

BeginWaitCursor();

//NAMECARD.EXE를 로드하는 함수

if(!m_auto.CreateDispatch("NAMECARD.AUTO"))

{

AfxMessageBox("Ex24b.auto 화일이 등록되어있지 않습니다.\r\n Ole 레지스터에 등록하십시오.");

return;

}

//이름을 설정한다.

m_auto.SetNameData(COleVariant("이상엽"));

//번호를 설정한다.

m_auto.SetNumberData(1);

//전화번호를 설정한다.

m_auto.SetTelData(COleVariant("555-5555"));

}

//메뉴에서 명함철 실행항목

void COleClientView::OnActionNamecard()

{

//NAMECARD.EXE DIALOG실행

m_auto.DisplayDialog();

//번호 데이타를 받고

COleVariant vaData = m_auto.GetNameData();

CString strNameData = vaData.bstrVal;

long lData = m_auto.GetNumberData();

}

//메뉴에서 명함철 삭제 항목

void COleClientView::OnFreeNamecard()

{

m_auto.ReleaseDispatch();

}

<3>프로그램 소스

다음은 OleClient 중에서 INameCard 소스입니다. 나머지 부분은 제공되는 CD를 참조하시기 바랍니다.

(프로그램 소스)

//INameCard.h COleDispatchDriver를 상속받은 클래스

class INameCard : public COleDispatchDriver

{

public:

INameCard(){}//생성자

INameCard(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch){}

INameCard(const INameCard& dispatchSrc) : COleDispatchDriver(dispatchSrc){}

public:

long GetNumberData();//번호를 받는 함수

void SetNumberData(long); //번호를 설정하는 함수

VARIANT GetNameData();//이름을 받는 함수

void SetNameData(const VARIANT&); //이름을 설정하는 함수

VARIANT GetTelData();//전호번호를 받는 함수

void SetTelData(const VARIANT&); //전화번호를 설정하는 함수

public:

BOOL DisplayDialog();//namecard의 DisplayDialog실행

};

//inamecard.cpp

#include "stdafx.h"

#include "inamec~1.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] =__FILE__;

#endif

//번호를 받는 함수

long INameCard::GetNumberData()

{

long result;

GetProperty(0x1,VT_I4,(void*)&result);

return result;

}

//번호를 설정한느 함수

void INameCard::SetNumberData(long propVal)

{

SetProperty(0x1,VT_I4,propVal);

}

//이름을 받는함수

VARIANT INameCard::GetNameData()

{

VARIANT result;

GetProperty(0x2,VT_VARIANT,(void*)&result);

return result;

}

//이름을 설정한는 함수

void INameCard::SetNameData(const VARIANT& propVal)

{

SetProperty(0x2,VT_VARIANT,&propVal);

}

//전화번호를 받는 함수

void INameCard::SetTelData(const VARIANT& data)

{

SetProperty(0x3,VT_VARIANT,&data);

}

//전화 번호를 설정하는 함수

VARIANT INameCard::GetTelData()

{

VARIANT result;

GetProperty(0x3,VT_VARIANT,(void*)&result);

return result;

}

BOOL INameCard::DisplayDialog()

{

BOOL result;

InvokeHelper(0x4,DISPATCH_METHOD,VT_BOOL,(void*)&result,NULL);

return result;

}

(프로그램 소스끝)

(그림 4-3-12)OleClient.exe 실행 화면