페이지

글타래

2009년 6월 11일 목요일

VC 에서 EXE크기 줄이면서 MFC90,MSVCRxx MSVCP DLL 포함 안하는 법



VC++ 프로젝트는 간단하지
않은 프로젝트들 거의 대부분이 SDK 가 아닌 MFC 프로젝트들로 이루어져 개발되고
있다.


그러나 개발할때는 편하게
쓰지만,막상 배포시 너무 큰 크기라든가 MFC DLL들을 함께 배포해야 하는등의
부수적인 문제가 수반된다.


물론 이렇게 이런 부분까지
민감하지 않는 사람들이 대부분이다.
(회사차원 프로젝트에서는 더더욱 이런 세심한
관심은 필요없다)


하지만,작은 유틸리티,SDK
(WIN32 API,이젠 뒤늦게 나마 O.S 시장에서 MS의 윈도우계열이 거의 꼴지로
64bit 대열에 합류했으므로 WIN32라는 말도 조만간 바뀌겠지)로는 짤수없고 다양한
공용컨트롤등을 사용하려면 MFC 클래스나 IE,OFFICE등의 컨트롤을 써야만 할것이다.


기업용은 대다수 기능과
안정성을 중시로 크게 오류가 나지않는 한은 제품의 형태나 크기등은 크게 문제되지
않는다.
하지만 PC에 설치되는 개인용 유틸리티 성격의 프로그램은 안정성은 물론
그 크기와 속도등의 최적화도 그 프로그램의 기능과 스케일에 맞게 성능을 판단하는
한가지 근거자료가 될것이다.


예를 들어,파일을 복사하고
이동하는 자동이름 변경마법사 등의 유틸리티가 200KB 정도내외이면 가능한 기능인데
무려 1-2MB 를 상회한다면 무엇인가 큰 낭비요소가 작용하고 있음을 나타낸다.


따라서 다음과 같은 고민에
빠져들수 있다.((물론 대부분 개발자가 안그러지만))


최신 컨트롤을 쓰기 위해서는
SDK API 로는 코딩의 한계와 어려움이 있다.따라서 분명 MFC나 ATL등 고급화된 함수나
클래스들을 써서 이쁘고 다양한 컨트롤이나 메인화면을 구현해야만 한다.


MFC를 쓰되 MFC DLL을 함께
배포하지 않는 방법은 없을까?
물론 '공유DLL에서 MFC 사용' 을 하면 MFC로 개발해도
상관없다.


단,VS6,VS2003,VS2005,VS2008등
무엇을 썼느냐에 따라 재배포 패키지를 함께 설치해야만 한다.
현재 시점에서
VS2008에서 개발했을 경우 무려 4MB가까이 되는 용량이 될수있다.(9.0.21022.8 버전은
MFC90.DLL이 약 1MB를 넘지만 9.0.30729.1 버전은 이미 4MB 에 가깝다) 그러나
내 본 프로그램의 크기는 작을 것이다. 그리고 프로그램이 하는 기능도 사실 몇줄
되지 않고 별거 아닐것인데 단지 MFC 프로젝트로 작성했다는 이유만으로 무려 배보다
배꼽이 더 크게 되버린 경우이다.


이럴경우, 내 프로그램을
더이상 '공유DLL에서 MFC 사용'으로 만들면 안되겠다. 그렇다면,SDK로 코딩을 바꿔야
하나? 이것은 전혀 불가능할지도 모를 쓸데없는 시간낭비 일 것이다 .. 물론 가능은
하겟지만,,, 불필요한...
예를 들어,CWnd 형으로 상속받아 작성한 내 커스텀 컨트롤(탭,리스트
및 다양한 것들) 의 소스코드를 모두 SDK에 맞도록 리펙토링 해야할것이다.


어려운방법인 셈이고 다른
최신형의 컨트롤을 쓸때마다 이런짓을 해야할지도 모른다.


MFC를 쓰면서 '공유DLL에서
MFC 사용' 을 하지않고 좀더 쉬운방법은 없을까??


