SimpleIsBest.NET

유경상의 닷넷 블로그
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.

프로그램을 작성하다 보면 다양한 요구 사항을 접하게 됩니다. IT 관점에서 보면 별 황당무계한 요구사항도 있기 마련이고, 프로그래밍 관점에서 말도 안 되는 요구사항도 있기 마련이죠. 황당무계할 정도의 요구사항은 아니지만 브라우저 임베디드 스마트 클라이언트 내에 브라우저가 나타나야 하는 상황이 발생할 수도 있습니다.

이번 포스트에서는 브라우저 임베디드 스마트 클라이언트 내에서 브라우저 컨트롤을 사용해야 할 때 발생할 수 있는 문제와 해결 방법을 팁으로 남기려고 합니다.

Using WebBrowser in SmartClient

세상엔 엿 같은 일이 많다. SI (System Integration)에 종사하는 개발자라면 누구나 고개를 끄덕일 만한 일 중 하나는 요구 사항 정의와 업무 협의 단계에서 당황스럽게 결정된 요구 사항이 개발자에게 떨어지는 경우이다. 어플리케이션 플랫폼에서 구현하기 매우 어렵거나 거의 불가능한 기능을 무뇌충 같은 인간이 덜컥 "다~ 됩니다" 라고 얘기하는 바람에 개발자만 죽어나는 전형적인 대한민국의 SI 시나리오...

무식한 요구 사항 분석 및 무리한 프로젝트 일정이 왜 죄다 개발자의 몫으로 떨어지는가? 매일 야근은 물론이요 주말까지 반납해야 하니, 업계에서 쓸만한 개발자 찾기가 어려워지는 것은 너무나도 당연한 일 아닌가?

최근 프로젝트에서 무쟈게 열 받는 필자가 쓸데없이 한 소리 해봤다. 헛소리 집어 치우고... 브라우저 임베디드 스마트 클라이언트에서 브라우저 컨트롤을 써야 하는 요구 사항이 있다고 가정해 보자. 이 때는 어떻게 코딩을 해야 할까?

Problem Space

닷넷 프레임워크 2.0의 윈폼(WinForm; System.Windows.Form 네임스페이스)에는 새로운 컨트롤들이 많이 추가되었다. 그 중 하나가 멋진 웹 브라우저 컨트롤(WebBrowser 클래스)이다. WebBrowser 컨트롤은 IE의 브라우저 ActiveX 컨트롤(Shdocvw.dll)을 감싸는(wrapping) 컨트롤로서 사용하기 쉬울 뿐 더러 다양한 기능들이 추가되어 있다. 브라우저 ActiveX를 직접 사용할 때에 비해 몇몇 사용 불가능한 기능들이 있지만 이들 기능은 많이 사용되는 것이 아니기 때문에 큰 문제가 되지 않는다. 스마트 클라이언트를 작성할 때 UI 화면이 브라우저 컨트롤을 요구하는 경우, 이 멋진 브라우저 컨트롤을 사용하면 매우 편리할 것이다.

그런데... 스마트 클라이언트가 브라우저에 임베드 되어 있는 경우라면 이야기가 달라진다. WebBrowser 클래스의 생성자를 리플렉터로 까보면 CheckIfCreatedInIE() 라는 private 메쏘드를 호출하도록 되어 있고 이 메쏘드는 WebBrowser 컨트롤이 IE 내부에서 생성되는 경우 예외를 발생하도록 되어 있다 (리스트1 참조).

private void CheckIfCreatedInIE()
{
      if (WebBrowser.createdInIE)
      {
            if (this.ParentInternal != null)
            {
                  this.ParentInternal.Controls.Remove(this);
                  base.Dispose();
            }
            else
            {
                  base.Dispose();
                  throw new NotSupportedException(SR.GetString("WebBrowserInIENotSupported"));
            }
      }
}

리스트1. WebBrowser 클래스의 IE 확인 코드

리스트1의 코드를 다 이해하려고 하지 말자. 이 코드는 WebBrowser 컨트롤이 IE 내에서 생성될 때 예외를 발생시키는 코드로서만 이해하고 있으면 된다. 결론적으로 브라우저 임베디드 스마트 클라이언트 안에서 WebBrowser 컨트롤을 사용할 수 없음을 알 수 있을 것이다.

