본문 바로가기
일하는 중에

MFC SDI 기반의 ActiveX - CS 프로그램의 일부를 ActiveX로 포팅하기(3)

by likebnb 2013. 12. 21.

It's Now or Never

Aaron Schroeder



0. 들어가는 말

자 이제 무엇을 만들어야 할지는 정해졌다. "어떻게 만들어야 하는가"를 알아야 할 때다. Visual Studio C++을 가지고 

MFC SDI 기반의  ActiveX를 만들려면 어떻게 해야할까? 이 질문에 대한 답을 써내려가기 전에 기술적인 설명의 깊이를

조절하기 위해 독자들이 기본적으로 Visual Studio C++을 가지고 프로그래밍하는 것에 어려움이 없는, 다만 

Visual Studio C++을 가지고 ActiveX를 만들어 본 적이 없거나 적어도 앞으로 이야기하려는 에피소드들에 대해선 경험이

없는 프로그래머들이라고 가정한다. 물론 이제 막 Visual Studio C++을 시작하려는 분들은 읽지 말라는 건 아니다.


몇 가지 아주 기본적인 개념을 점검하는 것으로 시작해서 ActiveX가 갖춰야 할 기본적인 모양새를 다듬어 보도록 하자.

그리고 ActiveX가 필요했던 근본적인 이유, 즉 웹페이지 안에 ActiveX를 담는 방법과 웹사용자들과의 인터렉션을 구현

하는 방법 그리고 마지막으로 ActiveX를 웹을 통해 사용자들의 컴퓨터에 배포하는 방법 등을 설명하고자 한다.



1. MFC, SDI는 무엇?

1992년에 발표된 MFC(Microsoft Foundation Class library)는 TCL에서 영감을(?) 받아 만들어졌다고 한다. 

초기엔 볼랜드 OWL(이후 VCL이 된다)과의 경쟁에서 한 동안 밀렸던 시절도 있었으나 홈그라운드의 이점으로 

윈도 프로그래머들의 각광을 받아 누리고 있다(참고, 위키). 


Visual Studio C++을 사용하는 프로그래머들은 왜 MFC를 사용할까? 그 이유는 바로 윈도 API를 객체지향적인 

방법으로 프로그래밍에 이용할 수 있다는 장점 때문이다. 게다가 MFC를 사용하면 Document/View로 대변되는 

MVC 디자인 패턴을 어렵지 않게 구현할 수 있다는 장점이 있다. 


MFC는 많은 기술들이 집약된 프레임워크인데 그 중에서도 핵심에 해당하는 것이 Document/View라고 한다.

MVC라는 것은 현대의 개발 프레임워크에선 널리 쓰이는 패턴으로써 데이터 모델과 사용자 인터페이스 그리고

제어 로직(비지니스 로직)을 분리해서 관리할 수 있도록 해준다. MFC에서는 하나의 Document를 관리하는지 

아니면 여러 개의 Document를 처리하는지에 따라 SDI(Single Document Interface) 또는 MDI로 구분한다. 


그러니까 앞으로 우리가 할 일은 Visual Studio C++을 가지고 MFC의 SDI 프레임워크를 적용한 ActiveX를

만드는 것이다. 



2. ActiveX와 EXE의 관계는?

ActiveX는 그 기원을 OCX에 두고 있다. 그리고 OCX는 OLE Custom Control을 줄여 쓴 말인데, 여기서 

OLE는 다시 Object Linking and Embedding의 줄임말이다. OLE를 프로그래밍 관점에서 다른 말로 풀면

컴포넌트 또는 DLL이라는 말로 대신할 수 있겠다. 즉 ActiveX는 DLL과 같은 형태로 OLE 또는 COM을 

구현한 컴포넌트라는 것이다. 


ActiveX가 OLE Custom Control에 그 기원을 두고 있다는 것은 다음 코드를 보면 알 수 있다. VC++의

마법사를 통해 ActiveX 프로젝트인 LikeBnbX를 생성한 뒤 헤더파일, LikeBnbXCtrl.h을 열어보면 

컨트롤 클래스인  CLikeBnbXCtrl은 COleControl 클래스를 상속받고 있음을 확인할 수 있다.


class CLikeBnbXCtrl : public COleControl


정리하자면 ActiveX는 DLL 형태의 컴포넌트 그리고 EXE는 이를 담아내는 그릇이라 할 수 있다. 컴포넌트는

단독으로는 실행될 수 없으며 자신을 메모리로 불러들여서 초기화해주고 호출해줄 컨테이너가 있어야만 실행될

수 있다. 




3. 웹페이지에 ActiveX를 끼워넣기, <OBJECT> 태그는 정체성

