Windows Azure에서 Java 기반의 응용프로그램을 만들고 호스트할 수 있다는 이야기는 지속적으로 제시되어왔었습니다. 하지만, JSP 서블릿 페이지도 Windows Azure에서 호스트할 수 있을까요? Microsoft Architect로 활동 중인 David Chou가 작성한 블로그 포스트에 답이 있습니다. 오늘 제가 올리는 블로그 포스트는 다음 포스트에 대한 설명을 기초로 중요한 부분을 추출한 것임을 밝힙니다. (http://blogs.msdn.com/b/dachou/archive/2010/03/21/run-java-with-jetty-in-windows-azure.aspx)

Jetty (http://jetty.codehaus.org/) 프로젝트는 자바 기반의 오픈 소스 웹 서버로, HTTP 서버로서의 역할과 서블릿 컨테이너의 기능을 제공합니다. 정적, 동적 컨텐츠를 모두 지원하고, 독립적으로 실행할 수도 있고, 다른 프로세스에 부착되어 실행되는 기능 또한 제공됩니다. 이러한 특성을 바탕으로, Apache Project의 ActiveMQ, Cocoon, Hadoop, Maven Project, BEA Web Logic Event Server, Eucalyptus, FioranoMQ Java Messaging Server, Google AppEngine 및 안드로이드, Eclipse용 웹 툴킷 플러그인, 레드햇 J보스, Sonic MQ, 스프링 프레임워크, 사이베이스 EA서버, 짐브라 데스크톱 등 광범위하게 Jetty가 채택되고 있습니다.

Jetty 프로젝트는 아래와 같은 기능들을 제공합니다.

  • 비동기 HTTP 서버
  • 표준 지향의 서블릿 컨테이너
  • 웹 소켓 서버
  • 비동기 HTTP 클라이언트
  • OSGi, JNDI, JMX, JASPI, AJP 지원

응용프로그램 컨테이너의 관점에서 보면, Jetty는 전통적인 Java 기반의 Web Application Server들 (Tomcat과 같은)을 대치하는 배포 방법으로 사용될 수 있으며, 스프링 프레임워크 및 관련된 모든 파생 프로젝트들, EJB 컨테이너 등 대부분의 일반적인 웹 기반 자바 어플리케이션을 손쉽게 제공할 수 있는 것이 특징입니다.

Windows Azure에서의 Java 기술 지원

PDC'09 이후로 http://code.msdn.microsoft.com/winazuretomcat 웹 사이트를 통하여 Windows Azure 환경 위에서 Tomcat을 이용하여 JSP 기반 웹 응용프로그램의 구동 가능 여부와 샘플 코드를 지속적으로 홍보해왔었습니다. 그리고 이러한 사실을 바탕으로 http://blogs.msdn.com/jonbox/archive/2009/11/17/domino-s-demonstrates-tomcat-site-on-windows-azure.aspx 에서는 도미노 피자의 기존 피자 온라인 주문 웹 서비스를 Windows Azure 환경에서 구동하는 사례도 선보였습니다. 아래는 그동안 언급되거나 발표되어왔던 Windows Azure 환경에서의 Java 구동 방법에 관한 간략한 리스트입니다.

  • http://code.msdn.microsoft.com/winazuretomcat
  • http://www.interoperabilitybridges.com/projects/windows-azure-sdk-for-java.aspx
  • http://microsoftpdc.com/Sessions/SVC50
  • http://blogs.msdn.com/jonbox/archive/2009/11/17/domino-s-demonstrates-tomcat-site-on-windows-azure.aspx
  • 그러나 위에서 언급하는 Tomcat만이 Windows Azure 환경에서 사용할 수 있는 유일한 Web Application Server의 종류는 아닙니다. 사실, 위의 접근 방법들은 모두 Windows Azure Role에 함께 첨부되어 배포되는 Java 실행 환경 (JRE)를 기초로 하는 것이고, 그러므로 어떤 종류의 Java 패키지이든 관계없이 명령줄을 통하여 Classpath를 명시하는 방식으로 실행될 수 있습니다. 대개의 경우, 작업자 역할 (Worker Role) 패키지 위에서 실행되도록 디자인되고, Windows Azure 환경으로 배포될 수 있습니다. 그리고, 당연한 이야기이지만 Java와 Fast CGI를 결합하고, IIS 7.x를 기반으로 Fast CGI를 지원하는 Web Role을 이용할 수도 있습니다. 이 부분은 나중에 좀 더 자세히 소개하도록 하겠습니다.

    Windows Azure 환경에서 Jetty 실행

    아래는 실제 Windows Azure 환경에서 Jetty를 기반으로 실행하는 Windows Azure Worker Role의 구동 예시입니다.

    위와 같이 구현하기 위한 방법을 지금부터 차례대로 따라해보기로 하겠습니다.

    1. Windows Azure Worker Role 프로젝트를 만들고, Worker Role 진입점 메서드인 Run 메서드의 중심부에 다음과 같이 프로그래밍합니다.

    string response = "";
    try
    {
        System.IO.StreamReader sr;
        string port = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HttpIn"].IPEndpoint.Port.ToString();
        string roleRoot = Environment.GetEnvironmentVariable("RoleRoot");
        string jettyHome = roleRoot + @"\approot\app\jetty7";
        string jreHome = roleRoot + @"\approot\app\jre6";
        Process proc = new Process();
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.StartInfo.FileName = String.Format("\"{0}\\bin\\java.exe\"", jreHome);
        proc.StartInfo.Arguments = String.Format("-Djetty.port={0} -Djetty.home=\"{1}\" -jar \"{1}\\start.jar\"", port, jettyHome);
        proc.EnableRaisingEvents = false;
        proc.Start();
        sr = proc.StandardOutput;
        response = sr.ReadToEnd();
    }
    catch (Exception ex)
    {
        response = ex.Message;
        Trace.TraceError(response);
    }

    제가 이전에 게시한 글 "[Software Development/Windows Azure] - Windows Azure 들여다보기"에서 언급한 내용들을 참조하여 위의 코드를 보시면 이해하기 쉽습니다. 몇 가지 Windows Azure 환경에서 기본으로 제공되는 환경 변수, 디렉터리 설정 등을 바탕으로 Jetty 프로세스를 Java VM (java.exe)를 통하여 실행하도록 명령어를 구성하고 실행한 다음, 표준 출력의 내용을 Windows Azure Worker Role Process로 가져오도록 만드는 것을 볼 수 있습니다.

    2. Jetty 프로젝트와 Java VM의 최신 버전을 Windows Azure Worker Role 프로젝트에 추가합니다.

    다음 그림과 같이 폴더를 구성하면 되겠습니다. jetty7 폴더와 jre6 폴더 아래에 직접 해당 프레임워크 및 시스템의 실제 디렉터리 구조가 오도록 맞추면 문제 없습니다. 참고로, 확실하게 동작할 수 있도록 만들기 위하여 Java VM의 경우는 특별히 Windows Platform을 기반으로하는 x64 아키텍처 버전을 다운로드하여 아래 그림과 같이 넣어두어야 합니다. (중요)

    3. ServiceDefinition.csdef 파일 수정하기

    1단계에서 사용한 HttpIn이라는 설정을 Worker Role에 추가해야 하므로, ServiceDefinition.csdef 파일을 찾아 "WorkerRole" 요소 아래에 다음과 같이 내용을 추가하고 저장합니다. 참고로 이 설정은 Windows Azure Worker Role이 80번 TCP 포트 통신이 필요함을 Windows Azure 환경 내의 방화벽에게 통지하기 위한 목적으로 사용됩니다.

    <Endpoints>
      <InputEndpoint name="HttpIn" port="80" protocol="tcp" />
    </Endpoints>

    4. Windows Azure에서 사용할 수 있도록 Jetty 환경 설정

    Jetty가 사용되거나 환경 설정을 만드는 방법은 Windows Azure 환경에서 여러 가지가 있을 수 있지만, 특별히 이와 같이 Worker Role 위에서 Standalone Server로 동작하기 위하여 필요한 환경 설정이 있어서 이를 소개합니다. Windows Azure 환경에서는 Jetty가 기본으로 사용하는 NIO ChannelConnector 대신 BIO SocketConnector를 사용하도록 구성해야 합니다. NIO ChannelConnector에서는 내부적으로 루프백 연결을 기초로 하지만 Windows Azure 환경에서는 이것이 가로막혀있기 때문이라는 것이 David Chou의 설명입니다.

    이러한 설정을 수정하기 위해서는 Jetty 패키지 디렉터리 아래의 etc 디렉터리의 jetty.xml 파일을 수정해야 하며, <New> 태그의 class attribute를 org.eclipse.jetty.server.nio.SelectChannelConnector 대신 org.eclipse.jetty.server.bio.SocketConnector로 변경하고, NIO ChannelConnector에만 한정되는 몇 가지 옵션을 제거하는 방법으로 변경이 가능하며 정리하면 다음과 같습니다.

    <Call name="addConnector">
      <Arg>
        <New class="org.eclipse.jetty.server.bio.SocketConnector">
          <Set name="host"><SystemProperty name="jetty.host" /></Set>
          <Set name="port"><SystemProperty name="jetty.port" default="8080" /></Set>
          <Set name="maxIdleTime">300000</Set>
        </New>
      </Arg>
    </Call>

    그리고 좀 더 최적화된 설정을 위하여, Jetty가 자체 로그를 기록하지 않도록, etc 디렉터리의 jetty.xml 파일에서 RequestLog 핸들러 부분을 주석으로 처리하고, 확장자가 war인 패키지의 압축을 풀지 않도록 etc 디렉터리의 jetty.xml 파일에서 addBean "org.eclipse.jetty.deploy.WebAppDeployer"의 "extract" 프로퍼티의 값을 "false"로 지정하고, contexts 디렉터리의 test.xml 파일에서 <Set name="extractWAR"> 부분의 프로퍼티 값을 "false"로 지정하였다는 것이 저자의 설명입니다.

    이러한 설정들이 모두 적용된 실제 샘플은 http://cid-b43074894ffe6264.skydrive.live.com/self.aspx/.Public/Projects/JettyCloudService.zip 에서 다운로드받아보실 수 있습니다. :-)

    마무리

    Windows Azure Platform이 Microsoft의 기술이므로 철저히 .NET Framework 기반의 응용프로그램 개발만을 지원할 것이라는 편견은 종합적으로 "잘못된 것입니다." Windows Server 2008 R2의 기술을 기반으로 하고 있기 때문에, 여러분의 응용프로그램이 Windows Server 2008 R2, 그리고 64비트 환경에서 성공적으로 수행될 수 있는 조건을 갖추고 있다면 Windows Azure Platform은 성공적으로 여러분들의 클라우드 컴퓨팅으로의 계획을 이끌어 줄 수 있을 것입니다.

    만약 좀 더 심층적인 예제나, Tomcat 기반의 Windows Azure 구동 사례를 보기 원하신다면, http://code.msdn.microsoft.com/winazuretomcat 를 방문하셔서 샘플 코드와 문서를 다운로드하시기를 권합니다.

    감사합니다. :-)

    저작자 표시 비영리 동일 조건 변경 허락
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)
    이번 Exploring Windows Azure 세미나를 시작으로 올 한해는 Windows Azure에 관련된 다양한 발표 및 소프트웨어 개발 활동을 병행해 나갈 계획에 있습니다. 계획의 일환으로, Windows Azure에서 사용할 수 있도록 이전부터 지속적으로 개발해 오던 xPlatform 프로젝트를 Windows Azure에서도 사용할 수 있도록 준비할 계획입니다.

    xPlatform 프로젝트는 Windows를 시작으로, Linux, Mac OS X 등 다양한 운영 체제에서 기본적으로 닷넷의 기본 프레임워크에서는 노출하지 않는 Low Level API를 손쉽게 호출하고, 포인터나 비 관리 메모리 영역을 손쉽게 제어할 수 있도록 돕습니다. 현재는 32비트 버전의 Windows에서 사용할 수 있도록 코드를 완성하였고 첫 알파 릴리즈를 지난 가을에 런칭한 상태입니다.

    Windows Azure는 단순히 웹 서비스를 클라우드 환경 위에 올리는것 뿐만이 아니라, Windows Azure의 바탕을 이루고 있는 64비트 버전의 운영 체제를 얼마나 잘 활용할 수 있는가에 대한 부분도 중요한 관건이 될 것입니다. Windows Azure의 기능을 극대화하고, 향후에 개발될 다양한 클라우드 기반 응용프로그램의 품질 향상에 기여할 수 있게 하기 위하여 xPlatform을 64비트 버전의 서버 운영 체제에서도 사용할 수 있도록 프로젝트 방향을 정하기로 하였습니다.

    Windows Azure 개발 및 플랫폼 관련 프로그래밍에 관심있으신 개발자 여러분들의 많은 관심과 참여 부탁드립니다. 조만간, 64비트 버전으로 개량된 xPlatform 라이브러리를 런칭할 수 있도록 하겠습니다. 감사합니다. :-)

    xPlatform 홈페이지: http://www.codeplex.com/blendxplatform/

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    인터넷 서핑을 하던 도중 눈에 띄는 아티클 하나가 보였습니다. Jim Teeuwen씨에 의해 작성된 "Exporting Managed Code as Unmanaged" (http://www.csharphelp.com/archives3/archive500.html) 라는 글이었습니다. 이 글의 내용은 IL Assembler와 IL Disassembler를 활용하여 Win32 Native 환경에서 직접 로드할 수 있는 DLL을 만드는 방법이었습니다.

     

    이 글을 작성하였던 분의 경우, Blitz Basic이라는 개발 환경에서 COM Interop을 사용할 수 없었던 점을 고심한 끝에 찾게된 방법을 소개한 것으로, 이러한 방법은 Visual C++ CLR 혼합 모드 컴파일의 결과물로부터 아이디어를 고안해 내신 것으로 보입니다.

     

    이 글에서 소개하는 방법의 요지는, 기본적으로 Managed 환경에서는 필요로 하지 않는 V-TABLE과 함수 노출에 대한 데이터를 MSIL이 제공하는 기본 사양 내에 포함된 명령어를 통해서 데이터를 작성하고, DLL의 유형을 Win32 일반 DLL로 변경한다는 것에 있습니다. 다음은 수정 전의 MSIL 코드와 수정 후의 MSIL 코드입니다. 수정 후의 MSIL 코드에는 강조 표시를 해두었습니다.

     

    수정 전의 코드

     

    수정 후의 코드

     

    위의 코드를 ILASM으로 다시 묶어서 만들어진 DLL을 Dependency Walker로 살펴보면 아주 흥미로운 결과가 나타납니다.

     

     

    SayHello라는 일반적인 DLL Export 문맥이 나타나는 것을 볼 수 있습니다. 그리고 MSCOREE.DLL과 연결되어있고, 클래스 라이브러리이므로 CorDllMain과 연관되어있다는 것이 보입니다. 그러면 Reflector에서의 결과는 어떨까요?

     

     

    다를 것이 없습니다. 다만 도출된 IL 결과를 살펴보면 우리가 방금 추가했던 명령어들만은 누락된채 MSIL이 나오는 것을 볼 수 있습니다.

     

    기술적으로, 이와 같이 COM Interop이 아닌 방법으로 양쪽을 연결하는 것은 가능하지만, 생각해봐야할 문제점들이 몇 가지가 존재합니다.

    • 작업 비용이 큰 편입니다. 컴파일러에 의하여 한번에 빌드될 수 없는 형태를 취하고 있으며 뿐만 아니라 수작업으로 진행해야 합니다.
    • 마샬링 문제가 있을 수 있으며, 사용할 수 있는 형식에도 제한이 많다는 점을 감안해야 합니다.

    시간적인 여유가 된다면, Reflection 기술과 Mono Project의 일부인 Cecil 라이브러리를 활용하여 이 아티클에서 설명하고 있는 기능에 대한 빌드 자동화 툴을 만드는 것도 재미있을것 같다는 생각이 듭니다.

     

     

    소스 코드 및 실제 샘플을 보고 싶으신 분은 위의 파일을 다운로드하시면 되겠습니다.

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    Visual Studio 2010 베타 2가 이 글을 쓰는 현 시점에 Express Edition과 함께 새롭게 공개되었습니다. .NET Framework 4.0과 더불어서 C# 4.0, Visual Basic .NET 10.0 등 언어들의 기능도 새롭게 향상되었으며, 특히 이목을 끄는 것은 Dynamic Language Runtime과 연동되는 Dynamic Programming에 관한 것입니다.

     

    Dynamic Programming에 대해서, 철수네 소프트웨어 세상 블로그 (http://blogs.msdn.com/bkchung/)에 소개된 내용을 오래 전에 처음 포스팅을 했었지만 최근까지도 dynamic 키워드의 실용적인 사례를 찾지 못하고 있었습니다만, 최근 dynamic 키워드의 새로운 활용 가능성을 새롭게 발견하였습니다. 여러 시나리오가 있을 수 있겠지만 제가 오늘 이 포스트를 통해서 조명해보고자 하는 것은 Windows Forms가 제공하는 Web Browser와의 연동에 관한 것입니다.

     

    Windows Forms가 버전 2.0에 들어서면서부터 매우 실용적인 컴포넌트를 새롭게 추가하였는데 바로 웹 브라우저 컨트롤이 그것이었습니다. 웹 브라우저 컨트롤은 단순히 브라우저로서도 활용할 수 있지만 잘 활용한다면 사용자 인터페이스를 웹 기반으로 업그레이드하는 것 또한 가능합니다.

     

    하지만 COM Interop 프로그래밍에 기댈 수 밖에 없는 부분은 여전히 많았고, 당시의 C# 언어 사양만으로 COM Interop을 다루는데에는 비효율적이고 생산성이 떨어지는 부분이 많았습니다. 그런 이유로, 예전의 브라우저 컨트롤보다는 "조금 뛰어난" 수준으로 인지되는데에 그쳤습니다. 하지만 이번에 새로 소개되는 Dynamic Programming과 결합하면 매우 매력적인 프레임워크로 다시 거듭나게 됩니다.

     

    Dynamic Programming을 시험해볼 좋은 자바스크립트 예제를 찾던 중, http://www.javascriptfreecode.com/34.htm 의 소스 코드를 발췌하여 Dynamic Programming 버전으로 만들어보았습니다.

     

    본디 이 스크립트가 하는 일은 Internet Explorer가 고유하게 제공하는 웨이브 필터를 활용해서 텍스트를 화려하게 보일 수 있도록 치장하는 것입니다. 그러나 이 기능을, 여러 브라우저가 보는 페이지로서가 아닌, 온전히 Windows Forms 응용프로그램만을 위한 고유한 기능으로 바꾸어보도록 하겠습니다.

     

    1. 폼에 웹 브라우저 컨트롤을 새로 추가합니다.
    2. 웹 브라우저 컨트롤이 처음 보일 페이지를 about:blank 로 설정합니다. 빈 문서를 준비시키도록 하는데에 도움이 됩니다.
    3. 기본 이벤트인 DocumentCompleted 이벤트 핸들러를 새로 추가합니다.
    4. 아래의 코드를 작성합니다.

    private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        dynamic document = this.webBrowser.Document.DomDocument;
        dynamic window = this.webBrowser.Document.Window.DomWindow;

        document.write(String.Format(@"
    <!-- Original Source: http://www.javascriptfreecode.com/34.htm -->
    <div id=""Zittertext"" style=""font-weight: bold; font-size: 25pt; filter: Wave(freq=5, light=0, phase=8, strength=1); width: 100%; color: orange;"">
    <div style=""background-color: #FFFF00; text-align: center; color: black; display: inline;"">
    {0}
    </div>
    </div>
    ", window.prompt("Please type a message to display: ", "Hello World!")));

        Action DynWave = () =>
        {
            dynamic filter = document.getElementById("Zittertext").filters[0];
            if (filter.freq > 130)
                filter.freq = 15;
            filter.freq++;
            if (filter.phase > 50)
                filter.phase = 1;
            filter.phase++;
            if (filter.strength > 1)
                filter.strength = .1;
            filter.strength++;
        };

        Timer timer = new Timer();
        timer.Tick += (a, b) => DynWave();
        timer.Interval = 50;
        timer.Enabled = true;
    }

     

    위의 코드에서 우리가 관심있게 봐두어야 할 부분은 dynamic으로 선언한 변수 뒤에 나타나는 표현식들입니다. 컴파일러 기반의 언어들은 늘 그렇듯 문법의 정확성을 언제나 따지게 됩니다. C#도 분명히 그런 언어였습니다. 하지만 Dynamic Programming을 통하여 위와 같이 컴파일 시점에는 존재하지 않는 내용을 런타임 때에 유추해낼 수 있게 되었습니다.

     

    여기서 의문점이 드는 것이 있는데, 그렇다면 이를 유추해내기 위해서 어떤 식으로 구현을 제공하는가에 대한 부분입니다. 이 부분에 대한 답은 바로 .NET Framework가 런타임 과정에서 정확한 객체 정보를 확인하기 위해서 사용하는 ObjectBinder에 있습니다. regasm 유틸리티로 등록해두었던 COM Interop Assembly나 Primary Interop Assembly에 대한 내용을 런타임 차원에서 관리하고, 이를 자동으로 연결하고 있는 것입니다. 직접 프로젝트에서 참조를 하지 않았다고 할지라도 그러합니다.

     

    그리고 이러한 기능 위에 DLR과 더불어서 이미 LINQ에서 소개된 적이 있는 Expression Tree 해석 엔진에 의하여 C# 기준으로 작성된 코드를 Object Binder가 가지고 있는 정보와 일치시켜 정확한 기능을 하는 실제 코드를 컴파일 과정에서 작성하게 되는 것입니다. 이것은 형식 정보를 읽어서 매번 같은 컬렉션을 여러번 반복하면서 조회하는 Reflection 기반의 프로그래밍보다 훨씬 빠르게 동작하고, 훨씬 정확도가 높습니다.

     

    위의 코드를 실행한 결과는 아래와 같습니다. 중간에 어떤 메시지를 표시할 것인지를 묻는 브라우저의 프롬프트 창에 원하는 메시지를 넣어보는 것도 괜찮을 것입니다.

     

     

    Dynamic Programming에 대한 자세한 내용은 http://msdn.microsoft.com/en-us/library/dd264736(VS.100).aspx 의 내용을 참고하시면 도움이 될 것입니다.

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    다음에 나열된 코드는 xPlatform의 msvcrt 모듈을 활용하여 ANSI, 유니코드 인코딩 아래에서 문자열의 길이를 측정하는 샘플 코드입니다.

     

    using System;
    using xPlatform;
    using xPlatform.x86.msvcrt;

    namespace xPlatformSample
    {
        class Program
        {
            static void Main(string[] args)
            {
                while (true)
                {
                    Console.WriteLine("측정할 문자열을 입력하고 Enter를, 종료하려면 입력없이 Enter를 누르세요.");
                    Console.Write(">> ");
                    string input = Console.ReadLine();

                    if (input.Length < 1)
                        break;

                    Console.WriteLine();
                    Console.WriteLine("ANSI Length (strlen): {0} Byte(s)", msvcrt.strlen(input));
                    Console.WriteLine("Unicode Length (wcslen): {0} Char(s)", msvcrt.wcslen(input));
                    Console.WriteLine();
                }
            }
        }
    }

     

    실행 결과

     

     

    xPlatform 다운로드: http://blendxplatform.codeplex.com/

     

    xPlatform 활용 예제는 계속 업로드될 예정입니다. xPlatform을 활용하는 예제들 중 필요한 것이 있으면 요청에 따라 예제 코드를 만들어서 공개할 수 있으니 많은 참여 부탁드립니다. :-)

     

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    닷넷 프레임워크를 통하여 응용프로그램을 개발할 때, 특히 Windows Forms 기반의 다중 스레드 프로그램을 구성할 때 자주 적용되는 패턴이 하나 있다면, 단일 메서드를 가지고 PreCondition/PostCondition으로 양분하는 패턴이 있을 것입니다. 예를 들면 다음과 같습니다.

     

    private void MyForm_Load(object sender, EventArgs e)
    {

        if (this.InvokeRequired)

        {

             this.Invoke(new EventHandler(MyForm_Load), new object[] { sender, e });

             return;

        }

        // 실제 메서드 코딩
    }

     

    위와 같이 조건에 따라서, 같은 메서드를 상태에 변화를 가하여 다시 호출하는 기법은 Windows Forms 뿐만 아니라 일반적인 응용프로그램에서도 자주 활용될 수 있는 기법이 될 것입니다. 사실, 위와 같은 코드는 어려울 것이 없겠습니다만 저는 이를 일반화할 수 있는 방법을 고민하던 중에, Reflection과 Interop을 이용할 수 있다는 것을 발견하였습니다.

     

    using System;
    using System.Reflection;

    namespace dotForex.Test
    {
        class Program
        {
            static bool needInvoke = true;

            static void Main(string[] args)
            {
                if (needInvoke)
                {
                    Action<string[]> func = Delegate.CreateDelegate(
                        typeof(Action<string[]>),
                        MethodInfo.GetCurrentMethod() as MethodInfo) as Action<string[]>;
                    needInvoke = false;
                    func(args);
                    return;
                }
                else
                {
                    Console.WriteLine("Test");
                }

                return;
            }
        }
    }

     

    위의 코드에서 강조표시된 부분이 일반화에서 핵심이 되는 부분들입니다. Delegate 클래스의 CreateDelegate 메서드를 통하여, 생성할 대리자의 형식을 지정하고, 현재 메서드의 정보를 가져와서 이를 대입하는 방법입니다.

     

    위와 같은 기법을 통하여 아래와 같이 Windows Forms의 스레드 안정성을 고려한 이벤트 핸들러 처리를 일반화하는 것도 가능합니다.

     

    using System;
    using System.Reflection;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    namespace dotForex.Application
    {
        public sealed partial class ErrorReportForm : Form
        {
            public ErrorReportForm()
                : base()
            {
                this.InitializeComponent();
            }

            private object EnsureThreadSafe(MethodInfo targetMethod, Type targetDelegateType, params object[] arguments)
            {
                if (!this.InvokeRequired)
                    throw new InvalidOperationException("EnsureThreadSafe is not required at this time.");

                if (targetMethod == null)
                    throw new ArgumentNullException("targetMethod");

                if (targetDelegateType == null)
                    throw new ArgumentNullException("targetDelegateType");

                if (!targetDelegateType.IsSubclassOf(typeof(Delegate)))
                    throw new ArgumentException("Selected type is not a delegate type.", "targetDelegateType");

                Delegate methodDelegate = Delegate.CreateDelegate(
                    targetDelegateType,
                    targetMethod);

                if (methodDelegate == null)
                    throw new Exception("Method cannot converted as delegate.");

                return this.Invoke(methodDelegate, arguments);
            }

            private void ErrorReportForm_Load(object sender, EventArgs e)
            {
                if (this.InvokeRequired)
                {
                    this.EnsureThreadSafe(MethodInfo.GetCurrentMethod() as MethodInfo, typeof(EventHandler), sender, e);
                    return;
                }

                // Method Coding Here
            }
        }
    }

     

     

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    약 3개월만의 작업 기록 포스팅입니다. Core Runtime에 대한 테스트를 강화하고 코딩할 때 불편한 부분을 최소화하면서 실제 C 프로그래밍 언어와 비슷한 환경을 나타낼 수 있도록 만드는데에 많은 노력을 들였습니다.

    Buffer, String 계열 클래스의 확장 및 개선

            [Test]
            public void memchrTest()
            {
                int ch = 'r';
                GlobalHeapAnsiString str = new GlobalHeapAnsiString("lazy");
                GlobalHeapAnsiString @string = new GlobalHeapAnsiString("The quick brown dog jumps over the lazy fox");
                GlobalHeapAnsiString fmt1 = new GlobalHeapAnsiString("         1         2         3         4         5");
                GlobalHeapAnsiString fmt2 = new GlobalHeapAnsiString("12345678901234567890123456789012345678901234567890");

                SBytePointer pdest;
                int result;

                Console.Write("String to be searched:\n             {0}\n", @string.ToString());
                Console.Write("             {0}\n             {1}\n\n", fmt1.ToString(), fmt2.ToString());

                Console.Write("Search char: {0}\n", (char)ch);
                pdest = msvcrt.memchr(@string, ch, (size_t)@string.Length);
                result = (int)(pdest - @string + 1);

                if (pdest != null)
                    Console.Write("Result:      {0} found at position {1}\n", (char)ch, result);
                else
                    Console.Write("Result:      {0} not found\n", (char)ch);

                Assert.AreEqual(12, result);

                str.Dispose();
                @string.Dispose();
                fmt1.Dispose();
                fmt2.Dispose();
            }

    위의 코드에서 보시는것처럼 String Buffer 클래스에 직접 Add / Subtract 연산자 오버로드를 추가하여 실제 포인터 연산의 결과를 재현합니다. 형식화된 Pointer 클래스와 다른점이 있다면 자기 자신에 대한 주소 설정은 허용하지 않습니다. 이것은 비관리 메모리 영역의 주소가 변경됨으로 인하여 발생할 수 있는 할당 해지 실패로 인한 메모리 누수를 예방하기 위한 디자인입니다.

    총 20여종의 주요 값 형식에 대한 형식화된 포인터 제공 (Typed Pointer)

    형식화된 포인터에 세부적인 기능 조절을 더하고 제거하는 노력을 통하여 총 20여종의 주요 값 형식에 대한 형식화된 포인터를 제공하게 되었습니다. 이들 포인터 형식 모두는 외관상 CLS 표준 사양을 만족하도록 만들어졌으며 포인터 개념이 없는 프로그래밍 언어 (예: Visual Basic .NET, Visual J# 등)에서도 포인터 연산을 간접적으로 사용할 수 있게 디자인되어있습니다.

    • AutoCharPointer
    • BooleanPointer
    • BytePointer
    • DateTimePointer
    • DecimalPointer
    • DoublePointer
    • GuidPointer
    • Int16Pointer
    • Int32Pointer
    • Int64Pointer
    • IntPtrPointer
    • Pointer<T>
    • SBytePointer
    • SinglePointer
    • TimeSpanPointer
    • UInt16Pointer
    • UInt32Pointer
    • UInt64Pointer
    • UIntPtrPointer
    • WideCharPointer

    사용 빈도가 높을 것으로 추정되거나 기본 형식들에 대한 형식화된 포인터는 모두 제공하고 있습니다. 하지만 특정한 목적으로 직접 추가한 구조체에 대한 지원도 필요했고 경우에 따라서는 제네릭에 대응되는 가변 포인터에 대한 구현을 필요로 하였기 때문에 Pointer<T> 형식을 새로 추가하였습니다. 전용 포인터를 이용하여 계산하는것보다는 연산 횟수가 많다는것이 단점입니다.

    실제 메모리 구조의 크기를 조사해주는 unsafe sizeof() 연산자의 Managed 버전 제공

            [Test]
            public void NativeSizeOfTest1()
            {
                foreach (Type eachType in new Type[] { typeof(char), typeof(bool),
                    typeof(byte), typeof(DateTime), typeof(decimal), typeof(double),
                    typeof(Guid), typeof(short), typeof(int), typeof(long), typeof(IntPtr),
                    typeof(sbyte), typeof(float), typeof(TimeSpan), typeof(ushort),
                    typeof(uint), typeof(ulong), typeof(UIntPtr)})
                {
                    Console.WriteLine(">> {0} byte{1}", Utilities.NativeSizeOf(eachType),
                        Utilities.NativeSizeOf(eachType) > 1 ? "s" : String.Empty);
                }
            }

    위의 테스트 코드에서 나열한 형식들에 대한 sizeof 연산자의 결과를 반환해주는것이 NativeSizeOf 메서드입니다. 그 외의 형식들에 대해서는 Marshal.SizeOf의 결과를 반환합니다.

    NUnit 테스트 코드 프로젝트 시작

    프로젝트에 대한 검증을 수행하기 위하여 NUnit 프로젝트를 생성하고 테스트하면서 조금씩 진척을 시켜나가고 있습니다. NUnit 테스트 코드의 내용은 실제 적용에 많은 도움이 되는 예제 코드로도 활용이 가능하니 한번 살펴보시길 권합니다.

    향후 계획

    조만간 공식 릴리즈를 0.1 버전으로 런칭할 계획을 세우고 있습니다. 그리고 준비가 되는대로 Windows CE, Windows Mobile에 관한 API도 수집하여 데이터베이스화를 수행할 예정이니 관심있으신 분들은 블로그나 데브피아 등을 통하여 연락주시면 언제든 채널이 열려있으니 함께 하실 수 있습니다.

    프로젝트 홈페이지는 http://blendxplatform.codeplex.com 이며 누구나 체크아웃해가실 수 있습니다. 서브 버전, TFS 등을 이용하여 쉽게 받아가실 수 있습니다.

    Subversion 주소: https://blendxplatform.svn.codeplex.com/svn
    TFS 주소: https://tfs05.codeplex.com

     

     

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    등록하기: http://www.devpia.com/DevStudy/Lecture/OffLineDetail.aspx?nSemiID=1395&lectype=dev

    사용자 삽입 이미지

    등록하기: http://www.devpia.com/DevStudy/Lecture/OffLineDetail.aspx?nSemiID=1395&lectype=dev

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    지난 번 시간에는 C++ 클래스의 생성자와 소멸자에 대한 프록시 함수를 디자인 하는 것에 관해서 다루어보았습니다. 이번 시간에는 일반 멤버 함수, 정적 멤버 함수, 멤버 변수에 관한 사항을 다루어보도록 하겠습니다.

    그리고 강좌를 시작하기 전에 따로 팁&강좌란에 옮겨주신 운영자 내길님께 감사의 뜻을 전합니다. 미약한 강좌이지만 여러 분께 도움이 되길 바라는 마음에서 힘이 닿는대로 계속 강좌를 써 나가도록 하겠습니다.

    * 지난번 글을 다시 여시지 않도록 하기 위하여 클래스를 다시 정의합니다.

    // TestClass.h에 정의하는 내용입니다.

    class TestClass

    {

    public:

        void TestFunction(int value);

        int MyFunction(void);

    private:

        int m_myValue;

    public:

        int GetMyValue(void);

        void SetMyValue(int value);

    public:

        TestClass(int value);

    }

    // TestClass.cpp에 정의하는 내용입니다.

    #include <iostream>

    void TestClass::TestFunction(int value)

    {

        stdout << value;

    }

    int TestClass::MyFunction(void)

    {

        return 123;

    }

    int TestClass::GetMyValue(void)

    {

        return this->m_myValue;

    }

    void TestClass::SetMyValue(int value)

    {

        this->m_myValue = value;

    }

    TestClass::TestClass(int value)

    {

        SetMyValue(value);

    }

    3. 일반적인 멤버 함수의 프록시 함수 만들기

    멤버 함수는 크게 두 종류로 구분이 됩니다. 하나는 일반 멤버 함수이며 또 하나는 정적 멤버 함수입니다. 일반 멤버 함수는 클래스의 인스턴스가 생성된 이후에 호출할 수 있는 함수를 뜻합니다. 반면 정적 멤버 함수는 클래스의 이름을 네임스페이스로 취급하여 호출될 수 있는 함수를 뜻합니다. 쉽게 생각하시면 static 키워드를 붙인것과 붙이지 않은 것의 차이입니다. (C++의 static 키워드가 C#에서도 쓰이고 있으며, VB .NET에서는 Shared 키워드를 사용합니다.)

    우선은 일반적인 멤버 함수에 관하여 생각해 보기로 하겠습니다.

      void TestClass_TestFunction(TestClass* pInstance, int value)

      {

        if(pInstance)

             pInstance->TestFunction(value);

      }

      int TestClass_MyFunction(TestClass* pInstance)

      {

        if(pInstance)

             return pInstance->MyFunction();

        return 0;

      }

    값을 반환하지 않는 멤버 함수의 프록시 함수를 만드는 것은 반환할 값에 관해 연연할 필요가 없으며 단지 받은 포인터가 NULL 참조인지 아닌지만을 확인해서 선택적으로 호출하기만 하면 되는 것입니다.

    값을 받는 멤버 함수의 프록시 함수를 만드는 것은 정상적으로 호출이 이루어졌을 경우와 그렇지 않을 경우를 구분하여 값을 반환해야 합니다. 즉, 성공하면 메서드가 반환하는 값 그 자체를 반환해야 하며 호출에 실패할 경우 실패하였음을 공지하여야 합니다. 반환 값을 반환할 때에는 실행이 어디서 실패한 것인지를 분명히 구분해주어야 하므로 복잡한 로직을 설계하는 경우 신경써야 합니다.

    4. 정적 멤버 함수의 프록시 함수 만들기

    정적 멤버 함수는 클래스의 이름을 하나의 네임스페이스로 취급하는 함수이기 때문에 클래스의 인스턴스를 필요로 하지 않습니다. 단지 static으로 선언된 C++ 멤버 함수 하나만을 호출해주면 됩니다. 여기서는 앞에서 선언한 TestFunction과 MyFunction이 정적 멤버 함수라고 가정하고 예제를 보여드리도록 하겠습니다.

      void TestClass_static_TestFunction(int value)

      {

        TestClass::TestFunction(value);

      }

      int TestClass_static_MyFunction()

      {

        return TestClass::MyFunction();

      }

    5. 멤버 변수에 관한 프록시 함수 만들기

    멤버 변수에 관한 프록시 함수를 만들기 위해서는 몇 가지 사항을 더 고려해 봐야 합니다. 첫 번째 조건은 멤버 변수의 액세스 제한이 public 인지 아니면 private, protected와 같은 제약이 걸려있는지 여부입니다. 두 번째는 일반 멤버 변수인지 정적 변수인지 확인해야 합니다.

    첫 번째 조건에서 후자의 경우에 해당된다면 클래스의 멤버 함수로 getter와 setter 멤버 함수를 도입해야 합니다. 보호된 멤버를 직접 액세스해서 값을 변경할 수는 없기 때문입니다. 그러나 전자의 경우에 해당된다면 getter와 setter 멤버 함수를 도입하지 말고 프록시 함수로 getter 함수와 setter 함수를 정의하시면 됩니다.

    두 번째 조건에서 후자의 경우에 해당된다면 멤버 함수와 마찬가지로 getter와 setter 함수 모두에서 클래스의 인스턴스는 필요치 않습니다. 그러나 전자의 경우에 해당된다면 getter와 setter 함수 모두에서 클래스의 인스턴스가 필요합니다. getter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 실패 코드를 적절히 통지해야 하며 setter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 통지 코드나 출력용 매개 변수를 받아서 통지하는 것이 좋습니다. 저수준 코드에서는 C#의 try-catch 핸들러를 알지 못하기 때문입니다.

    모두 네 가지 경우의 수가 존재합니다. 각각 살펴보도록 하겠습니다.

    5.1. 만약 m_myValue가 public: 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현

      int TestClass_public_GetMyValue(TestClass* pInstance)

      {

        if(pInstance != NULL)

             return pInstance->m_myValue;

        else return 0;

      }

      int TestClass_public_SetMyValue(TestClass* pInstance, int value)

      {

        if(pInstance != NULL)

         {

             pInstance->m_myValue = value;

             return 1; // 성공했음을 알리는 표현

        }

        return 0; // 액세스에 실패했음을 알리는 표현

      }

    5.2. 만약 m_myValue가 public: 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현

      int TestClass_public_static_GetMyValue()

      {

        return TestClass::m_myValue;

      }

      // 정적 멤버 변수는 특별한 상황이 아닌 이상 실패하는 경우는 없습니다.

      // 굳이 정확성을 추구하자면 C++의 Structured Exception Handling을 사용해도 좋지만

      // 복잡한 로직을 사용하지 않을 경우 성능상의 손해가 있을 수 있습니다.

      void TestClass_public_static_SetMyValue(int value)

      {

        TestClass::m_myValue = value;

      }

    5.3. 만약 m_myValue가 public:이 아닌 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이어야 합니다.)

      int TestClass_private_GetMyValue()

      {

        if(pInstance != NULL)

             return pInstance->GetMyValue();

        else return 0;

      }

      int TestClass_private_SetMyValue(int value)

      {

        if(pInstance != NULL)

         {

             pInstance->SetMyValue(value);

             return 1; // 성공했음을 알리는 표현

        }

        return 0; // 액세스에 실패했음을 알리는 표현

      }

    5.4. 만약 m_myValue가 public:이 아닌 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이면서 정적 멤버 함수여야 합니다.)

      int TestClass_private_static_GetMyValue()

      {

        return TestClass::GetMyValue();

      }

      void TestClass_private_static_SetMyValue(int value)

      {

        TestClass::SetMyValue(value);

      }

    오늘 시간에는 세 가지 사항을 살펴봤습니다. 다음 시간에는 마지막으로 C#과의 연동 방법에 관하여 살펴보도록 하겠습니다.

    오늘 시간에 강의한 내용이 프록시 함수를 설계하는 패턴의 전부는 아닙니다. 얼마든지 새로운 방식을 사용하여 입맛에 맞게 고치실 수 있음을 인지하시고 활용해 주십시오. 그리고 이 프록시 함수는 포인터를 활용해서 구현된다는 점을 잊으시면 안됩니다.

    참고 자료: Win32 API를 플랫폼 호출할 수 있도록 미리 코딩된 플랫폼 호출 코드들의 라이브러리를 제공하는 사이트가 있습니다. http://www.pinvoke.net/

    긴 강좌를 봐주셔서 감사합니다. 행복한 하루 되세요. ^^

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)

    wxWindows의 .NET용 포팅인 wx.NET의 소스 코드를 살펴보다가 재미있는 토픽을 한 가지 발견해서 글을 써올립니다.

    C#은 System.Interop.DllImportAttribute 라는 속성을 사용해서 MS .NET에서는 DLL을, Mono의 경우 Win32 버전에서는 DLL을, Linux, Unix, Mac OS X 등의 OS에서는 Dynamic Library와의 Interop을 가능하게 해줍니다. Interop 할 수 있는 대상은 라이브러리 외부로 노출된 C 언어 스타일의 함수입니다. 하지만 C++은 C#에서 직접적으로 Interop을 성립시킬 수 없는데, 운영 체제와 컴파일러에 따라서 C++ 클래스를 참조하는 방식이 제각기 다르기 때문입니다.

    지금 소개하는 방법은 매우 간단하면서도 생각보다 잘 작동하는 것 같습니다. 바로 C++의 멤버 메서드와 멤버 변수, 생성자와 소멸자에 대한 프록시 함수를 작성하는 방법입니다.

    1. 시작하기에 앞서 간단한 C++ 클래스 정의하기

      // TestClass.h에 정의하는 내용입니다.

      class TestClass

      {

      public:

          void TestFunction(int value);

          int MyFunction(void);

      private:

          int m_myValue;

      public:

          int GetMyValue(void);

          void SetMyValue(int value);

      public:

          TestClass(int value);

      }

      // TestClass.cpp에 정의하는 내용입니다.

      #include <iostream>

      void TestClass::TestFunction(int value)

      {

          stdout << value;

      }

      int TestClass::MyFunction(void)

      {

          return 123;

      }

      int TestClass::GetMyValue(void)

      {

          return this->m_myValue;

      }

      void TestClass::SetMyValue(int value)

      {

          this->m_myValue = value;

      }

      TestClass::TestClass(int value)

      {

          SetMyValue(value);

      }


    2. 생성자와 소멸자에 대한 프록시 함수 만들기

    프록시 함수에서 생성자와 소멸자의 정의는 반드시 이루어져야 합니다. 모든 프록시 함수는 클래스의 인스턴스를 레퍼런스 형식으로 참조해야 하기 때문에 레퍼런스를 할당하고 제거하는 일을 하는 함수가 있어야합니다. C#에서는 이러한 일을 하기 위해서 복잡한 시스템 API (예를 들자면 malloc()과 free()와 유사한) 를 다시 참조해야 하므로 C++에서 대신 처리하는 것이 바람직합니다. 또한 생성자는 C++ 클래스를 바인딩하는 C# 클래스의 생성자에서 호출되어야 하며 소멸자는 System.IDisposable 인터페이스의 구현 메서드인 Dispose() 메서드 또는 C# 클래스의 소멸자에서 호출되어야 합니다.

    프록시 함수는 라이브러리 외부로 노출되어야 하는 함수입니다. Win32에서는 DLL로 제작해야 하며 이 때에는 .def 파일을 선언하여 컴파일러에게 전달하거나 Visual C++ 컴파일러를 사용한다면 extern "C" __declspec(dllexport) 키워드를 사용해도 됩니다.

    생성자 프록시 함수는 매개 변수로는 생성자의 매개 변수 그대로를 받습니다. 반환값은 포인터로 반환하면 됩니다. 만약 여러개의 생성자가 정의된 클래스라면 프록시 함수도 이름을 달리해서 여러개로 정의해주면 됩니다.

      TestClass* TestClass_ctor(int value) // Creator

      {

          return new TestClass(value);

      }

    소멸자 프록시 함수는 다른 매개 변수는 받을 필요 없이 포인터 매개 변수 하나만 받습니다. 단, 메모리 액세스 위반을 방지하기 위하여 받은 인스턴스가 이미 정리된 인스턴스인지 확인하는 단계를 거쳐서 delete 연산자로 소멸시키면 됩니다.

      void TestClass_dtor(TestClass* pInstance) // Destructor

      {

          if(pInstance != NULL)

               delete pInstance;

          pInstance = NULL;

      }

    추가 정보: Visual C++ 컴파일러에서 클래스에 대해 __declspec(dllexport)를 사용하거나 MFC의 확장 매크로를 사용해도 이 강좌와 비슷한 디자인의 프록시 함수를 생성합니다. 하지만 Visual C++에 종속적인 내용이므로 다른 컴파일러에서는 사용할 수 없는 방법입니다. 참고해 주십시오. 또한 위와 같은 방법으로 생성된 프록시 함수들은 컴파일러가 임의로 이름을 붙이기 때문에 알아보기도 어렵고 일관성있는 규칙을 만들기가 어렵습니다. 그리고 이러한 함수를 찾기 위해서는 Dependency Walker라는 Visual Studio의 부가 도구를 사용해야 합니다. (Visual Studio Command Line에서 depends.exe 실행)

    참고 자료: http://www.jprl.com/~jon/interop.html

    일단 여기까지 글을 올립니다. 다음 글에서 멤버 함수와 멤버 변수에 관한 프록시를 만드는 방법을 소개하도록 하겠습니다.

    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Posted by Windows Azure MVP 남정현 (rkttu.com)