Solution

WebBrowser 컨트롤이 IE 상에서 사용할 수 없다고 포기할 것인가? 그럴 수는 없지 않은가?

닷넷 프레임워크 2.0이 나오기 전에 브라우저 컨트롤을 사용할 때 어떻게 했나 생각해 보자. 직접 ActiveX (Shdocvw.dll)를 '참조' 하지 않았는가? 그렇다. 2.0에서 제공하는 WebBrowser 컨트롤을 사용할 수 없다면 직접 브라우저 ActiveX 컨트롤을 사용하면 된다. 여기서 브라우저 ActiveX 컨트롤을 폼 혹은 사용자 정의 컨트롤(UserControl)에 올려 놓는 방법을 설명하진 않겠다.

결론은 브라우저 임베디드 스마트 클라이언트 내에서 닷넷 프레임워크 2.0에서 기본으로 제공하는 브라우저 컨트롤은 사용할 수 없고 대신 ActiveX 브라우저 컨트롤을 사용해야 한다는 것이다.


화면1. 스마트 클라이언트에서 ActiveX 브라우저 컨트롤(shdocvw.dll)을 사용한 경우

Consideration

ActiveX 브라우저 컨트롤을 스마트 클라이언트에서 사용할 때는 몇 가지 주의할 사항이 있다. 첫째로 ActiveX를 닷넷에서 사용할 때 항상 등장하는 Interop 어셈블리 역시 배포의 대상이기 때문에 항상 이 어셈블리가 다운로드 될 수 있도록 신경 써야 한다는 것이다. 뭐 이정도야 스마트 클라이언트 가지고 여러 닭짓을 해 본 독자라면 "그 정도 쯤이야" 라고 할 것이고...

두 번째 조심해야 할 사항은 ActiveX 브라우저 컨트롤이 포함된 UserControl의 Load 이벤트 발생 시점이 좀 꼬일 수 있다는 것이다. 정상적인 경우라면 UserControl 혹은 Form의 생성자가 수행된 후에 Load 이벤트가 발생한다. Load 이벤트는 폼 혹은 UserControl 자신과 그 자식 컨트롤들의 윈도우가 생성된 직후 이며 이들이 화면 상에 나타나기 직전에 발생되는 이벤트이다. 쪼끔 더 어렵게 이야기 하자면 WM_CREATE 윈도우 메시지와 더불어 발생되는 이벤트가 Load 이벤트이다. -_-; (WM_CREATE 메시지에 대해서는 여기서 설명할 수 없다. 모르는 독자는 그냥 넘어가는 것이 정신 건강에 좋을 것이다)

ActiveX 브라우저 컨트롤이 사용되면 이 Load 이벤트가 생성자 수행 도중에 발생해 버리는 현상이 나타난다. 그 이유는 ActiveX 컨트롤이 UserControl 혹은 폼에 추가 되기 위해서는 반드시 윈도우의 핸들(Handle 속성)을 필요로 하는데, 이 핸들은 윈도우가 생성되어야만 접근이 가능하기 때문이다. 따라서 생성자에서 ActiveX를 생성하여 추가하기 위해선 부모, 즉 UserControl 혹은 폼의 핸들을 알아야 하고 핸들을 알기 위해서는 윈도우를 생성해야 한다. 윈도우가 생성됨에 따라 WM_CREATE 메시지가 발생할 것이고 그 결과로 Load 이벤트가 발생해 버린다는 아픔이 있게 되는 것이다.

잘 이해가 안 간다던가 복잡하게 여겨 진다면, 이것만 외우면(-_-) 된다. 스마트 클라이언트에서 사용하는 UserControl 내에 ActiveX 브라우저 컨트롤을 사용하면 Load 이벤트가 생성자 수행 도중에 발생한다는 것만 외워두자. 못 믿겠다면 직접 테스트 해봐도 좋다.

