제목을 보고 링크를 클릭하여 여기까지 온 거라면 독자는 지대로 낚인 것이 되겠다. 게으름으로 인해 최근 포스트가 전무한 탓에 점점 방문자 수와 페이지 뷰가 줄어들자 위기감을 느낀 필자. 페이지 뷰 좀 올려 보려고 그래도 필자의 블로그에서 가장 인기 있는 스마트 클라이언트란 제목으로 낚시질을 좀 해본 것이다. 흐흐흐... 하지만... 아직 실망하긴 이르다. 끝까지 읽어는 보고 낚시에 당했는지 아닌지 판단해 보기 바라는 바이다. (간만에 글이다 보니 글이 약간 까칠하더라도 이해해 주기 바란다. 뭐... 싫음... 딴 데 가서 게시판 놀이나 하든가... 아싸 막가파...)
스마트 클라이언트 란 것이 사실 원리를 알면 별 것이 아니다. 기술적인 이슈와 노하우 없이 존나게 고생할 수는 있어도 그 원리는 조낸 심플하다는 것이다. 왜냐면 기존에도 사용했던 기술을 닷넷화(化) 해 놓은 것이기 때문이다. 스마트 클라이언트의 원리에 대해 또 설명하자면 입만, 아니 팔만 아픈 관계로 살짝 링크만 떤져 본다. (날로만 먹으려고 하지 말고 어떻게 링크도 클릭도 해보고... 글도 좀 읽어 보는 노력도 쪼금만 해보고 그러지 그러셔?)
회나 낙지(아우 낙지 먹구 싶다...) 같이 날로 먹는 거 좋아하는 독자들을 위해 간단히 정리하자면 스마트 클라이언트 중에서 브라우저 임베디드 스마트 클라이언트란 것은 IE 브라우저에겐 ActiveX와 전혀 다를 것이 없는 COM 객체일 뿐이라는 것이 오늘의 핵심 뽀인뜨가 되겠다. 그게 닷넷으로 작성되었는지 UserControl 에서 파생되었는지 IE 브라우저는 전혀 관심이 없을 뿐 더러 그것이 MIME 필터에 의해 활성화 되었는지 표준 COM 방식으로 활성화 되었는지 역시 큰 관심사가 아니다. 다만 OBJECT 태그에 의해 나타나는 COM 객체가 IE가 필요로 하는 몇몇 COM 인터페이스를 구현하고 있는지가 중요한 것이다.
브라우저 임베디드 스마트 클라이언트(이하 스마트 클라이언트는 모두 브라우저 임베디드 스마트 클라이언트를 지칭함)를 구현할 때 UserControl 클래스에서 파생된 사용자 정의 컨트롤을 만들곤 했었음을 기억해 보자. 왜 UserControl 클래스에서 파생된 사용자 정의 컨트롤이어야 했을까?
허탈하지만 이유는 없다. UI(user interface)를 갖는 스마트 클라이언트를 만들고자 한다면, ActiveX 로서 요구되는 COM 인터페이스들을 구현하는 닷넷 클래스를 사용한다면 어떤 클래스를 사용해도 상관이 없다는 말이 되겠다. 이 시점에서 궁금해 지는 것은 ActiveX의 요건을 갖추고 있는 닷넷의 클래스는 무엇인가가 궁금할 지도 모르겠다. 그 클래스는 다름 아닌 System.Windows.Forms 네임스페이스의 Control 클래스가 되겠다. 이 클래스는 UI 를 갖는 ActiveX 컨트롤이 구현해야 할 모든(?) COM 인터페이스를 모두 제공하고 있다. UserControl 클래스는 다른 컨트롤들(라벨, 텍스트박스, 트리뷰, 리스트뷰 등)과 마찬가지로 직/간접적으로 Control 클래스에서 파생되었고 또한 사용자의 디자인이 가능한 컨트롤이기 때문에 스마트 클라이언트를 작성할 때 UserControl 클래스에서 파생된 사용자 정의 컨트롤을 사용했던 것일 뿐이다.
이 글의 제목을 생각한다면 이쯤에서 UI 가 없는 ActiveX, UI 가 없는 스마트 클라이언트에 대해 언급해줘야 한다. 안 그러면 독자들이 낚였다고 생각할 테니 말이다...
1990년대 말 초기의 ActiveX의 정의는 브라우저에 포함될 수 있는 UI를 갖는 컨트롤을 지칭했었다. 그래서 일반적으로 ActiveX 컨트롤이라고 함은 OLE 컨트롤 혹은 OCX 컨트롤의 서브셋(subset)으로써 UI를 갖는 COM 컴포넌트를 지칭하는 것 이였다. UI를 갖는 COM 컴포넌트가 구현해야 할 COM 인터페이스는 상당히 많기 때문에 VB6.0, Delphi, MFC, ATL 등의 도움을 받지 않고선 맨땅에 헤딩하기란 상당히 어려운 개발 항목 중 하나로 꼽힌다.
하지만 점차로 브라우저에 삽입될 수 있는 COM 컴포넌트들의 범위가 넓어짐에 따라 OBJECT 태그를 통해 브라우저에 삽입될 수 있는 ActiveX 컨트롤의 허용 범위가 넓어진다. 즉, UI 가 없는 ActiveX 컨트롤의 경우에는 달랑 IUnknown, IDispatch 두 인터페이스만을 구현해도 되기 때문이다. 이 두 인터페이스는 COM에서 사용하는 가장 근본적인 인터페이스이다. 더 이상 알려고 하면 인생이 피곤해 질 터이니 이 정도만 해 두자. 어찌 되었건 결론적으로 브라우저에 OBJECT 태그로 삽입할 수 있는 ActiveX 객체는 단순한 COM 객체도 가능하다는 얘기가 되겠다.
그렇다면... 스마트 클라이언트도 UserControl 에서 파생되지 않는 일반 닷넷 클래스를 사용할 수는 없을까? 그냥 UI 가 없는 일반 클래스를 스마트 클라이언트 OBJECT 태그에 표시하면 어떨까?
오늘 간만에 포스트를 올리는 이유가 드디어 나왔다. 스마트 클라이언트가 반드시 UI를 가지란 법은 없는 것이다. UI가 없는 스마트 클라이언트도 가능한 것이며, 그 스마트 클라이언트는 UserControl에서 파생할 필요 없이 단순한 COM 객체로서 만들어도 되는 것이다.
그렇담 UI가 없는 스마트 클라이언트에서 어떤 의미를 찾을 수 있을까? 의미란 찾으려고 할 때만 찾아지는 것이 아니다. 의미를 부여할 수도 있지 않은가? 지나가는 소(牛)를 돼지(豚)가 쳐다볼 때 아무런 의미를 찾을 수 없을 수도 있지만, 그 돼지가 소에게 의미를 부여하면 사랑스런 소가 될 수도 있는 것이다.
가까운 예로 인터넷 뱅킹에서 사용하는 암호화 ActiveX 컨트롤은 UI를 가지고 있지 않다. 또한 UI는 HTML에게 맡겨두고 파일을 선택하고 업로드만 수행해 주는 스마트 클라이언트를 생각해 볼 수도 있다. 이름을 불러줄 때나 의미를 갖게 된다는... UI가 없는 스마트 클라이언트의 의미는 독자들이 부여해 보기 바란다. (아... 글이 어울리지 않게 철학적으로 나가네... 나이 쳐먹고 있다는 증거인가? -_-; )
그럼 UI를 갖지 않는 ActiveX와 동등한 스마트 클라이언트는 우찌 만들면 될까? 2000년 처음 등장한 닷넷 프레임워크의 내부 이름이 COM+ 2.0 이였다는 사실을 아는 독자들과 일정 내공을 가지고 Unmanaged 세상과 섞여 사는 독자라면 이미 잘 알고 있을 터...
닷넷의 대부분의 클래스는 COM 객체로서 Unmanaged 세상과 상호 운용될 수 있다. 필요한 조건은 COM 을 통해 Unmanaged 동네에서 액세스를 허용하도록 '표시'하는 ComVisibleAttribute 특성과 public 액세스 한정자만 있다면 그 클래스의 인스턴스는 COM 객체로서 취급 받을 수 있다. 이러한 기능은 CLR(Common Language Runtime)이 제공하는 Interop 기능에 의해 제공되며, 보다 구체적으로는 이런 저런 문서에서 많이 들어봤을 법한 CCW(COM Callable Wrapper)를 통해 가능한 것이 되겠다. CCW 에 대해서는 필자보다 친절하고 공손하게 정보를 제공하는 MSDN이나 다른 사이트, 블로그, 동영상 등등의 자료를 찾아 보기 바란다.
그럼 코드를 살짝 살펴보자. 예제는 Javascript로는 구현이 어려운(?) 암호화를 수행하는 컨트롤이다.
[System.Runtime.InteropServices.ComVisible(true)]
public class CryptoHelper
{
public string EncryptString(string source)
{
// Symmetric 암호화 알고리즘 초기화
RijndaelManaged crypto = new RijndaelManaged();
ICryptoTransform transform = crypto.CreateEncryptor(GetKey(), GetIV());
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write);
// 문자열을 바이트 배열로 바꾼다. 인코딩은 Unicode를 사용했다.
byte[] data = Encoding.Unicode.GetBytes(source);
// 암호화를 수행한다.
cryptoStream.Write(data, 0, data.Length);
cryptoStream.Close();
// 암호화된 데이터를 Base64 인코딩 하여 반환한다.
byte[] encryptedData = memoryStream.ToArray();
string cipherText = Convert.ToBase64String(encryptedData);
return cipherText;
}
public string DecryptString(string cipherText)
{
// Rijndel 알고리즘을 사용하여 복호화 하는 로직... (대세와 무관하므로 생략... ^^)
}
// 이하 GetKey(), GetIV() 메쏘드 생략...
}
리스트1. UI가 없는 스마트 클라이언트 예제
UI가 없는 스마트 클라이언트 예제 코드인 리스트1 에서 가장 핵심적인 것은 ComVisibleAttribute 특성을 true로 설정한 것과 CrypoHelper 클래스가 public 한정자를 갖는다는 점이다. ComVisible 특성이 true 이어야만 COM Interop을 통해 웹 브라우저가 이 클래스를 볼 수 있기 때문이다. 그리고, public 클래스이어야만 어셈블리 외부에서 접근이 가능하다는 점도 잊지 말자.
그렇다면 브라우저에서 이 클래스를 어떻게 사용할 수 있을까? 뭐... 닭이 아니라면 public 메쏘드나 public 속성을 스크립트에서 액세스할 수 있다는 것은 찍어도 맞출 수 있을 것이다. 리스트1에서는 문자열을 암호화 하고 복호화 하는 EncryptString 메쏘드와 DecryptString 메쏘드를 public 메쏘드로 제공하고 있으므로 스크립트는 public 으로 선언된 EncryptString 메쏘드와 DecryptString 메쏘드를 호출할 수 있다는 것이다.
자, 이제 CryptoHelper 클래스를 사용하는 HTML 코드를 훔쳐봐 보자. 먼저 OBJECT 태그는 다음과 같다.
<object id="CryptoHelper" classid="UIlessControl.DLL#UIlessControl.CryptoHelper" style="display:none" />
이건 뭐 특별할 것도 없고 너무나 간단하다. 일반적인 다른 스마트 클라이언트와 다를 것이 전혀 없는 것이며 다운로드, configuration, CAS 등이 모두 UserControl 클래스에서 파생된 UI를 갖는 스마트 클라이언트와 완전히 동일하다.
이제 선언된 스마트 클라이언트를 사용하는 스크립트를 살펴보자.
// 암호화 수행
function DoEncrypt()
{
var ctrl = document.all["CryptoHelper"];
var textElement = document.all["SourceText"];
var text = textElement.value;
var resultSpan = document.all["EncryptResult"];
var result;
if (text == null || text.length == 0) {
alert("암호화할 문자열을 입력하세요.");
textElement.focus();
return;
}
result = ctrl.EncryptString(text);
resultSpan.innerHTML = result;
}
리스트2. 스마트 클라이언트를 '사용'하는 자바 스크립트
리스트2는 입력 상자에 사용자가 입력한 문자열을 CryptoHelper 스마트 클라이언트(리스트1)가 제공하는 EncryptString 메쏘드를 사용하여 암호화하고 그 결과를 미리 정의된 SPAN 영역에 보여주는 코드이다. 뭐 복잡할 것도 없고 설명할 것도 없이 간단 그 자체라고 할 수 있다. 리스트1과 리스트2를 사용하여 작성한 예제는 다음 화면과 같다.

