다음을 통해 공유


How to embed manifest inside C/C++ binary using makefiles

Here is an unofficial preview of sample that is going to be available soon on MSDN. As always, standard disclosure that this post is provided "AS IS" with no warranties, and confer no rights and use of this sample is subject to the terms specified at https://www.microsoft.com/info/cpyright.htm

It is recommended for C/C++ applications to have manifest embedded inside the final binary because this ensures proper runtime behavior in most of scenarios. Visual Studio by default tries to embed manifest when building project from source files, see my post on how manifest is generated in visual studio for more details. However if an application is built using nmake build utility, some changes to existing makefile are necessary. In this section it is demonstrated how to change existing makefiles to automatically embed manifest inside final binary.

First let’s take a look on MyApplication.exe, which is a simple application built from one file using a nmake script as the following:

 !if "$(DEBUG)" == "1"
 CPPFLAGS=$(CPPFLAGS) /MDd
 LFLAGS=$(LFLAGS) /INCREMENTAL
 !else
 CPPFLAGS=$(CPPFLAGS) /MD
 !endif
  
 MyApplication.exe : MyApplication.obj
           link $** /out:$@ $(LFLAGS)
  
 MyApplication.obj : MyApplication.cpp
  
 clean : 
           del MyApplication.obj MyApplication.exe

If this script is run unchanged with Visual C++ 2005, it successfully creates MyApplication.exe and external manifest MyApplication.exe.manifest that is used by operation system to load dependent assemblies at runtime.

However it may be required to embed manifest inside MyApplication.exe for some reasons. There are several ways to achieve this. A simple change to make is to embed external manifest generated by linker as a resources inside the final binary. This can be achieved by making changes to nmake script file. First

 # Step 1. Adding _VC_MANIFEST_INC to specify which build is incremental (1 - incremental)
 # Step 2. Adding _VC_MANIFEST_BASENAME used to specify name of a temporary resource file
 !if "$(DEBUG)" == "1"
 CPPFLAGS=$(CPPFLAGS) /MDd
 LFLAGS=$(LFLAGS) /INCREMENTAL
 _VC_MANIFEST_INC=1
 _VC_MANIFEST_BASENAME=__VC80.Debug
  
 !else
 CPPFLAGS=$(CPPFLAGS) /MD
 _VC_MANIFEST_INC=0
 _VC_MANIFEST_BASENAME=__VC80
  
 !endif
  
 #Step 3. Specifying name of temporary resource file used only in incremental builds
  
 !if "$(_VC_MANIFEST_INC)" == "1"
 _VC_MANIFEST_AUTO_RES=$(_VC_MANIFEST_BASENAME).auto.res
 !else
 _VC_MANIFEST_AUTO_RES=
 !endif
  
 #Step 4. Adding _VC_MANIFEST_EMBED_EXE - command to embedd manifest to EXE
  
 !if "$(_VC_MANIFEST_INC)" == "1"
  
 #MT_SPECIAL_RETURN=1090650113
 #MT_SPECIAL_SWITCH=-notify_resource_update
 MT_SPECIAL_RETURN=0
 MT_SPECIAL_SWITCH=
 _VC_MANIFEST_EMBED_EXE= \
           if exist $@.manifest mt.exe -manifest $@.manifest -out:$(_VC_MANIFEST_BASENAME).auto.manifest $(MT_SPECIAL_SWITCH) & \
           if "%ERRORLEVEL%" == "$(MT_SPECIAL_RETURN)" \
           rc /r $(_VC_MANIFEST_BASENAME).auto.rc & \
           link $** /out:$@ $(LFLAGS)
           
 !else
  
 _VC_MANIFEST_EMBED_EXE= \
           if exist $@.manifest mt.exe -manifest $@.manifest -outputresource:$@;1
  
 !endif
  
 # Step 5. Changing command used to build EXE
 MyApplication.exe : MyApplication.obj $(_VC_MANIFEST_AUTO_RES)
           link $** /out:$@ $(LFLAGS)
           $(_VC_MANIFEST_EMBED_EXE)
           
  
 MyApplication.obj : MyApplication.cpp
  
 # Step 6. Adding commands to generate initial empty manifest file, RC file
 #         that references it and for generating the .res file
  
 $(_VC_MANIFEST_BASENAME).auto.res : $(_VC_MANIFEST_BASENAME).auto.rc
  
 $(_VC_MANIFEST_BASENAME).auto.rc : $(_VC_MANIFEST_BASENAME).auto.manifest
           type <<$@
 #include <winuser.h>
 1         RT_MANIFEST        "$(_VC_MANIFEST_BASENAME).auto.manifest"
 << KEEP
           
 $(_VC_MANIFEST_BASENAME).auto.manifest :
           type <<$@
 <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
 <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
 </assembly>
 << KEEP
  
 # Step 7. Adding _VC_MANIFEST_CLEAN - command to clean generate files 
 #         for temporary resources
 !if "$(_VC_MANIFEST_INC)" == "1"
  
 _VC_MANIFEST_CLEAN=-del $(_VC_MANIFEST_BASENAME).auto.res \
     $(_VC_MANIFEST_BASENAME).auto.rc \
     $(_VC_MANIFEST_BASENAME).auto.manifest
  
 !else
  
 _VC_MANIFEST_CLEAN=
  
 !endif
  
 clean : 
           del MyApplication.obj MyApplication.exe MyApplication.exe.manifest
           $(_VC_MANIFEST_CLEAN)
           