우선,'정적 라이브러리에서
MFC 사용'을 선택하면 MFC 공용 DLL들이 필요하지 않게 된다. 하지만 Dependency
Tool 로 의존성 있는 DLL이 무엇인지 MFC,MSVCR90,MSVCP90(VS2008의 경우 90,그리고
VS2005는 71등,버전별로 달라진다.) 잘 봐야한다.


그림에서) 물론 UICtrlsd.dll
은 Debug모드이지만 (Debug모드에서는 이방법대로 빌드하지 않고 기존처럼 공유DLL
모드로 빌드한것이기 때문에) MFC DLL이 의존되어 있다.(뒤쪽에 나중에 빌드한 UICtrls.dll
그림과 비교해보자)





하지만,이또한
다소 문제가 있다. MFC 공유 DLL을 안쓰는대신에 내 코드크기가 그만큼 Statictics
되어 삽입되어 지므로 훨씬 더 커진다.


여러차례에 걸쳐 100% 까지
검증된 법은 아니지만 방금 전 데모 프로그램으로 확인완료했다. 즉 1차로 검증해본
결과 100% 실행되고 있음이 확인되었다.






그림은 이렇게 완전히 MFC 공유 DLL 사용하지 않도록 '정적
라이브러리에서 MFC 사용' 모드로 빌드한 결과이다.MFC 로
빌드한 것 치고는 일단은 크기들이 그렇게 많이 크지않다,EXE 크기는 현재 372KB
(381,440 바이트) 이므로 여기에 사용자 기능을 덕지덕지 붙인다면 아마 1MB 안팎의
안정된(?) 크기로 만들수 있을 것이다. UltraEdit 10.5 버전이 크기가 무려 9.06MB
(9,507,600 바이트) 가 넘는 엄청난 크기이다. 그다지 멀지 않은 예전 같으면 시스템
메모리카드 9개 크기를 훌쩍넘다니... 예전에는 어떻게 해서든 1KB 라도 줄여보려고
나온 기술들이 진일보해 주도했던 반면, 요새는 최적화된 기법없이 모두 하드웨어적
사양에만 의존하며 하드웨어적으로도 소프트웨어적으로도 너무 낭비하고 안일하게
방대해지는 듯한 현실이 씁쓸할 뿐이다.


먼훗날 누군가 최선형 우주선을
타고 우주여행을 갔다가 의문의 사고로 돌아오지 못했다.본부에서는 블랙박스를 회수해
원인을 분석한 결과,우주선 시스템에 문제가 발생해 관련 응용프로그램을 다시 설치하는
도중 너무 방대한 크기에 설치시간이 너무 지체되어 운석층을 미쳐 피하지 못했다
한다..


솔직히 윈도우즈 O.S 크기만
무려 2GB가 훌쩍넘는다. 그나마 리눅스는 CD한장에(670MB) 딱들어간다.
예전에는
디스켓 한두장에 다 집어넣으려고 총력을 기울였던 말그대로 마이크로 기술이 대세였지만
어느새,좀 잘살게 되고 넉넉한 탓인지 너무 흥정망청 자원을 쓰는게 아닌가 생각이
든다.


1)먼저 Panorama.exe 는
MFC 클래스를 이용한 실행파일 프로젝트이다.


2)UICtrls.dll 은 MFC 컨트롤
클래스를 커스텀화한 다양한 사용자 클래스들이 포함되어 있다.


3)Sknsys.dll 은 Win32 API
로만 구성된 순수한 SDK DLL 이다.


1)의 프로젝트 구성은 다음과
같다.


MFC 사용:정적
라이브러리에서 MFC 사용
런타임 라이브러리: 다중 스레드(/MT)


2)의 프로젝트 구성은 다음과
같다.


원래는,공유DLL에서
MFC 사용이며 Extension DLL로 생성한 프로젝트이다.


정적 라이브러리에서
MFC 사용
런타임 라이브러리: 다중 스레드(/MT)



3)의 프로젝트
구성은 다음과 같다.


원래는,표준
Windows 라이브러리 사용으로 생성한 프로젝트이다.


