Tech/닷넷 일반2013. 10. 2. 18:10

응용프로그램을 배포하다보면 특정 조건 때문에 파일 하나로 만들어서 배포해야 할 경우가 있습니다. 사실 응용프로그램중에 EXE 파일 하나만으로 구성되는 경우가 드물지 않을까 싶은데, 지역화된 리소스만 집어넣어도 기본적으로 위성 어셈블리가 생성되기 때문이죠.


이번에 각종 DLL을 EXE에 임베드시키고 AssemblyResolve 이벤트를 사용해서 로드하는 방식을 사용해보았기에 정리해보려고 합니다.


실행시키면 아래와 같이 덩그라니 창이 뜨는 아주 단순한 WPF 응용프로그램입니다. (.NET 4 Client Profile 기반)


프로젝트 구조는 왼쪽 처럼 되어 있습니다. RESX 를 사용해서 문자열 리소스(여기서는 윈도우 제목 하나뿐이기 하지만...)를 지역화하고 있습니다. 리소스 어셈블리는 별도 프로젝트로 분리되어 있죠. 중립 언어는 en-US 로 AssemblyInfo.cs 에 설정이 되어 있고요.


기본적으로 이 프로젝트를 빌드해서 배포하려면 아래와 같은 파일들을 배포해야 합니다.

  • WpfSingSangSung.exe
  • WpfSingSangSung.Resources.dll
  • ko-KR/WpfSingSangSung.Resources.resources.dll
파일 하나로 만들어서 배포하려면 아래쪽 두 개의 리소스 DLL을 EXE 에 몰아넣어야 겠네요.


우선 빌드된 DLL 을 embed 시킬 것이므로, WpfSingSangSung 프로젝트 빌드 시에 자동으로 WpfSingSangSung.Resources 프로젝트의 빌드 결과물을 복사할 필요가 없습니다. 아래 그림과 같이 프로젝트 참조에서 Copy Local 을 False 로 바꿔줍니다.

빌드가 성공하려면 어셈블리 참조 자체는 유지해주어야 합니다.


다음 단계는 빌드된 리소스 DLL을 둘 곳을 정하는 건데요, 개발 중에 Debug 로 빌드했다가 Release 로 빌드했다가 할텐데, 그 때 빌드된 리소스 DLL 이 업데이트 되어야 하니까, 빌드된 리소스 DLL 들을 정해진 장소로 복사해주도록 하겠습니다.

프로젝트 폴더 구조는 위와 같습니다. WpfSingSangSung.Resources 프로젝트의 빌드 결과물을 bin/Result 폴더에 복사하도록 하죠. Post-build event 에 아래와 같이 설정해 줍니다.

xcopy "$(TargetPath)" "$(ProjectDir)bin\Result\" /Y
xcopy "$(TargetDir)ko-KR\$(ProjectName).resources.dll" "$(ProjectDir)bin\Result\ko-KR\" /Y

만약 다른 언어가 하나 더 추가된다면 ko-KR 과 동일한 패턴으로 복사하는 코드를 한 줄 넣어야겠죠.


이제 복사된 위치에 있는 리소스 DLL 들을 EXE 에 리소스로 임베드 시킵니다. 리소스로 임베드된 DLL 들만을 위해서 별도의 네임스페이스를 두는 것이 좋으니까, WpfSingSangSung 프로젝트에 폴더를 하나 추가하고, 리소스 DLL 들을 그냥 프로젝트에 추가하는 것이 아니라 Link 로 추가합니다.

바로 위 이미지는 DLL 을 추가하고, 리소스로 임베드시키는 작업이 다 된 상태입니다. 각 DLL 파일들의 Build Action 을 "Embedded Resource" 로 설정해준 것이 보이시죠? 그리고 또 하나 눈에 띄는 점은 ko-KR 폴더를 만들어서 한국어 리소스 DLL을 넣었다는 점이겠네요.


여기까지 해서 솔루션을 빌드하면, WpfSingSangSung.exe 에는 DLL들이 모두 리소스로 들어가 있는 상태입니다. 리소스 DLL을 찾아서 사용할 수 있게 해주는 기능이 없으니 실행은 아직 안되지만, ILSpy 로 열어보면 두 개의 리소스 DLL 이 리소스로서 포함되어 있는 것을 확인할 수가 있습니다.

