Как уменьшить размер приложения WTL?

Приложения WTL уже достаточно малы. Однако при использовании VS 2005 статически связанное приложение с WTL 9.10 весит 136 КБ (139 264 байта) для конфигурации Win32.

Глядя на исполняемый файл, я заметил статический импорт oleaut32.dll. Беглый взгляд с dumpbin показывает один импорт по порядковому номеру.

OLEAUT32.dll
            4181C0 Import Address Table
            41C9B8 Import Name Table
                 0 time date stamp
                 0 Index of first forwarder reference

                  Ordinal   277

При просмотре oleaut32.dll обнаруживается, что экспорт называется VarUI4FromStr.

Немного покопавшись с IDA, я обнаружил, что VarUI4FromStr используется ATL::CRegParser::AddValue. Вслед за иждивенцами оттуда показал два звонка в ATL::CRegParser::RegisterSubkeys.

Сопоставив код ATL моей установки Visual Studio с выводами, я обнаружил, что виновником был ATL::CAtlComModule. Он делает много вещей для регистрации TypeLib, которые просто не нужны для моего варианта использования.

Однако компоновщик, кажется, оставляет все это, потому что он не может разумно решить выбросить это.

Как мне избавиться от этого, казалось бы, лишнего импорта?


person 0xC0000022L    schedule 22.03.2016    source источник


Ответы (1)


Увы, поскольку WTL::CAppModule происходит непосредственно от ATL::CComModule, включение заголовка atlbase.h при наличии _ATL_NO_COMMODULE приводит к ошибке:

Error   1   fatal error C1189: #error :  WTL requires that _ATL_NO_COMMODULE is not defined $(ProjectDir)\wtl\Include\atlapp.h  33  

Однако настоящим виновником, который в конечном итоге втягивает ATL::CComModule, является ATL::CAtlComModule. Итак, наша цель — избавиться от них обоих.

Мы попробуем обманом заставить atlbase.h исключить весь регистрационный код TypeLib, определив _ATL_NO_COMMODULE в любом случае, но отменив его определение сразу после того, как завершим его включение. Таким образом, atlapp.h и другие заголовки WTL не будут "замечать".

#define _ATL_NO_COMMODULE
#include <atlbase.h>
#undef _ATL_NO_COMMODULE
#include <atlapp.h>

Очевидно, это доставляет нам некоторые проблемы:

1>atlapp.h(1515) : error C2039: 'CComModule' : is not a member of 'ATL'
1>atlapp.h(1515) : error C2504: 'CComModule' : base class undefined
1>atlapp.h(1524) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1543) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1625) : error C3861: 'GetModuleInstance': identifier not found
1>atlapp.h(1784) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1806) : error C2065: 'm_nLockCnt' : undeclared identifier

Тем не менее, похоже, мы сможем быстро исправить эту горстку ошибок.

Прежде всего, давайте проверим, откуда взялся ATL::CComModule: он объявлен и определен в atlbase.h. Мы, безусловно, можем объявить собственный класс и втиснуть его между #include <atlbase.h> и #include <atlapp.h> (см. выше).

Начнем с основ, исходя из того, что можно найти в atlbase.h:

namespace ATL
{
    class CComModule : public CAtlModuleT<CComModule>
    {
    public:
        CComModule() {}
    };
};

Теперь мы получаем другой набор ошибок:

1>atlapp.h(1524) : error C2039: 'Init' : is not a member of 'ATL::CComModule'
1>        stdafx.h(31) : see declaration of 'ATL::CComModule'
1>atlapp.h(1625) : error C3861: 'GetModuleInstance': identifier not found

Отлично, так что на самом деле нам не хватает еще двух функций класса: GetModuleInstance и Init. Притянем и их. На всякий случай мы также добавим GetResourceInstance:

namespace ATL
{
    class CComModule : public CAtlModuleT<CComModule>
    {
    public:
        CComModule() {}
        HINSTANCE GetModuleInstance() throw() { return _AtlBaseModule.m_hInst; }
        HINSTANCE GetResourceInstance() throw() { return _AtlBaseModule.m_hInstResource; }
        HRESULT Init(_ATL_OBJMAP_ENTRY*, HINSTANCE, const GUID*) throw() { return S_OK; }
    };
};

