1.DDE 프로그래밍

본장은 DDE를 이용한 프로그래밍이 어떤 것인가를 소개합니다. OLE가 있기 이전에 OLE의 모체가 된것이 DDE입니다. DDE란 동적으로 서로다른 프로그램과 데이터를 교환하고자 하기 위해서 만들어진 것입니다. 본장에서는 DDE를 원할하게 하는 DDEML라이브러리를 이용하여 프로그램 메니저와 통신을 하는 간단한 예제를 통해서 DDE의 개념을 이해하도록 합니다.

본장을 통해서 인스톨 프로그램을 어떻게 만드는가에 대해서도 알수가 있을것입니다.

대상자: 중급,고급

Chapter 등급: 선택

공부방법:

본장은 setup프로그램을 직접 제작하고자 하는 분들에게 필요한 부분입니다. 그외 분들은 그냥 책읽듯이 읽으셔서 단순하게 DDE가 어떤것인가를 이해만 하시면 됩니다.

1.DDE 프로그래밍은 무엇인가?

DDE란 Dynamic Data Exchange입니다. 프로그램과 프로그램간에 기본적으로 서로 통신을 하는 내용입니다.

A라는 프로그램은 a라는 작업을 수행하고 B라는 프로그램은 a라는 작업 수행 결과를 가지고 b라는 작업을 수행할 경우, B는 A의 작업 처리 결과를 달라고 요청을 하고 A는 작업이 끝나면 결과를 보낸다는 이야기를 하며 B가 데이터를 받겠다고 하면 A는 결과를 보내고 B가 결과를 받았으면 결과를 받았다고 전달합니다. 분산 처리를 하기 위한 어떤 발단 단계의 라이브러리라고 볼 수가 있는 것이죠.

그러나 DDE 가지고는 분산 처리를 하기에는 너무 부족했었습니다. 컴퓨터 안에 A와 B가 모두 실행되어 있어야 하고 또한 한 컴퓨터의 같은 메모리를 공유하고 있기 때문에 서로 다른 컴퓨터에서 서로 다른 프로세서로 작업을 수행하는 분산 처리 또는 병렬 처리는 가능하지 못했기 때문입니다.

OLE 이전에 DDE는 프로그램간의 통신에 많이 이용했었습니다. SDK 라이브러리로 프로그래밍 하던 때는 DDE처럼 복잡한 게 없었습니다. 또한 프로그램에 버그가 있어도 잡아내기가 굉장히 어려울 정도로 DDE 프로그램이 필자에게는 난벽이었습니다. 또한 데이터 전송도 간단한 명령은 DDE로, 대규모의 데이터는 클립보드나 파일을 이용해서 전달했기 때문에 다소 부족한 게 많았었습니다. 그 후 DDEML이라는 라이브러리가 나왔습니다. DDEML이란 Dynamic Data Exchange Management Library입니다. SDK DDE 함수를 간단하게 묶어 둔 것입니다. 프로그래머가 쉽게 DDE를 사용할 수 있게 말입니다.

그런데 이제 점점 DDE를 사용하는 수가 줄어들었습니다. OLE가 나왔기 때문입니다. OLE의 하부는 실질적으로 DDE 통신을 하고 있지만 그 윗부분에 많은 것이 첨가되어 대규모 데이터 전송 및 인베이딩을 할 수 있게 만들었기 때문에 DDE의 기능을 포함할 뿐 아니라 더욱 강력한 기능이 들어 있는 것이죠.

Part4의 큰 주제가 OLE입니다. OLE를 하기 위해서는 기본적으로 DDE는 알아야 합니다. 그렇다고 DDE를 많이 사용해야 한다는 이야기가 아니라 개념을 알자는 이야기입니다.

아직 DDE를 이용하는 부분은 바로 프로그램 관리자와의 통신입니다. WIN98의 메인 부분이 프로그램 관리자와의 통신이죠. □새로운 폴더를 만들어라□, □그 폴더 안에 특정 파일을 넣어라□, □특정 프로그램을 실행하라□ 등등 윈도우 프로그램 관리자만이 하는 작업들을 DDE를 이용하여 수행시킵니다. 주로 많이 이용하는 것이 인스톨 프로그램입니다. 프로그램을 디스크에 설치하고 프로그램 그룹을 만든 다음 그 안에 설치된 프로그램의 아이콘을 등록시켜서 사용자가 아이콘만 클릭함으로써 프로그램이 실행될 수 있게 만드는 것입니다.