정적 라이브러리에서
MFC 사용
런타임 라이브러리: 다중 스레드(/MT)
추가종속성에
다음을 포함하였다.
(링크에러가 나면 찾아서 추가해줘야함)
Comctl32.lib
Gdi32.lib User32.lib Ole32.lib olepro32.lib



'정적 라이브러리에서 MFC
사용' 하면 1) EXE 크기가 커지므로 최대한
리소스(이미지,아이콘)를 파일로 Export해서
런타임시 읽도록 코드수정해야 한다.


또한,const 형식으로 소스내
선언한 고정값의 "스트링"도 크기에 한몫한다.


이를 없애서 별도의 리소스
DLL에 빼놓거나 INI등 환경설정파일로 Export해야 한다.


또한,2)번의 DLL은 내부적으로
MFC 클래스를 쓰고 있는데,이때 1)의 EXE 메인 프로그램만 '정적 라이브러리에서
MFC 사용'으로 변경해서 빌드했다고 해도 2)번의 UICtrls.dll을 로드하는 과정에서
MFC 공용 DLL을 찾게 된다.


따라서,여기서 2)번처럼
EXE 자신이 불러쓰는 DLL 파일의 프로젝트도 바꿔줘야 한다. 한데 ,위의 2)의 설정으로
아무리 설정을 바꿔서 컴파일해도 Dependency Tool 로 확인 해보면 MFC 계열 공유
DLL이 그대로 의존된 채로 변함없고 잘 바뀌지가 않을 것이다.


정적링크 = /MT, 공유링크
= /MD



이런경우에 대해 찾던중,꼬박
반나절만에,,,해결책을 발견!


이런경우,프로젝트의 소스코드를
약간 바꿔준다.


예를들어 UiCtrls.dll에
포함된 한가지 클래스중에는 다음과 같은것이있었다.



class AFX_EXT_CLASS CResizingDialog
: public CDialog


그리고 UICtrls.cpp 프로젝트
메인 cpp 파일 에는 다음과 같이 되어있을 것이다.


#include "stdafx.h"
#include <afxdllx.h >


static AFX_EXTENSION_MODULE
UIctrlsDLL = { NULL, NULL };



extern "C" int
APIENTRY


DllMain(HINSTANCE hInstance,
DWORD dwReason, LPVOID lpReserved)


{


//
lpReserved를 사용하는 경우 다음을 제거하십시오.


UNREFERENCED_PARAMETER(lpReserved);



if
(dwReason == DLL_PROCESS_ATTACH)


{


TRACE0("UIctrls.DLL
초기화!\n");



//
확장 DLL을 한 번만 초기화합니다.


if
(!AfxInitExtensionModule(UIctrlsDLL, hInstance))


return
0;



//
이 DLL을 리소스 체인에 삽입합니다.


//
참고: 이 확장 DLL이 MFC 응용 프로그램이


//
아닌 ActiveX 컨트롤 같은 MFC 기본 DLL에


//
의해 명시적으로 링크되어 있는 경우에는


//
DllMain에서 이 줄을 제거하고, 제거한 줄은 이 확장 DLL에서


//
내보낸 별도의 함수에 추가합니다.


//
그런 다음 이 확장 DLL을 사용하는 기본 DLL은


//
해당 함수를 명시적으로 호출하여 이 확장 DLL을


//
초기화해야 합니다. 그렇지 않으면, CDynLinkLibrary 개체가


//
확장 DLL의 리소스 체인에 추가되지 않으므로


//
심각한 문제가 발생합니다.



new
CDynLinkLibrary(UIctrlsDLL);



}


else
if (dwReason == DLL_PROCESS_DETACH)


{


TRACE0("UIctrls.DLL
종료!\n");



//
소멸자가 호출되기 전에 라이브러리를 종료합니다.


AfxTermExtensionModule(UIctrlsDLL);


}


return
1; // 확인


}


위를 각각 다음과 같이 수정한다.
#ifdef
UICTRLS_EXPORTS


#define
UICTRLS_API __declspec(dllexport)


#else

#define
UICTRLS_API __declspec(dllimport)


#endif


class UICTRLS_API CResizingDialog
: public CDialog


물론 프로젝트 전처리기에는
다음처럼 UICTRLS_EXPORTS 이 추가되어있어야한다.
또한 _AFXEXT가(_AFXDLL도)
있으면 지우고 _USRDLL을 선언해라.


WIN32;_WINDOWS;NDEBUG;_USRDLL;UICTRLS_EXPORTS


*_USRDLL 선언시 AfxGetResourceHandle(); 함수 도움말 내용을 꼭 확인하라.



DllMain은 중복에러가 날것이다.
그러므로 #if 0 ~~ #endif 로 주석처리하든가 소스에서 제거해라!


UICtrls.def 는 아직 수정하지
않아도 된다.



EXE 소스에서 CResizingDialog
클래스를 불러다 쓸수 있게 된다.


물론,두 프로젝트 모두 MFC
공유 DLL이 필요하지 않다.


크기가 상당히 작아졌다.



2)의 경우는 이미 MFC Extension
DLL 로 선언한 프로젝트를 정적으로 바꾸는 경우이고 3)의 경우는 SDK WIN API DLL
로 선언한 프로젝트를 나중에 MFC 가능하게 바꾸는 경우이다.