이렇게 ILSpy 로 DLL의 리소스 이름을 확인해두면 뒤에 AssemblyResolve 이벤트 핸들러에서 어셈블리 로드하는 코드를 작성할 때 도움이 됩니다.


가장 중요한 내용으로 들어가기 전에, 기본적인 WPF 응용프로그램의 구조를 조금 바꿔서 코드 작성을 좀 쉽게 만들려고 합니다. 템플릿으로 WPF 응용프로그램을 생성하면 App.xaml 을 이용하는 형태로 만들어 주는데요, 이걸 지워버리고 Program.cs 를 만들어서 진입점으로 만들어주는거죠. 아래와 같이 Program.cs 를 만들어서 넣어줍니다.

    public class Program : Application
    {
        [STAThread]
        public static void Main(string[] args)
        {
            Program app = new Program();
            MainWindow mainWin = new MainWindow();
            app.Run(mainWin);
        }
    }


이제 AssemblyResolve 이벤트 핸들러만 만들어주면 끝납니다! 위에서 만들 Program.cs 에 코드 몇 줄만 추가하면 됩니다.

        public static void Main(string[] args)
        {
            // 이벤트 핸들러 연결
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

            Program app = new Program();
            MainWindow mainWin = new MainWindow();
            app.Run(mainWin);
        }

        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            AssemblyName missingAssemblyName = new AssemblyName(args.Name);
            CultureInfo ci = missingAssemblyName.CultureInfo;

            // ILSpy 에서 확인한 형태로 리소스 이름을 만들어줍니다.
            string prefix "WpfSingSangSung.Embedded.";
            string culturePart = "ci.Name.Replace("-", "_") + ".";
            string resourceName = prefix + culturePart + missingAssemblyName.Name + ".dll";
            // 중립 리소스 요청인 경우에는 Culture 이름이 비어있습니다.
            if (ci.Name == string.Empty)
                resourceName = prefix + missingAssemblyName.Name + ".dll";

            // 리소스에서 읽은 바이너리를 Assembly로 전환하여 리턴합니다.
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                byte[] bytes = new BinaryReader(stream).ReadBytes((int)stream.Length);
                return Assembly.Load(bytes);
            }
        }


이렇게 만들어서 빌드한 EXE 를 각각 영어 환경과 한국어 환경에 EXE만 복사해서 실행해보면 환경에 맞게 윈도우 제목이 영어와 한국어로 표시되는 것을 확인할 수 있습니다. 성공이네요!


Posted by wafe
Tech/WPF2011. 5. 3. 17:26

WPF에서 GDI를 이용하여 이미지를 생성할 수 있다.
Graphics객체를 통해, 도형이나 선, 텍스트, 이미지 등을 그릴 수 있다.