여기서는 DDEML을 이용한 INSTALL 프로그램을 제작합니다. DDEML에서 클라이언트 영역만 프로그램하는 것입니다. 본 장의 목표는 OLE에 접근하기 위한 기본 지식을 알고자 하는 것이고 또한 INSTALL 프로그램을 어떻게 만드는가를 알자는 것입니다.

본 장에서 제공하는 프로그램은 Ex2031, Ex2032, Ex2033입니다. Ex2031은 Win32를 이용하여 프로그램한 예제이고 Ex2032는 앞에서 배운 DLL에서 만들지 않은 정규 DLL로 INSTALL 함수를 만듭니다 다음 Ex2032.dll을 이용한 MFC를 이용한 인스톨 프로그램을 제작해 보겠습니다.

<1>DDEML 초기화

우선 DDE를 하기 위해서는 초기화를 해주어야 합니다. 초기화를 해주는 함수는 DdeInitialize입니다.

UINT DdeInitialize(

LPDWORD  pidInst, // 초기화하였을 때 할당되는 ID

PFNCALLBACK  pfnCallback, // 메시지를 주고받는 프로시저 함수 포인터

DWORD  afCmd, // 필터 플래그

DWORD  ulRes  // 0으로 설정함.

);

DdeInitialize 함수를 실행시키고 초기화가 성공하면 pidInst에 ID를 할당해 줍니다. pfnCallback이란 프로시저이므로 모든 메시지 및 이벤트를  pfnCallback에 연결된 함수에 넘겨줍니다. 필터 플래그 afCmd는 통신을 할 때 어떻게 할 것인가의 필터입니다. 이 필터에는 많은 플래그들이 있습니다. APPCLASS_STANDARD란 보통 형태의 DDE를 하자는 이야기이고 그 외의 많은 플래그들은 도움말을 참조하세요.

본 장에서는 APPCMD_CLIENTONLY를 사용합니다. 클라이언트용으로만 DDE를 설정하겠다는 이야기이죠.

이제 초기화가 성공하면 통신하고자 하는 서버명을 만들어야 합니다.DDE통신을 할경우에는 어떤 데이터 이든지 DDE전용으로 사용하는 문자열로 바꾸고 이문자열의 핸들을 얻어서 사용합니다. 이 때 사용하는 함수가 DdeCreateStringHandle입니다.

HSZ DdeCreateStringHandle(

DWORD  idInst, // instance ID

LPTSTR  psz, // DDE 문자열로 만들 스트링

int  iCodePage  // 코드 페이지

);

예를 들어, 서버 클래스명이 □TEST□라면 다음과 같이 합니다.

HSZ    szTest;

szTest = DdeCreateStringHandle(idInst,"TEST",CP_WINANSI)

DDE 통신을 할 때 스트링은 자체를 이용하는 것이 아니라 DDE 통신을 하는 특정 포맷으로 전환해야 합니다. HSZ형이 바로 통신을 하는 데 필요한 스트링 포맷입니다. □TEST□라는 클래스명을 가지고 있는 프로그램과 통신을 하고자 한다면 위의 DdeInitalize 호출 후 할당된 ID pidInst와 함께 클래스명을 넣고 이것을 ANSI 형태로 전환시켜 szTest에 할당하자는 이야기입니다.

이제 "TEST"라는 프로그램과 연결합니다. 연결할 때 사용하는 함수는 DdeConnect입니다.

HCONV DdeConnect(

DWORD  idInst,

HSZ  hszService, // 서버명

HSZ  hszTopic, // 토픽명

PCONVCONTEXT  pCC  // 컨텍스트값

);

HCONV      hConv;

CONVCONTEXT   CCFilter = { sizeof (CONVCONTEXT), 0, 0, 0, 0L, 0L };

hConv = DdeConnect (pidInst, szTest, szTest, &CCFilter);

CCFilter는 DDE 통신을 하기 위한 데이터를 컨버전하기 위한 정보입니다. 사용자가 새로운 필터를 만들면 데이터가 이 필터를 통해서 전환되어 들어옵니다. 보통 0으로 설정하여 윈도우에서 사용하는 디폴트값을 사용합니다. 첫번째 인자 pidInst는 ID이고 두 번째 인자는 서버명입니다. 세 번째 인자는 이 DDE를 하는 토픽을 말합니다. 위의 예에서는 토픽과 서버명을 같이 두었지만 따로 사용해도 됩니다.

DdeConnect가 성공적으로 끝나면 대화 인스턴스라고 할 수 있는 HCONV에 인스턴스값이 들어옵니다. 나중에 대화를 할 때 이 인스턴스를 사용합니다.