리스트1의 CryptoHelper 클래스는 public 메쏘드만을 제공하고 있지만 public 속성을 제공하여 속성을 액세스할 수 도 있다. 하지만 UserControl에서 파생된 클래스처럼 PARAM 태그를 사용하여 속성의 초기값을 줄 수는 없다. PARAM 태그는 IPersistPropertyBag COM 인터페이스를 구현하는 COM 객체에 대해서만 작동하기 때문에, 이를 구현하지 않는 CryptoHelper 클래스는 적용이 되지 않는 것이다. 반면 UserControl 클래스의 조상 클래스인 Control 클래스는 이 인터페이스를 구현하고 있기 때문에 PARAM 태그를 적용할 수 있다.
또 한가지 언급해야 할 것은 UserControl 에서 파생되지 않은 클래스를 스마트 클라이언트로 사용할 때에 이벤트를 사용할 수 없다는 점이다. 즉, UserControl 클래스에서 파생된 스마트 클라이언트는 이벤트를 정의하고 스크립트가 이벤트 핸들러를 작성할 수 있지만 CryptoHelper 클래스처럼 UserControl 에서 파생되지 않은 클래스는 스크립트가 이벤트를 받을 수 없다. 그 이유는 필자도 명확하지 않다. 기본적으로 닷넷의 객체가 CCW를 통해 COM 클라이언트로부터 액세스 될 때 COM 이벤트를 사용할 수 있음에도 불구하고 UserControl에서 파생되지 않은 클래스를 스마트 클라이언트로 사용할 때는 이벤트가 작동하지 않았다. Windows Script Host 나 VB 6.0 과 같은 COM 컨테이너는 이벤트를 받을 수 있었지만 브라우저는 그렇지 않았다는 얘기이다. 이는 아마도 브라우저가 UI를 갖는 경우에만 이벤트 핸들러를 초기화하는 것으로 보인다.
꼭 리스트1의 CryptoHelper 클래스처럼 해야 할 필요가 있을까? CryptoHelper 클래스를 UserControl 클래스에서 파생시키고 스타일 시트를 이용하여 보이지 않게 해도 되지 않은가?
그렇다. 그렇게 해도 된다. 결과는 같다. 하지만 과정은 조금 다르다. UserControl 클래스에서 파생되기 위해서는 한 등빨 하는 System.Windows.Forms 어셈블리를 로드해야 할 뿐 만 아니라 UI를 갖는 ActiveX 초기화에 필요한 닷넷 코드가 수행되어야 한다. 반면 UserControl 클래스에서 파생되지 않은 CyrptoHelper 클래스는 추가적으로 로드 할 어셈블리가 상대적으로 적기 때문에 보다 빠르게 초기화를 수행할 것이다. 사실 CryptoHelper 클래스는 mscorlib 어셈블리와 System 어셈블리만이 사용된다. 목적을 이루는 방법은 다양하다. 파리를 잡는데 파리채를 쓸 것인가 아니면 105mm 박격포로 조져버릴 것인가는 여러분의 선택이다.
또 한가지 언급하고 싶은 것이라면 UI 가 없는 스마트 클라이언트도 CAS(Code Access Security)의 제약을 받는다는 점이다. 사실 모든 닷넷 코드는 CAS의 제약을 받는다. 때문에 기본 인터넷 영역의 CAS 설정이 갖는 한계를 잘 고려해야 할 것이다. 기본 CAS 설정으로도 위와 같이 자바 스크립트와 이를 기반으로 하는 AJAX 로는 수행할 수 없는 암호화 작업이나 제약적이지만 로컬 파일 액세스(OpenFileDialog나 IsolatedStorage를 이용하여)도 가능하다. 또한 스크립트가 수행할 수 있는 일 이라 할지라도 닷넷 코드가 훨씬 더 빠르게 작업을 수행할 수 있다는 점도 잊지 말자. 예를 들어, 다량의 문자열을 파싱 하거나 가공해야 한다면 닷넷 코드가 더 빠른 결과를 낼 것임은 너무나 자명하다.
만약 클라이언트의 CAS 설정을 FullTrust로 설정하는 것이 가능하다면 로컬 파일 액세스, 레지스트리 수정, 파일 업로드 등의 다양한 작업을 수행할 수 있게 된다(Windows Vista의 보호모드라면 좀 곤란하겠다). CAS 설정을 배포해야 한다는 복잡한 과정이 떡하고 버티고 있지만 이미 스마트 클라이언트를 위해 클라이언트 PC 환경이 갖추어진 기업 같은 곳에서는 일반 ASP.NET, JSP, ASP 웹 어플리케이션에서도 UI 없는 스마트 클라이언트를 이용하여 다양한 작업을 수행할 수 있을 것이다. ActiveX 보다야 작성하기 쉽고 배포하기 편리한 것이 스마트 클라이언트 아니더냐?
UI를 갖지 않는 스마트 클라이언트... 이 녀석은 보는 사람의 시각에 따라 아무것도 아닌 것일 수도 있지만 어떤 사람에게는 훌륭한 기능을 제공하는 녀석이 될 수도 있다. 의미를 찾으려는 것도 중요하지만 사람에 따라서 그 '의미'라는 것이 다르게 다가올 수 있다는 점을 생각해 보자.
자... 이제 독자들이 대답해 볼 차례이다. 필자의 "낚시"에 걸려든 느낌인가?