참고 (http://msdn.microsoft.com/ko-kr/library/system.drawing.graphics.aspx)

아래의 예제는 파일목록들을 얻어와서 1000*1000크기의 Bitmap객체에 이미지들을 추가하여 이미지를 생성하는 코드이다.

 private void Button_Click(object sender, RoutedEventArgs e)
         {
            Bitmap bitmap = new Bitmap(1000, 1000);
             Graphics g = Graphics.FromImage(bitmap);

            float x = 0;
            float y = 0;
            DateTime start = System.DateTime.Now;
            for(int i = 0; i< this.imageList.Count; i++)
             {
                try
                {
                    System.Drawing.Image image = System.Drawing.Image.FromFile(this.imageList[i]);

                    g.DrawImage(image, x, y, 100, 100);
                     x += 20;
                    y += 20;
                    bitmap.Save(System.IO.Path.Combine(this.outputFolderPath, "test" + i.ToString() + ".png"));
                 }
                catch (Exception)
                {
                    //파일 경로중 이미지가 아닌 파일의 경우 Image객체를 생성할 때, 예외가 발생한다.
                     continue;
                }
            }
            
            System.Diagnostics.Debug.WriteLine("elapsed :" + System.DateTime.Now.Subtract(start).TotalSeconds.ToString());
}

 

'Tech > WPF' 카테고리의 다른 글

WPF 화면에 보여지는 이미지 삭제하기  (1) 2010.10.29
[WPF 3D UI 구현하기 - 3]  (1) 2009.11.12
[WPF 3D UI 구현하기 - 2]  (0) 2009.11.12
[WPF 3D UI 구현하기 - 1]  (2) 2009.11.11
Posted by 알 수 없는 사용자
IT 이야기2010. 6. 21. 00:07

이 포스트는 TechDays 2010 Spring의 Expression Blend와 함께 WPF3D Workflow를 알아보자!라는 세션을 시청하고 요약한 것입니다.


사실 예전에 WPF 3D를 맛보기 정도로 경험해봤던 나는 좀 더 새로운 3D 관련 지식에 대해 필요성을 느끼고 있었다. 속도 문제라거나 좀 더 좋은 방법들에 대해서 궁금해했었다.

그런 의미에서 이 동영상은 새로운 방법들이 몇몇 보였다.

우선 동영상에 나온 자료 자체가 -_- 내가 만들었던 단순 패널 3D와는 차원이 달라! 뭐야 이거! 덜덜덜...
이 분은 MAX를 이용하여 3D를 만드시고, 그것을 저장한다.


MAX에서 저장한 3D파일을 ZAM 3D로 읽는다. 예전 세미나 때도 한 번 본 적 있었는데, ZAM 3D는 3D파일을 읽어들이거나 생성한 3D객체들을 XAML 형태로 변환해주는 멋진 프로그램이다. 사실 처음 봤을 때에는 "우왕 굳" 했었는데 말이지...
여하튼 3D파일을 불러오면 아래 사진처럼 MAX에서 작성한 3D화면이 그대로 보이게 된다. "뿅!"
ZAM 3D로 가져올 때 주의점이 있는데 3D파일과 3D파일을 구성하는 이미지 파일들이 한 폴더 내에 같이 있어야 잘 보인다고 한다.


이제 XAML 파일로 내보내야 하는데, Export할 때 몇 가지 옵션 사항들을 체크해주면 된다. Control Type에는 Viewport3D, ViewBox등의 컨트롤을 지정할 수 있으며, Resource로 내보낼건지, Template으로 내보낼건지, Inline으로 내보낼건지 선택할 수 있다.


이제 XAML파일로 내보내었으니, Blend로 이동하여 새 프로젝트를 만들고 생성한 XAML 파일을 열어보도록 하자.
파일을 가져오면 아래의 사진처럼 똑같이 나온다. (대단한데?)



또 놀라웠던 점은 나는 3D의 객체 구성 내용들을 블렌드에서 확인해보는 정도로 그쳤었는데, 이 분은 블렌드에서 직접 3D객체들을 움직여서 애니메이션도 거시더라... -_-); 왠지 코드로 열심히 애니메이션을 걸었던 내가 바보같이 느껴졌다. (원숭이가 된 느낌)
이 부분이 매력적으로 느껴졌던 이유는 내가 예전 3D를 할 때, 애니메이션이나 이동 같은 걸 구현하면서 실제 눈으로 보면서 확인할 수 있으면 얼마나 좋을까하는 불만이 있었기 때문이다.

음... 하지만 또 한 편으로는 이런 기능들도 ZAM 3D에서 잘 코드가 만들어져서 나오기 때문에 편하게 적용되는 방법일 수도 있겠단 생각이 들었다.

그리고 예전애 내가 사용자의 입력을 받기 위해 3D객체의 타입을 Visual2DViewport3D 객체를 사용했었는데, 이 분은 ModelViewport3D를 ModelUIElement3D로 객체 이름만 간단하게 바꿔서 구현해내시더라. (아놔 ㅋ 밥 아저씨를 보며 느꼈던 허무감이 밀려옴)

필요한 툴도 있고, 생각보다 쉽지 않을 수도 있지만! 블렌드를 이용한 정말 간단한 Workflow였다!
좋은 정보와 함께 블렌드를 통해 3D를 갖고 놀 수 있는 방법을 알 수 있어 더더욱 좋았다.

Posted by 알 수 없는 사용자