Visual Studio C++에서 마법사를 부려서 ActiveX를 만드는 것은 어렵지 않다. C++에 익숙한 분이라면 

마법사를 통해 만들어진 ActiveX 코드들을 한 번 훑어 보는 것으로도 ActiveX의 기본 구조를 쉽사리 

잡을 것이다. 하지만 ActiveX는 Visual Studio C++의 마법사가 만들어 낸 코드만 가지고는 자기 역할을 

다 할 수 없다는 것을 잊지 말자.


ActiveX는 DLL 처럼 하나의 컴포넌트 또는 모듈로서 자신을 초기화하고 불러 줄 Container가 반드시 

필요하다고 했다. 여기서 소개할 그 그릇들 중 하나는 바로 인터넷 브라우저, Internet Explorer이다. 


그리고 인터넷 브라우저에서 ActiveX를 담아내기 위한 Tag는 <OBJECT> 이다. 다음 예제에서 전형적인

<OBJECT> 태그의 사용법을 보여주는데 여기서 주목할 것은 굵은 글씨로 표현된 세 개의 속성값들이다.


   <object  id="LikeBnbX" 

            classid='clsid:16진수로된-GUID' 

            codebase="http://사이트.주소/LikeBnbX.cab#version=1,1,1,127" 

            width=800px height=500px>

      <PARAM NAME="변수이름" VALUE="전달할값">

   </object>



우선 속성, id는 HTML의 DOM 객체를 자바스크립트나 CSS에서 접근하기 위해 식별자로 사용하는 

이름이다. 보통은 getElementById()라는 자바스크립트 함수의 파라미터로 쓰인다. 


classid는 인터넷 브라우저가 윈도의 레지스트리에서 해당 ActiveX를 찾아내기 위한 용도로 쓰인다.

이 값은 Visual Studio C++의 마법사가 ActiveX를 생성할 때 자동으로 할당되는 값이다. 


마지막으로 codebase는 ActiveX의 최초 배포 및 업그레이드 등을 위한 원본 모듈이 저장된 위치 및

버전을 알려주는 속성이다.


너는 누구냐? 

그렇다, 위에서 설명한 세 가지 속성값은 바로 이 질문에 대한 답이 분명하다.



4. 타이틀바가 없는 ActiveX, 윈도 스타일

ActiveX를 HTML의 구성요소 중 하나로 끼워넣을 땐 보통 윈도와는 달리 타이틀바(캡션)를 갖지 않는다. 

이를 위해 다음과 같이 메인프레임에서 그리고 컨트롤에서 윈도의 스타일(모양새)을 조금 손봐줘야 한다.


다음 코드는 마법사를 써서 SDI 기반 응용프로그램을 만들면 생기는 MainFrame.cpp 파일에 내장함수

PreCreateWindow()를 재정의한 것이다. 굵게 표시한 부분과 그 주석을 주의 깊게 읽어보도록 하자.


BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

   // 프레임을 Dialog Frame으로 만든다.

   // 작업 표시줄에 프로그램 항목이 생기지 않고, 제목표시줄도 없다.

   // 여기서 윈도우 스타일을 WS_DLGFRAME으로 바꿔줘야,

   // ActiveX 컨트를 내에 달라 붙게 된다.

   cs.style = WS_DLGFRAME;


   // ActiveX를 위해 SDI의 메뉴를 제거한다.

   if (cs.hMenu!=NULL) {

      ::DestroyMenu(cs.hMenu);      // delete menu if loaded

      cs.hMenu = NULL;              // no menu for this window

   }


   if ( !CFrameWnd::PreCreateWindow(cs) )

      return FALSE;


   return TRUE;

}



그리고 다음 코드 블럭은 ActiveX의 컨트롤에 해당하는 클래스 중 OnDraw를 재정의한 것인데 캡션을 없애는

과정에서 Windows XP와 Windows 7이 다르게 동작하는 문제가 있어서 추가한 코드이다. 역시 굵게 표시한

부분을 주의 깊게 보도록 하자.

void CLikeBnbXCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, 

                           const CRect& rcInvalid)

{

   if (m_bCreate == false) {  

      m_pMainFrame->SetParent(this);


      // Windows XP, Windows 7에서 캡션에 대한 처리가 약간 다르다.

      LONG lCurStyle = GetWindowLong(m_pMainFrame->GetSafeHwnd(), GWL_STYLE);


      // 캡션(타이틀바)을 없앤다.

      lCurStyle &= (WS_CAPTION || WS_SYSMENU || WS_THICKFRAME || WS_MINIMIZE);

      SetWindowLong( m_pMainFrame->GetSafeHwnd(), GWL_STYLE, lCurStyle);


      // 다이얼로그의 크기를 재지정한다.

      m_pMainFrame->SetWindowPos(NULL, 0, 0, rcBounds.right, rcBounds.bottom, SWP_DRAWFRAME);

      m_pMainFrame->ShowWindow(SW_SHOW );

      m_bCreate = true;

   }

}