Similarly changes to nmake scripts required in case of an library MyLibrary.dll. If it was built using the following script:

 !if "$(DEBUG)" == "1"
 CPPFLAGS=$(CPPFLAGS) /MDd
 LFLAGS=$(LFLAGS) /DLL /INCREMENTAL
  
 !else
 CPPFLAGS=$(CPPFLAGS) /MD
 LFLAGS=$(LFLAGS) /DLL
  
 !endif
  
 MyLibrary.dll : MyLibrary.obj
           link $** /out:$@ $(LFLAGS)
  
 MyLibrary.obj : MyLibrary.cpp
  
 clean : 
           del MyLibrary.obj MyLibrary.dll

In this case, build script has to change in a similar way to EXE with only one exception. Manifest has to embedded as resource with ID equal to 2, instead of 1 as for EXE.

 # Step 1. Adding _VC_MANIFEST_INC to specify which build is incremental (1 - incremental)
 # Step 2. Adding _VC_MANIFEST_BASENAME used to specify name of a temporary resource file
 !if "$(DEBUG)" == "1"
 CPPFLAGS=$(CPPFLAGS) /MDd
 LFLAGS=$(LFLAGS) /DLL /INCREMENTAL
 _VC_MANIFEST_INC=1
 _VC_MANIFEST_BASENAME=__VC80.Debug
  
 !else
 CPPFLAGS=$(CPPFLAGS) /MD
 LFLAGS=$(LFLAGS) /DLL
 _VC_MANIFEST_INC=0
 _VC_MANIFEST_BASENAME=__VC80
  
 !endif
  
 #Step 3. Specifying name of temporary resource file used only in incremental builds
  
 !if "$(_VC_MANIFEST_INC)" == "1"
 _VC_MANIFEST_AUTO_RES=$(_VC_MANIFEST_BASENAME).auto.res
 !else
 _VC_MANIFEST_AUTO_RES=
 !endif
  
 #Step 4. Adding _VC_MANIFEST_EMBED_DLL - command to embedd manifest into DLL
  
 !if "$(_VC_MANIFEST_INC)" == "1"
  
 MT_SPECIAL_RETURN=1090650113
 MT_SPECIAL_SWITCH=-notify_update
  
  
 _VC_MANIFEST_EMBED_DLL= \
           if exist $@.manifest mt.exe -manifest $@.manifest -out:$(_VC_MANIFEST_BASENAME).auto.manifest $(MT_SPECIAL_SWITCH) & \
           if "%ERRORLEVEL%" == "$(MT_SPECIAL_RETURN)" \
           rc /r $(_VC_MANIFEST_BASENAME).auto.rc & \
           link $** /out:$@ $(LFLAGS)
           
 !else
  
 _VC_MANIFEST_EMBED_DLL= \
           if exist $@.manifest mt.exe -manifest $@.manifest -outputresource:$@;2
  
 !endif
  
 # Step 5. Changing command used to build DLL
 MyLibrary.dll : MyLibrary.obj $(_VC_MANIFEST_AUTO_RES)
           link $** /out:$@ $(LFLAGS)
           $(_VC_MANIFEST_EMBED_DLL)
           
  
 MyLibrary.obj : MyLibrary.cpp
  
 # Step 6. Adding commands to generate initial empty manifest file, RC file that references it
 #         and for generating the .res file
 # Note:  RT_MANIFEST has ID 2, because this is DLL.
  
 $(_VC_MANIFEST_BASENAME).auto.res : $(_VC_MANIFEST_BASENAME).auto.rc
  
 $(_VC_MANIFEST_BASENAME).auto.rc : $(_VC_MANIFEST_BASENAME).auto.manifest
           type <<$@
 #include <winuser.h>
 2         RT_MANIFEST        "$(_VC_MANIFEST_BASENAME).auto.manifest"
 << KEEP
           
 $(_VC_MANIFEST_BASENAME).auto.manifest :
           type <<$@
 <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
 <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
 </assembly>
 << KEEP
  
 #Step 7. Adding _VC_MANIFEST_CLEAN - command to clean generate files for temporary resources
 !if "$(_VC_MANIFEST_INC)" == "1"
  
 _VC_MANIFEST_CLEAN=-del $(_VC_MANIFEST_BASENAME).auto.res \
     $(_VC_MANIFEST_BASENAME).auto.rc \
     $(_VC_MANIFEST_BASENAME).auto.manifest
  
 !else
  
 _VC_MANIFEST_CLEAN=
  
 !endif
  
 clean : 
           del MyLibrary.obj MyLibrary.dll MyLibrary.dll.manifest
           $(_VC_MANIFEST_CLEAN)

Try your new makefile. This should work for you. If it does not, please let me know ASAP. I will try fixing it for you.

 

Have fun,
Nikola

Comments