<2>DDEML 통신하기

DDE로 통신을 하려면 어떤 문장이 필요할 것입니다. 이런 문장형이 HSZ라고 위에서 설명을 했습니다. DDE로 데이터를 전송할 경우에도 데이터를 전환을 해야 합니다. 이 때 전환하는 데이터형이 HDDEDATA입니다. 스트링을 HDDEDATA형으로 바꾸는 함수는  DdeCreateDataHandle입니다.

HDDEDATA DdeCreateDataHandle(

DWORD  idInst, // ID

LPBYTE  pSrc, // 데이터 스트링

DWORD  cb, // 데이터의 길이

DWORD  cbOff, // 소스 버퍼의 시작점

HSZ  hszItem, // 아이템의 이름

UINT  wFmt, // 클립보드에 들어갈 데이터형

UINT  afCmd  // creation flags

);

□전송 데이터 009!□라는 데이터를 □TEST□에 전해 주려고 한다면 먼저 데이터를 전환시켜야 합니다.

HDDEDATA   hData;

hData = DdeCreateDataHandle (idInst,(unsigned char*)"전송 데이터 009!",

16, 0, (HSZ) NULL, CF_TEXT, 0L);

이제 이 데이터를 서버에 전송합니다. 이 때 사용하는 함수가 DdeClientTran- saction입니다.

HDDEDATA DdeClientTransaction(

LPBYTE  pData, // 서버에 보낼 데이터

DWORD  cbData, // 데이터의 길이

HCONV  hConv, // 대화창구 인스턴스

HSZ  hszItem, // 아이템의 이름

UINT  wFmt, // 클립보드에 저장시 저장할 형태

UINT  wType, // 트랜잭션 타입

DWORD  dwTimeout, // 타임 지속 시간

LPDWORD  pdwResult  // 결과값

);

위의 내용을 □TEST□에 보낸다면 다음과 같이 합니다.

unsigned long       lResult;

DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult);

<3>DDEML 통신 종료

DDE를 종료시킬 때는 먼저 idInst에 연결된 스트링을 해제해야 합니다. 이 때 사용하는 함수가 DdeFreeStringHandle입니다.

BOOL DdeFreeStringHandle(

DWORD  idInst, // ID

HSZ  hsz  // 서버명 스트링

);

본 예제에서는 다음과 같이 idInit와 szTest를 해제합니다.

DdeFreeStringHandle (idInit, szTest);

이제 연결된 DDE 대화창을 완전히 닫습니다. 이 때 사용하는 함수는 DdeDisconnect입니다.

DdeDisconnect (hConv);

위의 1, 2, 3번에 의해서 DDE를 실행하면 클라이언트 영역에서 DDE 통신이 이루어집니다. 서버 영역도 이와 비슷합니다. 다만 기다리고 있는 부분과 데이터 전송이 클라이언트라는 점이 다릅니다.

2.Win32 DDEML 예제 Ex2031

Ex2031 예제는 Win32SDK용으로 작성된 Install 프로그램 예제입니다.

본 프로그램은 실행되자마자 타이머 1번에 0.5초를 걸어 둡니다. 그럼 0.5초 후에는 WM_TIMER라는 메시지가 실행됩니다. 이곳에서 □Test Application□이라는 그룹을 만들고 그 그룹 폴더 안에 c:\cppwork\bin\wavemix.exe를 등록하고 등록된 프로그램 이름을 □웨이브테스트□라고 설정합니다. 이 프로그램이 실행된 후 0.5초 후에 화면에는 Test Applicaiton이라는 폴더가 만들어질 것이고 그 안에 특정 프로그램이 설정될 것입니다. 본 프로그램의 예제는 Ex2031이며 그림 4-1-1이 Ex2031.exe를 실행시킨 모습입니다.

이 프로그램은 프로젝트를 만들 때  Win32 Application을 선택해서 만들어야 합니다.

(그림 4-1-1)Ex2031 실행결과

<1>초기화 함수 DDEInit()

DDE를 하기 위한 기본적인 서버명 스트링과 전송할 데이터 포인터 대화창구 인스턴스 결과값을 저장하는 변수나 텍스트를 저장하는 변수, ID, 필터 등을 extern에 설정합니다.

DWORD lIdInst; //클라이언트 ID

CONVCONTEXT   CCFilter = { sizeof (CONVCONTEXT), 0, 0, 0, 0L, 0L };

HSZ        szProgMan;//프로그램 관리자명을 담은 스트링

HCONV      hConv;//대화창구 인스턴스

HDDEDATA   hData;//전송할 데이터 포인터

