Programming/Dot.NET

IE에서 닷넷 스마트 클라이언트 개발2-인터넷 익스플로러와 연동하기1

bcheul 2008. 2. 25. 13:53

출처 블로그 .. 맨 땅에 헤딩~

IE에서 닷넷 스마트 클라이언트 개발2-인터넷 익스플로러와 연동하기1


인터넷 익스플로러와 연동하기 - 1
  저 자 : 정성태
  출판일 : 2004년 3월호

 
연+재+순+서
1회 2004.1 | 스마트 클라이언트 탐험하기
2회 2004. 2 | 인터넷 익스플로러와 연동하기
3회 | 스마트 클라이언트 배포하기

연+재+가+이+드
운영체제 | IIS가 설치된 윈도우(필자의 경우 윈도우 2003 서버)
개발도구 | 비주얼 스튜디오 닷넷 2003, 닷넷 프레임워크 v1.1.4322
기초지식 | COM, C#, ASP.NET 기초
응용분야 | 현재로서는 기업 내부의 인트라넷 환경에서 액티브X 컨트롤 대체. 닷넷 프레임워크가 일반화되면 외부 웹 사이트에서 액티브X 컨트롤 대체

=====================================================================================

스마트 클라이언트는 제작하기 쉽고 유지/보수가 간편하긴 하지만 기존 액티브X 컨트롤들은 그 제작 방법에 있어서 다양한 IE와 연동 기법을 사용해 왔다. 스마트 클라이언트도 그와 동일한 기능을 기본적으로 구현할 수 있어야만 실제 현업에서 사용할 수 있는데, 비록 닷넷 프레임워크가 COM 환경과 연동을 고려해 제작되긴 했지만, COM 자체로 동작되는 것은 아니기 때문에 기존 액티브X 컨트롤의 기능을 스마트 클라이언트로 포팅하는 것이 그다지 만만하진 않다. 이번 호에서는 바로 스마트 클라이언트가 어떻게 IE와 연동할 수 있는지를 소개 한다.


COM으로 액티브X를 개발해 본 독자라면 대개의 경우 일정한 순서로 공부를 했을 것이다. 우선 기본적인 메쏘드와 속성을 구현했을 것이고 그 다음 약간 어려운 이벤트 연결을 공부했을 것이다. 그렇게 COM에 익숙해지고 나면, 웹에서 액티브X 컨트롤 제작을 해보게 되고 결국 IE(Internet Explorer)와 연동을 익히게 된다. 마찬가지로 우리는 지난 호에서 기본적인 기능을 할 수 있는 스마트 클라이언트를 개발해 보았다. 스크립트 상에서 스마트 클라이언트의 속성, 메쏘드, 이벤트를 다룰 수 있는 방법을 소개했으며, 그에 따른 보안 관련된 설정을 알아보았다. 순서대로 이제 우리는 스마트 클라이언트로 IE와 연동하는 방법을 알아내야 한다.
닷넷 프레임워크는 일종의 ‘프로그래밍 운영환경’에 지나지 않기 때문에 IE와 연동이 그다지 매끄럽게 이뤄지지는 않는다. 아마도 그 부분은 닷넷에서의 변화만으로는 부족하고 IE 자체도 변화돼야 가능할 텐데, 그 부분에 대해서는 차기 IE 버전에서나 기대해 볼 수밖에 없다. 어쨌든 우리는 현재의 IE 환경에서 구현을 해야만 한다. 그러다 보니 이번 연재에서도 역시 COM에 대한 지식을 기본 전제로 하게 되나, 그렇다고 해서 코드가 비주얼 C++로 된 것은 아니니 닷넷 언어만 아는 독자라도 unmanaged 환경과 interop을 어떻게 할 수 있는 지에 대한 많은 지식을 얻게 될 것이다.
아직 공식적으로 마이크로소프트(이하 MS)에 의해 제공되는 IE와 연동에 대한 예제 코드는 나와 있지 않은 상태이기 때문에 여기서 보여준 코드가 ‘표준’적인 접근 방식이라고 말할 수는 없다. 단지 필자가 시행착오로 알아낸 것들이기 때문에 이외에도 얼마든지 다양한 방법으로 구현 가능할 수도 있다는 것이다. 각자의 개발 경험만큼 여전히 다른 식으로 ‘IE와 연동’을 이끌어 낼 수 있으며 오히려 더 좋은 방법들이 여러분 머리속에 있을 지도 모르겠다. 이 기사를 보면서 그 가능성을 제한하지 말고 자신이 생각하는 IE와 연동 기법을 구현해 보기 바라며 또한 그 방법을 널리 알려주기 바란다. 지난 호 기사를 읽지 않은 독자는 반드시 읽어보고 이번 호 기사를 따라해 볼 것을 권한다. 왜냐하면, 이번 호에서의 실습은 지난 호에서 했던 설정사항을 그대로 이어받아 진행되기 때문이다. 만약 그러한 고려를 하지 않은 사용자라면 이번 호에서 개발하는 스마트 클라이언트의 동작이 안 될 수도 있음을 감안하기 바란다.