Load 이벤트가 생성자 수행 도중에 발생하는 것이 어떤 문제가 있을까? Load 이벤트에서 별다른 작업을 하지 않는다면 전혀 문제될 것이 없을 수도 있지만, Load 이벤트에서 다양한 작업들(인증 처리, 권한 처리, 데이터 액세스, 웹 서비스 호출 등)을 수행하는 경우에는 문제를 유발할 수도 있다. 대개 브라우저 임베디드 스마트 클라이언트는 <PARAM> 태그나 자바 스크립트 호출을 통해 HTML과 통신하고 HTML 로부터 인증 정보나 권한 정보를 속성으로 넘겨 받게 되는데, 이러한 과정이 생성자가 완료된 후에 발생한다. 만약 인증 처리를 Load 이벤트에서 수행한다면 ActiveX 브라우저 컨트롤로 인해 생성자가 완료되지 않은 시점에서 Load 이벤트가 발생할 것이고 인증에 필요한 정보는 아직 HTML로 부터 전달 받지 않았을 것이므로 당연히 정상적인 인증 처리가 되지 않을 것임은 너무나 자명한 것이다. 필자 역시 작년 스마트 클라이언트 프로젝트에서 이러한 현상으로 상당히 애를 먹었다.

필자가 이러한 현상을 피해 가기(workaround) 위해 사용한 방법은 비주얼 스튜디오의 디자이너를 통해 ActiveX 브라우저 컨트롤을 디자인 하지 않고 Load 이벤트 내에서 코드를 이용하여 브라우저 컨트롤을 생성하는 것이었다. 비주얼 스튜디오 디자이너를 통해 디자인을 수행하면 디자인 내용은 모두 InitializeComponents 라는 메쏘드 내에 코드로서 삽입되고 이 메쏘드는 생성자에서 호출되므로, 앞서 언급한 Load 이벤트의 시점이 꼬이게 되는 것이다. 따라서 디자이너를 사용하여 ActiveX 브라우저 컨트롤을 폼/UserControl 에 올려놓지 않고 리스트2과 같이 Load 이벤트 내에 코드를 통해 생성하는 것이다.

    1 // 로드 이벤트 핸들러

    2 private void ActiveXBrowserUI_Load(object sender, EventArgs e)

    3 {

    4     wbControl = new AxWebBrowser();

    5     wbControl.Dock = DockStyle.Fill;

    6     wbControl.NavigateComplete2 += new DWebBrowserEvents2_NavigateComplete2EventHandler(wbControl_NavigateComplete2);

    7     this.pnlBrowser.Controls.Add(wbControl);

    8 }

리스트2. ActiveX 브라우저 컨트롤을 코드에 의해 생성하는 예제

컨트롤의 생성은 단순히 컨트롤 클래스의 인스턴스를 new를 통해 생성하는 것이고, 비주얼 스튜디오에서 디자인 한다는 얘기는 곧 컨트롤들의 속성을 지정해 주는 것이다. 이것을 모두 코드로 표현하면 된다. 리스트2에서 5번째 라인과 같이 속성을 지정한다던가 6번째 라인 처럼 이벤트 핸들러를 지정하는 등의 작업을 모두 코드로서 진행할 수 있다. 다행히도 ActiveX 브라우저 컨트롤을 지정해야만 하는 속성이 그다지 많지 않다.

브라우저 컨트롤의 위치를 잡는 것이 하드 코드가 들어갈 수 있으므로 좀 거시하지만, 패널(Panel) 컨트롤을 이용하면 간단해 진다. 즉, 패널 컨트롤을 PlaceHolder 처럼 사용하여 폼에서 브라우저 컨트롤의 위치해야 할 레이아웃을 잡는다. 그리고 생성된 브라우저 컨트롤을 패널의 자식 컨트롤로 추가해 버리면 된다. 리스트2의 7번째 라인은 바로 이런 과정을 보여주고 있다.

리스트2는 일단 Load 이벤트가 발생한 이후에 ActiveX 브라우저 컨트롤을 생성하고 있으므로 Load 이벤트가 꼬이는 등의 문제를 유발하지 않는다.

Conclusion

닷넷 프레임워크 2.0에 새로이 추가된 WebBrowser 컨트롤은 다양한 기능과 편리한 사용법을 제공하는 훌륭한 브라우저 컨트롤이다. 하지만 WebBrowser 컨트롤은 임베디드 스마트 클라이언트처럼 IE 내에서는 사용할 수 없도록 하드 코드 되어 있다. 이를 해결하기 위해서는 닷넷 프레임워크 1.1에서 했던 것처럼 ActiveX 브라우저 컨트롤(shdocvw.dll)을 사용하면 된다.