char szText[80];//기본 템플레이트

LONG       lResult;//결과값을 저장하는 변수

위와 같이 설정한 다음 모든 함수는 위의 데이터를 이용합니다. 클래스로 만든다면 위의 내용이 멤버가 됩니다.

DDE를 초기화하는 함수를 만들어 보겠습니다. 함수명은 DDEInit라고 설정합니다.

//DDE를 초기화한다.

int DDEInit()

{

//DDE를 초기화하고

if(DdeInitialize (&lIdInst, (PFNCALLBACK) GroupDDECallback,(DWORD) APPCMD_CLIENTONLY, 0L))

return FALSE;

//프로그램 관리자와 연결하기 위해 스트링을 만들고

szProgMan = DdeCreateStringHandle (lIdInst, "PROGMAN", CP_WINANSI);

//프로그램 관리자와 통신을 한다.

hConv = DdeConnect (lIdInst, szProgMan, szProgMan, &CCFilter);

}

프로그램 관리자의 프로그램 이름은 □PROGMAN□입니다. 이 형태로 szProgMan에 스트링으로 연결한 다음 DdeConnect를 이용하여 lIdInst에 인스턴스값을 연결시킵니다. CCFilter는 0으로 설정되었습니다. 위의 프로그램을 실행시키면 프로그램 관리자와 연결됩니다.

이 때 CallBack 함수로 GroupDDECallback 함수를 설정하였는데 이 함수 안에서 하는 일은 없습니다. 이벤트를 받을 필요없이 단순히 그룹을 만들고 그 안에 프로그램을 설정하는 것이니까요.

//CallBack 함수 아무것도 안 함.

HDDEDATA CALLBACK GroupDDECallback (UINT uiType, UINT uiFmt, HANDLE hConv,

HSZ sz1, HSZ sz2, HDDEDATA hData, LONG lData1, LONG lData2) {

return ((HDDEDATA) NULL);

}

<2>그룹만들기와 아이템 등록

그룹을 만들 때 프로그램 관리자에게 주는 명령문이 있습니다. 그룹을 만들라고 할 경우에는 □[CreateGroup(그룹명)]□입니다. 또한 아이템을 그룹에 등록할 경우에는 [AddItem(파일명, 아이콘명)]입니다. 그 외 많은 명령어는 MS 사에서 나온 SDK 프로그램 시리즈를 참고하세요.

먼저 그룹을 만드는 함수입니다.

//그룹을 만든다.

int DDEInstallAddGroup(LPSTR data)