Короткий тест доказывает, что это работает нормально. И импорт oleaut32.dll ушел вместе с еще тремя импортами из ole32.dll. Огромный успех!

В результате мы сэкономили 12 КБ на размере исполняемого файла.

Знаете ли вы другие крутые приемы по уменьшению размера приложения WTL?

ВАЖНЫЙ

Обратите внимание, что в зависимости от функций ATL::CComModule, используемых в вашем коде, вам может придется вернуться к использованию оригинала. Единственная другая альтернатива — расширить приведенную выше фиктивную версию ATL::CComModule, чтобы исправить любые ошибки компиляции, с которыми вы можете столкнуться.

ПРЕДОСТЕРЕЖЕНИЕ

Я еще не тестировал это широко, и я сообщу (путем редактирования этого ответа), если у меня возникнут какие-либо проблемы. Однако, глядя на это как в коде ATL, так и в WTL, а также в базе данных IDA до моих изменений, я думаю, что это безопасное изменение.

Бонусный трюк

Вы также можете использовать автономный WDK 6001.18002 (для Vista SP1), который поддерживает Windows 2000 (SP4) и новее. Он включает ATL 7.0 и поэтому подходит для создания приложений ATL+WTL.

Для этого вам просто нужно добавить следующие две строки в ваш файл sources:

USE_STATIC_ATL=1
ATL_VER=70

Благодаря WDK, который использует msvcrt.dll, системную DLL, в качестве среды выполнения C по умолчанию (многопоточной и динамической компоновки), вы можете уменьшить размер исполняемого файла, динамически связав его со средой выполнения C. И в этом случае, поскольку эта конкретная среда выполнения C является системной DLL (начиная с Windows 2000), вы можете быть уверены, что она будет работать.

Принимая все это во внимание, вы можете создавать стандартные приложения Windows (графический интерфейс или консоль) и очень маленькие библиотеки DLL.

Вот пример файла sources, который вы можете использовать в качестве шаблона:

# Name of the program
TARGETNAME=progname
# Prefix used for the intermediate/output paths (e.g. objfre_w2k_x86)
TARGETPATH=obj
# A program, not a driver or DLL or static lib
TARGETTYPE=PROGRAM
# windows == GUI, console == Console, native == no subsystem ...
UMTYPE=windows
# Use Unicode ("wide char") instead of ANSI
UNICODE=1
# By default the WDK build treats warnings as errors - this turns it off
BUILD_ALLOW_ALL_WARNINGS=1
# Link dynamically to msvcrt.dll
USE_MSVCRT=1
# Don't link against any of the ATL DLLs
USE_STATIC_ATL=1
# In the 7600.16385.1 WDK you can use 70 as well, which translates to 71
ATL_VER=70
USE_NATIVE_EH=1
 # RTTI is required for some ATL/WTL features
USE_RTTI=1

# GUI programs require these entry points
!IF defined(UNICODE) && $(UNICODE)
UMENTRY=wwinmain
C_DEFINES=$(C_DEFINES) /DUNICODE /D_UNICODE
!ELSE
UMENTRY=winmain
C_DEFINES=$(C_DEFINES) /DMBCS /D_MBCS
!ENDIF

# Include folders
INCLUDES=$(DDK_INC_PATH);$(CRT_INC_PATH);$(SDK_INC_PATH);wtl\Include

# Libraries to link to, not just import libraries
TARGETLIBS=\
            $(SDK_LIB_PATH)\kernel32.lib \
            $(SDK_LIB_PATH)\user32.lib \
            $(SDK_LIB_PATH)\Comctl32.lib \

# Give source files (also resources and .mc) in the current or parent directory
SOURCES=...

Для этого также подходит автономный WDK 7600.16385.1. WDK, начиная с Windows 8 WDK, теперь требуют Visual C++, в который их можно интегрировать. Это также приводит к тому, что для двоичных файлов требуется соответствующая версия среды выполнения C вместо msvcrt.dll из предыдущих версий WDK.

person 0xC0000022L    schedule 22.03.2016