ActiveX 브라우저 컨트롤을 브라우저 임베디드 스마트 클라이언트에서 사용할 때는 Interop 어셈블리(AxInterop.SHDocVw.dll 및 Interop.SHDocVw.dll) 역시 배포를 해야 한다는 점과 Load 이벤트의 발생 순서가 꼬인다는 점을 주의해야만 한다.

별 쓸데 없는 이야기를 좀 길게 한 듯 하다. 그리고 Load 이벤트가 꼬이는 부분은 닷넷 WinForm 과 WIN32 윈도우와의 관계를 잘 알아야만 완전히 이해할 수 있는 부분인지라 설명을 길게 할 수 없었음이 좀 아쉬웠다.

뭐... 아는 사람은 알고 모르는 사람은 걍 외워야지... (재수할 때 학원 강사의 명언)

아니꼬우면 딴 데 가든가... 텨~텨~텨~



Comments (read-only)
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 이방은 / 12/4/2006 5:49:00 PM
잘 읽었습니다..
저도 스마트클라이언트를 꼭 써보고는 싶은데..그걸 쓸만한 프로젝트가 안생기네요..>.<
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 쿠쿠쿠111 / 12/5/2006 12:55:00 AM
근데, 텨~텨~텨~ 가 뭔 뜻이지??? ^^
#re: 제가 찾던 거네요 / 임대진 / 12/5/2006 8:53:00 AM
예전에 임베딩이 않되서 웹으로 띄웠었는데...
이런 자료를 올려주시고
그동안 뜸하시던데....돌아오셔서 기뻐요....꾸준히 들릴게요
수고하세요
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / NETMANIA / 12/5/2006 9:16:00 AM
정말 시원하게 뚤어주시는것 같네요..
너무나 좋은 내용이었습니다..^^
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 블로그쥔장 / 12/5/2006 1:39:00 PM
쿠쿠쿠111//
텨~텨~텨~ 는 튀어~ 튀어~ 튀어~ 를 줄여서 쓰는 말로서
도망간다는 뜻이죠... 쩝...
텨~텨~텨~
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 어흥이 / 12/5/2006 3:30:00 PM
음...쉽게 얻을 수 없는 경험을 이번에도 간접적으로 훔쳐보고 갑니다.^^.
좋은 글 감사합니다~
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 무하야 / 12/6/2006 8:55:00 AM
매일 매일 재밌게 읽고 갑니다
좋은글 감사합니다~
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 잘보았습니다 / 12/6/2006 12:52:00 PM
이거 언제 스킬업 해야할지^^;

이해좀 됐음 좋겟네^^//

텨텨텨~~
#쿄쿄쿄 / 조조 / 12/14/2006 4:47:00 PM
이거 간만에 들어오니 또 게시물이 올라왔군요.
이번 게시물은 이미 해본것이긴 한데 역시 저랑은 또 다른 에로사항이 있어서 제가
생각안한 것도 있군요 쿄쿄쿄 정말 client의 상상력은 우리 기술력에 상상력을 불어넣어주죠 -_-
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / ded / 12/15/2006 3:25:00 PM
---
なるほど。Framework1.1と同じように、shdocvw.dll使えばいいんですね。
?考になりました。
---
과연.Framework1.1과 같이, shdocvw.dll 사용하면 좋네요.
참고가 되었습니다.
---
http://translation.ocn.ne.jp/amiweb/browser.jsp
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / zgg / 4/13/2007 5:52:00 PM
이거 찾고 있었는데, 마침 좋은 자료 잘 읽고 갑니다. ^^
#re: TIP : 스마트 클라이언트 내에서 브라우저 사용하기... / 김무경 / 11/2/2009 10:51:00 AM
ㅎㅎㅎ 그 무뇌충이라는 존재가 대부분 영업사원들이 그런 만행을 많이 저지르는 일이 많아서 참 많이 다툽니다 ㅎㅎㅎ
머 그들의 입장을 이해하지 못하는건 아니지만... 이건 머 도가 지나칠때도 많아서리 ㅋ
좋은 강좌 감사합니다^^