{

//그룹을 만드는 명령어를 쓰고

sprintf (szText, "[CreateGroup(%s)]",data);

//명령어를 데이터로 만든 다음

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

//전송한다.

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

//이미 만들어진 그룹일 경우에는 화면에 나타나게 하고

//처음으로 만든 것이라도 화면에 보여 준다.

sprintf (szText, "[ShowGroup(%s)]",data);

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

그룹을 만들 때는 CreateGroup 명령어를 사용하여 프로그램 관리자에게 보내고 난 후 만든 그룹을 화면에 표시해 주어야 합니다. 이 때 ShowGroup 명령어를 사용하여 현재 만들어진 그룹을 화면에 보여 줍니다.

만일 만들려고 한 그룹이 이미 존재한다면 CreateGroup에서는 실패하지만 ShowGroup에서는 성공하므로 똑같이 화면에 나타나게 되어 있습니다.

이제 만든 그룹 안에 프로그램을 삽입합니다.

//프로그램을 등록한다.

int DDEInstallAddProgram(LPSTR path,LPSTR name)

{

//프로그램 등록 명령어를 만든 다음

sprintf (szText, "[AddItem(%s,%s)]",path,name);

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

//데이터를 전송한다.

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

<3>DDE 종료

DDE 종료는 간단합니다. 먼저 프로그램 관리자와의 연결을 해제하고 대화창구를 종료하면 됩니다. 해제는 DdeFreeStringHandle을 이용하고 대화창구 종료는 DdeDisconnect입니다.

//DDE를 종료한다.

void DDEClose()

{

//ID를 해제하고

DdeFreeStringHandle (lIdInst, szProgMan);

//DDE 대화창구를 종료한다.

DdeDisconnect (hConv);

}

<4>메인 프로시저

이제 실질적으로 위의 함수를 이용하는 메인프로시저를 보겠습니다. 먼저 프로그램이 만들어지면 타이머를 걸어서 0.5초 후에 WM_MESSAGE가 발생되게 합니다.

case WM_CREATE: {

//타이머를 만든다.

SetTimer(hwnd,1,500,NULL);

break;

}

다음  WM_TIMER의 메시지가 왔을 때 그룹을 만들고 그룹 안에 프로그램을 등록합니다.

case WM_TIMER:

{

//타이머를 해제하고

KillTimer(hwnd,1);

//DDE를 초기화하고

DDEInit();

//그룹을 만들고

DDEInstallAddGroup((LPSTR)"Test Application");

//프로그램을 등록한 다음

//프로그램 등록 파일명은 현재 상황에 맞게 변경한다.

DDEInstallAddProgram((LPSTR)"C:\\CPPWORK\\BIN\\

WAVEMIX.EXE",

(LPSTR)"웨이브테스트");

//DDE를 해제한다.

DDEClose();

break;

}

본프로그램을 실행하기 위해서는 C:\CPPWORK\BIN 디렉토리 안에 WAVEMIX.EXE가 존재하거나 또는 위의 DDEInstallAddProgram 의 파일명을

바꾸어 주어야 합니다.

<5>프로그램 소스

다음은 Ex2031.c의 전체 소스입니다.

//Ex2031.c DDEML를 이용한 Win32 프로그램

#include <windows.h>

#include <ddeml.h>

LRESULT APIENTRY    MainWndProc (HWND, UINT, WPARAM, LPARAM);

HANDLE           ghModule;

DWORD lIdInst;

HWND ghwndMain;

CONVCONTEXT   CCFilter = { sizeof (CONVCONTEXT), 0, 0, 0, 0L, 0L };

//메인프로그램

int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst,

LPSTR lpCmdLine, int iShowCmd) {

MSG   msg;

WNDCLASS wc;

wc.style         = CS_HREDRAW | CS_VREDRAW;

wc.lpfnWndProc   = (WNDPROC)MainWndProc;

wc.cbClsExtra    = 0;

wc.cbWndExtra    = sizeof(LONG);

wc.hInstance     = ghModule;

wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor       = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

wc.lpszMenuName  = "MainMenu";

wc.lpszClassName = "DDEMLInstall";

if (!RegisterClass(&wc))

return FALSE;

ghwndMain = CreateWindowEx (0L, "DDEMLInstall", "Sample Installer",

WS_OVERLAPPED   | WS_CAPTION     | WS_BORDER       |

WS_THICKFRAME   | WS_MAXIMIZEBOX | WS_MINIMIZEBOX  |

WS_CLIPCHILDREN | WS_SYSMENU,

80, 70, 550, 250,

NULL, NULL, ghModule, NULL);

if (ghwndMain == NULL)

return FALSE;

ShowWindow (ghwndMain, SW_SHOWDEFAULT);

UpdateWindow (ghwndMain);

while (GetMessage (&msg, NULL, 0, 0)) {

TranslateMessage (&msg);

DispatchMessage (&msg);

}

return (TRUE);

}

//CallBack함수 아무것도 안함

HDDEDATA CALLBACK GroupDDECallback (UINT uiType, UINT uiFmt, HANDLE hConv,

HSZ sz1, HSZ sz2, HDDEDATA hData, LONG lData1, LONG lData2) {

return ((HDDEDATA) NULL);

}

HSZ        szProgMan;//프로그램 매지저 이름을 담은 스트링

HCONV      hConv;//대화창구 인스턴스

HDDEDATA   hData;//전송할 데이타 포인터

char szText[80];//기본 템플레이트

LONG       lResult;//결과값을 저장하는 변수

//DDE를 초기화 한다.

int DDEInit()

{

//dde를 초기화 하고

if(DdeInitialize (&lIdInst, (PFNCALLBACK) GroupDDECallback,(DWORD)

APPCMD_CLIENTONLY, 0L))

return FALSE;

//프로그램 메니저와 연결하기 위해 스트링을 만들고

szProgMan = DdeCreateStringHandle (lIdInst, "PROGMAN", CP_WINANSI);

//프로그램 메니저와 통신을 한다.

hConv = DdeConnect (lIdInst, szProgMan, szProgMan, &CCFilter);

}

//DDE를 종료한다.

void DDEClose()

{

//ID를 해제하고

DdeFreeStringHandle (lIdInst, szProgMan);

//DDE대화창구를 종료한다.

DdeDisconnect (hConv);

}

//그룹을 만든다.

int DDEInstallAddGroup(LPSTR data)

{

//그룹을 만드는 명령어를 쓰고

sprintf (szText, "[CreateGroup(%s)]",data);

//명령어를 데이타로 만든다음

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

//전송한다.

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

//이미 만들어진 그룹일경우에는 화면에 나타나게 하고

//처음으로 만든것이라도 화면에 보여준다.

sprintf (szText, "[ShowGroup(%s)]",data);

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

//프로그램을 등록한다.

int DDEInstallAddProgram(LPSTR path,LPSTR name)

{

//프로그램 등록 명령어를 만든다음

sprintf (szText, "[AddItem(%s,%s)]",path,name);

hData = DdeCreateDataHandle (lIdInst, szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

//데이타를 전송한다.

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

//메세지 프로시저

LRESULT APIENTRY MainWndProc (HWND hwnd, UINT uiMsg, WPARAM wParam,

LPARAM lParam) {

switch (uiMsg) {

//프로그램이 만들어질때

case WM_CREATE: {

//타이머를 만든다

SetTimer(hwnd,1,500,NULL);

break;

}

case WM_DESTROY: {

PostQuitMessage (0);

return (0L);

}

case WM_TIMER:

{

//타이머를 해제하고

KillTimer(hwnd,1);

//DDE를 초기화하고

DDEInit();

//그룹을 만들고

DDEInstallAddGroup((LPSTR)"Test Application");

//프로그램을 등록한다음

//프로그램 등록화일명은 현재 상황에 맞게 변경하세요

DDEInstallAddProgram((LPSTR)

"C:\\CPPWORK\\BIN\\WAVEMIX.EXE",

(LPSTR)"웨이브테스트");

//DDE를 해제한다.

DDEClose();

break;

}

}

return (DefWindowProc (hwnd, uiMsg, wParam, lParam));

}

(프로그램 소스끝)

3.인스톨 전용 DLL 만들기

이제 Ex1031에서 제작한 내용을 모두 정규 DLL로 전환시키겠습니다. DLL를 만드는 방법은 Part2의 1장에서 자세히 설명하였습니다. 본항목에서 만드는 DLL 은 Ex2032입니다. 이예제는 프로젝트를 만들때 정규 DLL를 이용하여 만들고 이렇게 만들어진 프로젝트안에는 Ex2032.cpp가 있을것입니다. 이파일에 위에서 만든 DDEInit, DDEInstallAddGroup, DDEInstallAddProgram, DDEClose를 기록합니다. 기록하는 내용은 다음과 같습니다.

HDDEDATA CALLBACK GroupDDECallback (UINT uiType, UINT uiFmt, HANDLE hConv,

HSZ sz1, HSZ sz2, HDDEDATA hData, LONG lData1, LONG lData2) {

return ((HDDEDATA) NULL);

}

CONVCONTEXT   CCFilter = { sizeof (CONVCONTEXT), 0, 0, 0, 0L, 0L };

DWORD lIdInst;

HSZ        szProgMan;

HCONV      hConv;

HDDEDATA   hData;

char szText[80];

unsigned long       lResult;

extern "C" __declspec(dllexport) int DDEInit()

{

if(DdeInitialize (&lIdInst, (PFNCALLBACK) GroupDDECallback,(DWORD) APPCMD_CLIENTONLY, 0L))

return FALSE;

szProgMan = DdeCreateStringHandle (lIdInst, "PROGMAN", CP_WINANSI);

hConv = DdeConnect (lIdInst, szProgMan, szProgMan, &CCFilter);

return TRUE;

}

extern "C" __declspec(dllexport) void DDEClose()

{

DdeFreeStringHandle (lIdInst, szProgMan);

DdeDisconnect (hConv);

}

extern "C" __declspec(dllexport) int DDEInstallAddGroup(LPSTR data)

{

sprintf (szText, "[CreateGroup(%s)]",data);

hData = DdeCreateDataHandle (lIdInst,(unsigned char*) szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

sprintf (szText, "[ShowGroup(%s)]",data);

hData = DdeCreateDataHandle (lIdInst,(unsigned char*) szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

extern "C" __declspec(dllexport) int DDEInstallAddProgram(LPSTR path,LPSTR name)

{

sprintf (szText, "[AddItem(%s,%s)]",path,name);

hData = DdeCreateDataHandle (lIdInst,(unsigned char*) szText,

strlen (szText) + 1, 0, (HSZ) NULL, CF_TEXT, 0L);

if (!DdeClientTransaction ((LPBYTE) hData, 0xFFFFFFFF, hConv,

(HSZ) NULL, 0, XTYP_EXECUTE, 10000, &lResult))

return FALSE;

return TRUE;

}

위와 같이 한후 컴파일하게 되면  Install 전용 Ex2032.Dll이 만들어 질것입니다.

4.MFC에서 구동하는 인스톨 프로그램 Ex2033

Ex2033.exe 프로그램의 실행 모습은 그림 4-1-2와 같습니다. 메뉴에 Install이란 항목이 있고 이 항목에서 DDE 연결을 선택하면 프로그램 관리자와 연결됩니다. 이 때 □그룹만들기□와 □프로그램 등록□, □DDE 해제□라는 항목이 활성화됩니다. □그룹만들기□를 선택하고 원하는 그룹명을 입력하면 그룹이 만들어지고, □프로그램 등록□을 설정하면 만들어진 그룹에 설정한 프로그램을 등록시켜 줍니다. 다음 □DDE 해제□라는 항목을 누르면 현재 연결된 프로그램 관리자와의 연결이 해제됩니다.

우선 Ex2032.dll를 호출하려면 Settings 메뉴를 사용하여 Ex2032.lib를 프로그램에 설정하고 사용할 함수를 extern□C□ 형태로 View의 헤더에 설정합니다.

//Ex2032에서 만든 DLL 안에 있는 함수들을 설정해 준다.

//DDE 초기화 함수

extern "C" __declspec(dllexport) int DDEInit();

//DDE 닫기 함수

extern "C" __declspec(dllexport) void DDEClose();

//그룹만들기 함수

extern "C" __declspec(dllexport) int DDEInstallAddGroup(LPSTR data);

//프로그램 등록하기 함수

extern "C" __declspec(dllexport) int DDEInstallAddProgram(LPSTR path,LPSTR name);

(그림 4-1-2)Ex2033 실행결과

Ex2033에서는 export가 import로 바뀐 것에 유념하세요.

제일 먼저 메뉴에서 DDE 연결을 설정하였을 때는 DDEInit()를 호출하면 됩니다.

//DDE 연결 메뉴를 선택했을 경우

void CEx2033View::OnDdeinst()

{

//프로그램 관리자와 연결한다.

DDEInit();

m_nConnect=TRUE;

}

□그룹만들기□ 항목을 눌렀을 때는 그룹명을 입력하는 대화상자를 만들어서 띄운 다음에 그곳에서 입력한 데이터로 그룹을 만듭니다.

//그룹만들기를 설정하였을 경우

void CEx2033View::OnDdeaddgroup()

{

//그룹명 입력 다이얼로그를 실행시키고

CAddGroup Dlg;

char temp[80];

if(Dlg.DoModal()==IDOK)

{

//그룹명을 받아서 그룹을 만든다.

lstrcpy((LPSTR)temp,(LPSTR)Dlg.m_strGroupName.operator const char*());

DDEInstallAddGroup((LPSTR)temp);

}

}

이렇게 하면 화면에 그룹이 만들어지고 이 만들어진 그룹에 프로그램 등록을 할 경우 프로그램 등록 대화상자를 실행시켜서 프로그램명과 파일명을 받게 만든 다음 그 데이터로 프로그램을 등록합니다.

//□프로그램 등록□이라는 메뉴를 선택했을 경우

void CEx2033View::OnAdditem()

{

//프로그램 등록 다이얼로그를 실행한 후

CAddItem Dlg;

if(Dlg.DoModal()==IDOK)

{

//프로그램을 등록한다.

DDEInstallAddProgram((LPSTR)Dlg.m_strPathName.operator const char*(),

(LPSTR)Dlg.m_strFileName.operator const char*());

}

}

이렇게 인스톨용 DLL을 만들면 프로그램 제작 후 매번 반복하는 인스톨 프로그램을 쉽게쉽게 제작할 수 있습니다.

다음은 Ex2033소스중 View클래스 부분입니다.

(프로그램 소스)

// Ex2033Vw.h : interface of the CEx2033View class

//

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

//Ex2032에서 만든 dll안에 있는 함수들을 설정해준다

//DDE초기화 함수

extern "C" __declspec(dllexport) int DDEInit();

//DDE 닫기 함수

extern "C" __declspec(dllexport) void DDEClose();

//그룹을 만들기 함수

extern "C" __declspec(dllexport) int DDEInstallAddGroup(LPSTR data);

//프로그램을 등록하기 함수

extern "C" __declspec(dllexport) int DDEInstallAddProgram(LPSTR path,LPSTR name);

class CEx2033View : public CView

{

protected: // create from serialization only

CEx2033View();

DECLARE_DYNCREATE(CEx2033View)

// Attributes

public:

CEx2033Doc* GetDocument();

BOOL m_nConnect;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEx2033View)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CEx2033View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CEx2033View)

afx_msg void OnUpdateAdditem(CCmdUI* pCmdUI);

afx_msg void OnUpdateDdeaddgroup(CCmdUI* pCmdUI);

afx_msg void OnUpdateDdeclose(CCmdUI* pCmdUI);

afx_msg void OnUpdateDdeinst(CCmdUI* pCmdUI);

afx_msg void OnDdeclose();

afx_msg void OnDdeinst();

afx_msg void OnAdditem();

afx_msg void OnDdeaddgroup();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in Ex2033Vw.cpp

inline CEx2033Doc* CEx2033View::GetDocument()

{ return (CEx2033Doc*)m_pDocument; }

#endif

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

// Ex2033Vw.cpp : implementation of the CEx2033View class

//

#include "stdafx.h"

#include "Ex2033.h"

#include "Ex2033Dc.h"

#include "Ex2033Vw.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CEx2033View

IMPLEMENT_DYNCREATE(CEx2033View, CView)

BEGIN_MESSAGE_MAP(CEx2033View, CView)

//{{AFX_MSG_MAP(CEx2033View)

ON_UPDATE_COMMAND_UI(IDD_ADDITEM, OnUpdateAdditem)

ON_UPDATE_COMMAND_UI(IDM_DDEADDGROUP, OnUpdateDdeaddgroup)

ON_UPDATE_COMMAND_UI(IDM_DDECLOSE, OnUpdateDdeclose)

ON_UPDATE_COMMAND_UI(IDM_DDEINST, OnUpdateDdeinst)

ON_COMMAND(IDM_DDECLOSE, OnDdeclose)

ON_COMMAND(IDM_DDEINST, OnDdeinst)

ON_COMMAND(IDD_ADDITEM, OnAdditem)

ON_COMMAND(IDM_DDEADDGROUP, OnDdeaddgroup)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

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

// CEx2033View construction/destruction

CEx2033View::CEx2033View()

{

m_nConnect=FALSE;

}

CEx2033View::~CEx2033View()

{

}

BOOL CEx2033View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

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

// CEx2033View drawing

void CEx2033View::OnDraw(CDC* pDC)

{

CEx2033Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

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

// CEx2033View printing

BOOL CEx2033View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CEx2033View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CEx2033View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

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

// CEx2033View diagnostics

#ifdef _DEBUG

void CEx2033View::AssertValid() const

{

CView::AssertValid();

}

void CEx2033View::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CEx2033Doc* CEx2033View::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx2033Doc)));

return (CEx2033Doc*)m_pDocument;

}

#endif //_DEBUG

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

// CEx2033View message handlers

void CEx2033View::OnUpdateAdditem(CCmdUI* pCmdUI)

{

pCmdUI->Enable(m_nConnect);

}

void CEx2033View::OnUpdateDdeaddgroup(CCmdUI* pCmdUI)

{

pCmdUI->Enable(m_nConnect);

}

void CEx2033View::OnUpdateDdeclose(CCmdUI* pCmdUI)

{

pCmdUI->Enable(m_nConnect);

}

void CEx2033View::OnUpdateDdeinst(CCmdUI* pCmdUI)

{

pCmdUI->Enable(1-m_nConnect);

}

//연결해제

void CEx2033View::OnDdeclose()

{

DDEClose();

m_nConnect=FALSE;

}

//dde연결 메뉴를 선택하였을경우

void CEx2033View::OnDdeinst()

{

//프로그램 관리자와 연결한다.

DDEInit();

m_nConnect=TRUE;

}

#include "additem.h"

//프로그램 등록 이라는 메뉴를 선택하였을경우

void CEx2033View::OnAdditem()

{

//프로그램 등록 다이얼로그가 실행한후

CAddItem Dlg;

if(Dlg.DoModal()==IDOK)

{

//프로그램을 등록한다.

DDEInstallAddProgram((LPSTR)Dlg.m_strPathName.operator const char*(),

(LPSTR)Dlg.m_strFileName.operator const char*());

}

}

#include "addgroup.h"

//그룹을 만들기를 설정하였을경우

void CEx2033View::OnDdeaddgroup()

{

//그룹명 입력 다이얼로그를 실행시키고

CAddGroup Dlg;

char temp[80];

if(Dlg.DoModal()==IDOK)

{

//그룹명을 받아서  그룹을 만든다.

lstrcpy((LPSTR)temp,(LPSTR)Dlg.m_strGroupName.operator const char*());

DDEInstallAddGroup((LPSTR)temp);

}

}

(프로그램 소스끝)