UICtrls.dll 의 stdafx.h
는 다음과 같다.


// stdafx.h : 자주 사용하지만
자주 변경되지는 않는


// 표준 시스템 포함 파일
및 프로젝트 관련 포함 파일이


// 들어 있는 포함 파일입니다.


#pragma once


#ifndef VC_EXTRALEAN

#define VC_EXTRALEAN //
거의 사용되지 않는 내용은 Windows 헤더에서 제외합니다.


#endif


// 아래 지정된 플랫폼에
우선하는 플랫폼을 대상으로 해야 한다면 다음 정의를 수정하십시오.


// 다른 플랫폼에 사용되는
해당 값의 최신 정보는 MSDN을 참조하십시오.


#ifndef WINVER //
Windows 95 및 Windows NT 4 이상에서만 기능을 사용할 수 있습니다.


#define WINVER 0x0400 //
이 값을 Windows 98 및 Windows 2000 이상을 대상으로 하는 데 적합한 값으로 변경하십시오.


#endif


#ifndef _WIN32_WINNT //
Windows NT 4 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_WINNT 0x0400 //
이 값을 Windows 2000 이상을 대상으로 하는 데 적합한 값으로 변경하십시오.


#endif


#ifndef _WIN32_WINDOWS //
Windows 98 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_WINDOWS
0x0410 // 이 값을 Windows Me 이상을 대상으로 하는 데 적합한 값으로 변경하십시오.


#endif


#ifndef _WIN32_IE //
IE 4.0 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_IE 0x0400 //
이 값을 IE 5.0 이상을 대상으로 하는 데 적합한 값으로 변경하십시오.


#endif


#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS //
일부 CString 생성자는 명시적으로 선언됩니다.



#include <afxwin.h >// MFC 핵심 및 표준 구성 요소입니다.

#include <afxext.h >// MFC 확장입니다.


#ifndef _AFX_NO_OLE_SUPPORT

#include <afxole.h >
// MFC OLE 클래스입니다.


#include <afxodlgs.h >
// MFC OLE 대화 상자 클래스입니다.


#include <afxdisp.h >
// MFC 자동화 클래스입니다.


#endif // _AFX_NO_OLE_SUPPORT


#ifndef _AFX_NO_DB_SUPPORT

#include <afxdb.h >//
MFC ODBC 데이터베이스 클래스입니다.


#endif // _AFX_NO_DB_SUPPORT


#ifndef _AFX_NO_DAO_SUPPORT

#include <afxdao.h >//
MFC DAO 데이터베이스 클래스입니다.


#endif // _AFX_NO_DAO_SUPPORT


#include <afxdtctl.h >//
Internet Explorer 4의 공용 컨트롤에 대한 MFC 지원입니다.


#ifndef _AFX_NO_AFXCMN_SUPPORT

#include <afxcmn.h >//
Windows 공용 컨트롤에 대한 MFC 지원입니다.