5. ActiveX와 자바스크립트 간의 대화

ActiveX는 HTML의 <OBJECT> 태그를 통해 인터넷 브라우저에서 식별 가능한 객체로 거듭난다.

이제 <OBJECT> 태그의 id 속성에 지정한 값이 이 ActiveX의 고유한 식별자로써 자바스크립트가

이를 지목하여 사용할 수 있다. 다음에 소개된 HTML은 간단하지만 앞서 설명한 모든 내용을 다 담고 

있는 예제이다.


<html>

<head>

<script language='javascript'>

   

   // ActiveX LikeBnbX의 함수 sayHello()를 호출한다.

   function sayHello() {

      var objOcx = document.getElementById('LikeBnbX');

      var yourName = document.getElementById('txtYourName').value;

      

      objOcx.sayHello(yourName);

   }

   

</script>

</head>

<body>

<div>

   <input type='text' id='txtYourName' value=''>

   <input type=button value='SayHello' onClick="sayHello();"><br />

   <object  id="LikeBnbX" 

            classid='clsid:16진수로된-GUID' 

            codebase="http://사이트.주소/LikeBnbX.cab#version=1,1,1,127" 

            width=800px height=500px>

      <PARAM NAME="변수이름" VALUE="전달할값">

   </object>

</div>

</body>

</html>


위 예제는 웹사용자들이 웹페이지에 입력한 자신의 이름을 자바스크립트를 통해  ActiveX로 전달하고

이를 윈도 모듈인 ActiveX가 받아서 뭔가를 수행해줄 것을 기대할 수 있는 코드 블럭이다. 이제 ActiveX 쪽에

구현된 함수를 한 번 살펴보도록 하자.


void CLikeBnbXCtrl::sayHello(LPCTSTR yourName) 

{

   CString strMesg;

   strMesg.Format(_T("안녕하세요? %s님, 환영합니다."), yourName);

   AfxMessageBox(strMesg);

}



위와 같은 함수는 임의로 생성해서는 안되고 반드시 아래 그림과 같이 클래스 마법사를 통해서 만들어야 한다.

클래스 마법사를 먼저 열고, 컨트롤 클래스인 CLikeBnbXCtrl을 선택한 뒤 Automation 탭을 클릭한다.

이 후 오른쪽의 버튼들 중 Add Method를 클릭하여 그림과 같이 함수 이름과 전달받을 매개변수를 지정한다.

주의할 것은 전달받을 매개변수의 타입을 LPCTSTR로 한다는 것이다.







6. CAB 파일로 OCX 배포하기

이제 오늘 다룰 내용 중 마지막이다. 앞서 codebase라는 속성을 설명하면서 잠깐 언급한 것과 같이 ActiveX를 

웹사용자들의 개별 PC에 배포하려면 정해진 몇 가지 약속을 따라야 한다.


Visual Studio C++을 가지고 만든 ActiveX는 파일확장자로 ocx를 갖는 DLL 형태의 파일이다. 그리고 이 모듈은

기본적으로 몇 개의 DLL에 대해 종속성을 갖는다. 즉 OCX 파일 하나만으로 정상적으로 동작하지 않는다는 것이다.

웹을 통해 여러 개의 종속성을 갖는 파일들을 손쉽게 배포하기 위해 cab이라는 파일 포맷을 제공한다.


그리고 이러한 cab 파일 안에 어떤 파일들이 있고 각각의 버전과 설치될 순서 등을 기술한 파일이 바로 INF 파일이다.

INF 파일 역시 CAB 파일 안에 같이 포함되어 있어야 한다.


CAB 파일은 cabarc.exe라는 유틸리티를 써서 만드는데 다음 링크에 자세한 사용법이 설명되어 있다.

http://technet.microsoft.com/en-us/library/cc757513(v=ws.10).aspx


INF 파일의 구조에 대한 설명은 다음 MSDN의 링크를 참고하도록 하자.

http://msdn.microsoft.com/ko-kr/library/aa741215(v=vs.85).aspx



아직 필요한 모든 것을 설명하지는 않았다. 우리 속담에 "첫 술에 배부를까"라는 말이 있다. 이제 시작이니 끈기를

갖고 한걸음씩 나가 보도록 하자.