스크립트와 연동
액티브X를 제작하면서 HTML에 포함된 스크립트 함수를 호출하는 기능을 만들어 본 적이 있을 것이다. 이것을 스마트 클라이언트로 구현해 보자. COM을 잘 아는 독자라면 그다지 어렵지 않은 작업이겠지만, 그렇지 않은 독자들은 감이 잘 잡히지 않을 것이다. 예제를 위한 스마트 클라이언트는 지난 호에 작업했던 TreeControl을 확장해 나가는 방식으로 하겠다.
스마트 클라이언트에서 HTML 내의 스크립트 함수를 호출하는 것은 어떤 의미에서 ‘이벤트’를 발생시키는 것과 동일한 기능을 한다. 사실 이벤트를 구현하기 위해서는 이벤트 소스를 구현해야 하는 등의 다소 복잡한 부가 코드가 필요하지만 스크립트 함수를 직접 호출하는 것은 그보다 좀더 간단한 코드로 구현할 수 있다. 실제로 필자의 경우에는 이벤트를 구현하는 것보다 스크립트 함수를 직접 호출하는 것을 선호한다. 우선 코딩에 앞서 이론적인 사항부터 살펴보자.
IE는 HTML 내부의 태그뿐만 아니라 JScript 함수까지도 IDispatch 인터페이스로 다룰 수 있게 하고 있다. 이해를 돕기 위해 다음의 JScript 함수를 보자. 여기서 test_func 함수 자체가 IDispatch 인터페이스인 것이다.

function test_func()
{
alert( ‘일반 함수입니다.’ );
}

좀더 정확히 살펴보면 test_func 함수는 IDispatchEx 인터페이스를 구현하고 있다. IDispatchEx에 대해서 다소 낯설어 할 독자들이 있을 텐데, 설령 그럴지라도 대개의 경우 모르는 상태로 일반적으로 사용한 것이 바로 IDispatchEx 인터페이스이다. 이것은 기존 IDispatch 구현을 포함하면서 멤버에 대한 동적 추가/삭제를 지원하고 있다. IDispatch의 동작 원리 자체가 IDispatchEx로 기능 확장을 위한 배려가 되어 있다고도 볼 수 있다.
예를 들어 IDispatch를 구현한 COM 개체의 ‘TestFunc’ 함수를 호출한다고 가정하자. 그러면 스크립트에서 TestFunc을 obj.TestFunc()이라고 호출하게 되고, 이는 액티브 스크립팅 엔진 내부에서 obj가 구현한 IDispatch 개체의 GetIDsOfNames 메쏘드를 호출해 ‘TestFunc’ 이름에 해당하는 DISPID를 반환받게 되고, DISPID를 인자로 해서 IDispatch::Invoke 메쏘드를 호출해 주는 것이다. 즉 IDispatch::GetIDsOfNames에 의한 이름과 DISPID 쌍으로 메쏘드 호출이 이뤄지므로 멤버를 추가하기 위해 단지 맵핑 테이블 구조에 다른 이름과 DISPID를 추가해 주고, DISPID와 호출될 메쏘드 함수의 포인터를 관리하기만 하면 되는 것이다. 여러분이 이러한 IDispatchEx를 언제 사용했는지를 잠깐 살펴보자.



<input type=button id=”_btn_Test” value=”버튼을 누르세요”>


앞의 코드를 보면 원래의 <INPUT /> 요소에는 newField나 newFunc 멤버는 존재하지 않는다. 하지만 INPUT 요소가 IDispatchEx를 구현하고 있기 때문에 앞과 같이 멤버 변수와 멤버 메쏘드를 실행시에 동적으로 추가하게 된 것이다. 앞의 코드는 COM 개체에 대해 전혀 모르는 웹 프로그래머들도 자주 사용해 보았을 것이다. 그렇게 우린 이미 암암리에 IDispatchEx와 충분히 친해져 있었던 것이다. HTML 요소에서는 앞과 같은 코드를 많이 사용해 봤을 텐데, 정작 스크립트 함수까지도 IDispatchEx라는 것을 아는 개발자들은 많지 않다. 원리를 알았으니 스크립트 함수도 IDispatchEx를 구현했다는 것을 직접 예를 들어 증명해 보자.