#endif // _AFX_NO_AFXCMN_SUPPORT


SknSys.DLL 의 Stdafx.h
는 다음과 같다.


// stdafx.h : 자주 사용하지만
자주 변경되지는 않는


// 표준 시스템 포함 파일
및 프로젝트 관련 포함 파일이


// 들어 있는 포함 파일입니다.


//



#pragma once


// 아래 지정된 플랫폼에
우선하는 플랫폼을 대상으로 하는 경우 다음 정의를 수정하십시오.


// 다른 플랫폼에 사용되는
해당 값의 최신 정보는 MSDN을 참조하십시오.


#ifndef WINVER //
Windows XP 이상에서만 기능을 사용할 수 있습니다.


#define WINVER 0x0501 //
다른 버전의 Windows에 맞도록 적합한 값으로 변경해 주십시오.


#endif


#ifndef _WIN32_WINNT //
Windows XP 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_WINNT 0x0501 //
다른 버전의 Windows에 맞도록 적합한 값으로 변경해 주십시오.


#endif


#ifndef _WIN32_WINDOWS //
Windows 98 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_WINDOWS
0x0410 // Windows Me 이상에 맞도록 적합한 값으로 변경해 주십시오.


#endif



#ifndef _WIN32_IE //
IE 6.0 이상에서만 기능을 사용할 수 있습니다.


#define _WIN32_IE 0x0600 //
다른 버전의 IE에 맞도록 적합한 값으로 변경해 주십시오.


#endif



#define WIN32_LEAN_AND_MEAN //
거의 사용되지 않는 내용은 Windows 헤더에서 제외합니다.


// Windows 헤더 파일:


#include <windows.h >

#include <windowsx.h >


// C RunTime Header Files

#include <stdlib.h >

#include <malloc.h >

#include <memory.h >

#include <tchar.h >

#include <commdlg.h >

#include <winuser.h >

#include <shellapi.h >

#include <shlobj.h >

#include <io.h >

#include <stdio.h >

#include <fcntl.h >

#include <sys>

#include <commctrl.h >


#define _CRT_SECURE_NO_WARNINGS


// Local Header Files

//#include "resource.h"


void LineLog(LPCSTR pszFormat,...);


// TODO: 프로그램에 필요한
추가 헤더는 여기에서 참조합니다.



서로 틀림을 알수있다.


Dependency Walker 로 의존성
검사한 화면







이 것은,필자 본인이 백업겸
올려놓은 개인적인 견해이므로 실험내용에 추가 검증이 있을때까지 오차가 있을수
있음.(예를 들어 CResizingDialog를 UICtrls.DLL을 EXE에서 불러오게끔 하기위해
변수 선언만 했을 뿐, 이것을 CDialog 형태로 DoModal() 등이나 Create() 등으로
직접 호출을 해보지는 않았다)


단,주의해야 할것은 HINSTANCE hInst = AfxGetResourceHandle();
AfxSetResourceHandle(hInst); 와 _USRDLL 전처리기 와의 상관관계를 반드시 찾아보라!DLL에서 이차이는 중요하다 DLL내부에서 핸들을 가져오지 못하는 경우가 발생할 수 있다.DLL내부에서 작동되는 자신의 코드가 무엇이냐에 따라 HINSTANCE hInst = GetModuleHandle(NULL); 을 해서라도 EXE의 핸들을 가져와야 할 지도 모른다! 단 이마저도 너무 크다고 생각이 되면 아주 줄이는 방법이 있다,즉 MSVC의 런타임 라이브러리 자체를 사용하지 않는 것이다.이에 대해 알려면 필자의 다른 글(
http://blog.hanafos.com/yeamaec/515 , http://blog.hanafos.com/yeamaec/518) 를 참조바란다.(코드프로젝트에서 libtc 검색)



*짜증나는 하나포스 블로그: 여기 편집기에서 저장만 했다하면 #include < ,> 문자(<,>으로 표현) 가 자꾸 사라진다. 개선하지 않으면 곧 이사가겠다.





댓글 없음:

댓글 쓰기