function window.onload()
{
testFunc.newField = “새로운 멤버 변수 추가”;
testFunc.newFunc = newAddedFunc;
testFunc();
}

function testFunc()
{
alert( testFunc.newField );
testFunc.newFunc( “새로운 멤버 함수 추가” );
}

function newAddedFunc( outputText )
{
alert( outputText );
}

앞의 코드에서 설명한 IDispatchEx를 이해하고 스크립트 함수가 그 IDispatchEx로 구현되었다는 것을 안다면 지금의 예제가 그리 낯설지 않을 것이다. 보는 바와 같이 스크립트 함수인 testFunc 역시 HTML 요소와 똑같이 동적인 멤버 추가가 가능하다는 것을 알 수 있다.
이야기의 주제를 다시 스마트 클라이언트로 돌려 보자. 결국 스마트 클라이언트에서 스크립트 함수를 호출하는 것은 IDispatch 인터페이스 포인터의 Invoke를 호출하는 것과 다를 것이 없다. 예를 위해 이전에 제작해 두었던 TreeControl에 스크립트 함수를 호출할 수 있는 코드를 추가해 보자. 우선 호출할 스크립트 함수의 IDispatch 인터페이스를 보관할 속성이 필요하다. TreeEvent.cs 파일의 ITreeControlCOMIncoming 인터페이스에 다음과 같은 속성을 추가한다.

object _NodeClicked { set; get; }

TreeControls.cs 파일에서는 앞의 인터페이스에 구현된 프로퍼티를 구현해 줘야 한다.

object _NodeClicked = null;
public object _NodeClicked // 공용 프로퍼티로 정의
{
 get { return _nodeClicked; }
 set { _nodeClicked = value;
}

// 기존에 정의된 TreeView::AfterSelect 이벤트 핸들러를 다음과 같이 수정
private void _treeView_AfterSelect( ... )
{
 if ( _nodeClicked != null )
 {
  // IDispatch::Invoke 메쏘드를 호출하는 것과 동일한 호출 역할
  Type type = _nodeClicked.GetType();
  type.InvokeMember( “”, BindingFlags.InvokeMethod, null, _nodeClicked, null );
 }
}

IDispatch::Invoke 메쏘드를 직접 호출해 주던 것과 달리 Type 클래스의 InvokeMember 메쏘드를 호출한 것만 제외한다면 비주얼 C++ 코드와 비교해 패턴 자체는 크게 변한 것이 없다. 스마트 클라이언트 측에서는 내부적으로 앞과 같이 구현해 주면 된다. 이어서 다음의 코드에서 보는 것처럼 HTML 스크립트에서 호출될 스크립트 함수를 대입해 주고, 해당 스크립트 함수 안에 코드를 구현해 주면 된다.

Form1._Control1._NodeClicked = treeControl_nodeClicked;
function treeControl_nodeClicked()
{
// 트리 컨트롤에 선택된 노드를 가져와 처리
}

이제 컴파일하고 웹 브라우저에서 테스트를 해보자. 트리 컨트롤에서 노드를 클릭하면 앞에서 구현한 treeControl_nodeClicked 스크립트 함수가 실행되는 것을 확인할 수 있다. 결국 이전에 말했던 것처럼 결과적으로는 이벤트 함수와 역할이 동일할 뿐만 아니라, 지난 호의 이벤트 구현을 위해 소요되었던 코드와 비교해 볼 때 구현도 간단해졌다(여전히 보안 설정은 ‘Unmanaged 코드를 호출 가능’하도록 설정해 둬야 한다).
참고로 필자가 겪은 재미있는 오류를 하나 설명하고자 한다. HTML에 구현된 스크립트 함수를 CLR(Common Language Runtime)에서는 InvokeMember로 호출하게 되는데, 만약 그 스크립트 함수 내부에서 오류가 발생하면 흔히 본 ‘스크립트 오류 메시지’ 창이 뜨지 않고 ‘닷넷 프레임워크의 예외 메시지’ 창이 뜨게 된다. 예외 내용은 System.Reflection.TargetInvocationException이니 혹시나 이 예외가 발생했다고 해서 CLR 코드를 의심하지 말고 스크립트 함수를 살펴보기 바란다.