Intel C++ Compiler (ICC) 所編譯出來的執行檔,其速度總令人驚豔,且可以近乎無痛地與 Microsoft Visual C++ (MSVC) 的開發經驗整合,是升級開發工具的好選擇。然而,在 Windows 上,ICC 需要搭配隨 ICC 附上的 libmmd.dll,才能夠執行,再加上 ICC 和 MSVC 是可以隨時切換的,因此,在 build system 的設計上,必須要有些特別的設計,以便處理這種 IDE 不會處理到的事。
我們用的 MSVC 是 VC6,需要偵測目前使用的編譯器,是 MSVC 還是 ICC,如果是 ICC 的話,還要偵測是哪一個版本,然後,依據偵測到的版本,作對應的 post-build 的動作。例如,若是使用 ICC 的話,就將正確版本的 libmmd.dll,複製到目標目錄下。
以下假設 ICC 是安裝在 ICPP_COMPILER 這個目錄下,如 %ProgramFiles%\Intel\Compiler\C++.1。
裝了 ICC 後,compiler/linker 會被換成在 %ICPP_COMPILER%\..\..\ISELECT\bin\ 目錄下的 xicl6.exe 跟 xilink6.exe,這只是一個只是一個 front-end,ICC 會依據目前的設定,啟動對應的 compiler/linker 進行編譯或連結。
依據隨附 ICC 安裝的文件裡的「Intel® C++ Compiler Documentation » Building Applications » Building Applications with Microsoft Visual Studio 6.0 » Selecting the Intel® C++ Compiler Using makefile」這一篇文章:
... The Makefile Utility provides users with the ability to switch between the Intel C++ Compiler and the Microsoft Visual C++ Compiler without requiring changes to their makefiles. This utility modifies the registry, so exported makefiles only call cl.exe (CPP = cl).
這個所謂的 Makefile Utility 指的就是 %ICPP_COMPILER%\..\..\ISELECT\bin\pickcmd.exe,會修改 registry,依據給定的參數,設定要使用的編譯器。也就是說,這個 utility 就是 MSVC IDE 裡的 Tools » Intel® C++ Compiler Selection Tool 的 command line 版本。
故 ICC 實際上是依據 registry 來決定要使用哪個 compiler/linker 的。所以觀察 registry 可以發現 HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE 下面有這幾組 key:
HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6
Path64 REG_SZ
MSVC_binary_dir REG_SZ C:\Program Files\Microsoft Visual Studio\VC98\Bin
Compiler REG_SZ 91032
Use_Intel_Cxx REG_DWORD 0x1
Languages REG_DWORD 0x1
Compiler_List REG_SZ 91032
HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\90032
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\bin
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\Docs\Main_cls.chm
Compiler_Interface REG_DWORD 0x0
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\include
Languages REG_DWORD 0x1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\lib
Name REG_SZ 9.0
HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\91032
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\bin
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Docs\Main_cls.chm
Compiler_Interface REG_DWORD 0x0
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\include
Languages REG_DWORD 0x1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\lib
Name REG_SZ 9.1
HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\91064
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\bin
Name REG_SZ 9.1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\lib
Languages REG_DWORD 0x1
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\include
Compiler_Interface REG_DWORD 0x0
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Docs\Main_cls.chm
從這幾組 registry 的結構以及實驗的結果,我們可以看出,ICC 係依據 Use_Intel_Cxx 這一個 key,來決定要不要用 ICC,以及依據 Compiler 這一個 key,決定要用哪一個版本的 ICC。故,如果我們能夠在 command line 下查詢這些 registry key,我們就可以寫出 BATCH 檔,依據設定作對應的 post-build 動作。
我們可以使用 reg.exe 這一支程式[],在 command line 查詢 registry key,如下:
SHELL> REG QUERY "HKCU\Software\Intel\Intel Tools\Select Compiler\IDE" \
/v Use_Intel_Cxx
! REG.EXE VERSION 3.0
HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6
Use_Intel_Cxx REG_DWORD 0x1
好,既然 registry 裡的資訊抓得出來了,那剩下的就只剩下 BATCH 檔的程式設計技巧。我們會需要下列技術:
- 如 UNIX shell 的 backquote 一般,取得程式執行的結果,並存入變數
-
BATCH 並沒有如 UNIX shell 的 backquote 的功能,也就是另外執行程式,將程式的輸出,轉變成目前的 script 部份內容,進而存入變數。為了取得 registry 裡的值,放入變數以便作後續的處理,我們必須在 BATCH 裡,模擬出 backquote 的功能。
要達到 backquote 的效果,解法很簡單,但非常 tricky。假設我們要達到 set foo `bar` 的效果,我們可以這麼寫:
bar > bar.tmp
SET /p foo=<bar.tmp
在 SET 使用 /p 選項時,會改向使用者詢問,將得到的字串,設給 foo,而 x 後面的字串,則當作 prompt string 使用。因此,我們先執行 bar,將結果存在暫存檔 bar.tmp 裡,然後用 redirect 將 bar.tmp 的內容,導給 SET /p foo=,如此就可以將 bar 的執行結果,存在 foo 變數裡。
- 如 UNIX 的 grep 工具一般,在一堆內容裡,找到符合某 pattern 的那些行
-
因為使用 reg.exe 查詢 registry key 的時候,會額外印出許多不相干的內容,所以我們第一步就是要把顯示值的那一行抓出來。
Windows 的 cmd.exe 有個指令叫 FIND,可以做到如 grep 的效果,只是沒有 grep 那麼強大。假設我們要抓 Use_Intel_Cxx 的值,我們可以這麼下指令:
SHELL> SET ISELECT6=HKCU\Software\Intel\Intel Tools\Select Compiler\IDE\6
SHELL> REG QUERY "%ISELECT6%" /v Use_Intel_Cxx 2>>NUL | FIND "REG_DWORD"
Use_Intel_Cxx REG_DWORD 0x1
-
其中,裡面的 2>>NUL 負責把 stderr 的東西丟掉。
- 如 UNIX 的 cut 工具一般,依據位置,取出某行文字的部份內容
-
這個要用到 cmd.exe 變數展開的延伸功能[]。在 cmd.exe 裡打 HELP SET 我們可以得到下面的說明[]:
您也可以為擴充功能指定子字串。
%PATH:~10,5%
這將會擴充 PATH 環境變數,然後只使用擴充結果的第 11 個(位移 10)字元
後的 5 個字元如果長度未指定,將會預設為上次使用的變數值。如果數字(位
移或長度)是負數,使用的數字將會是環境變數的長度加上位移或指定長度。
假設含有 Use_Intel_Cxx 的值的那一整行,已經在 ICC_USE_INTEL_CXX 變數裡了,我們就可以用下面的指令,擷取出 0x1 的部份:
SET ICC_USE_INTEL_CXX=%ICC_USE_INTEL_CXX:~28%
很可惜的,Windows 下預設沒有如 sed、cut 或 awk 等強大的文字處理工具,而 cmd.exe 的變數延展功能又太過陽春,不能依靠如 regular pattern 的方式處理,因此這裡的數字 28 必須自行計算並寫死。還好,以目前的應用來說,使用固定的數字不會有任何的問題。
有了以上的技術,我們就可以全部組裝起來,達到我們想要的功能。最後完整的程式如下:
@ECHO OFF
SET PREFIX=..\FooProj
IF "%1"=="" GOTO USAGE
IF "%1"=="/?" GOTO USAGE
IF "%1"=="/h" GOTO USAGE
IF "%1"=="/help" GOTO USAGE
IF "%1"=="-h" GOTO USAGE
IF "%1"=="--help" GOTO USAGE
SET CONFIGURATION=%1
IF "%CONFIGURATION%"=="debug" GOTO CONFIGURATION_CHECK_OK
IF "%CONFIGURATION%"=="release" GOTO CONFIGURATION_CHECK_OK
:CONFIGURATION_CHECK_FAILED
ECHO ERROR: Bad [configuration].
GOTO USAGE
:CONFIGURATION_CHECK_OK
SET ISELECT6=HKCU\Software\Intel\Intel Tools\Select Compiler\IDE\6
SET TMP_FILE=tmp-backquote.txt
REM Determine whether ICC is used
REG QUERY "%ISELECT6%" /v Use_Intel_Cxx 2>>NUL | FIND "REG_DWORD" > %TMP_FILE%
SET /P ICC_USE_INTEL_CXX=<%TMP_FILE%
SET ICC_USE_INTEL_CXX=%ICC_USE_INTEL_CXX:~28%
IF %ICC_USE_INTEL_CXX%==0x1 GOTO POST_BUILD_ICC_BEGIN
IF %ICC_USE_INTEL_CXX%==0x0 GOTO POST_BUILD_MSVC_BEGIN
:POST_BUILD_ICC_BEGIN
ECHO Perform ICC post-build steps:
REM Get ICC version code, which is part of next reg key to query
REG QUERY "%ISELECT6%" /v Compiler 2>>NUL | FIND "REG_SZ" > %TMP_FILE%
SET /P ICC_VER_CODE=<%TMP_FILE%
SET ICC_VER_CODE=%ICC_VER_CODE:~20%
REG QUERY "%ISELECT6%\%ICC_VER_CODE%" /v bin 2>>NUL | FIND "REG_SZ" > %TMP_FILE%
SET /P ICC_BIN_PATH=<%TMP_FILE%
SET ICC_BIN_PATH=%ICC_BIN_PATH:~15%
IF "%CONFIGURATION%"=="debug" XCOPY "%ICC_BIN_PATH%\libmmdd.dll" %PREFIX%\bin /Y /F /D
IF "%CONFIGURATION%"=="release" XCOPY "%ICC_BIN_PATH%\libmmd.dll" %PREFIX%\bin /Y /F /D
:POST_BUILD_ICC_END
GOTO POST_BUILD_COMMON_BEGIN
:POST_BUILD_MSVC_BEGIN
ECHO Perform MSVC post-build steps:
:POST_BUILD_MSVC_END
GOTO POST_BUILD_COMMON_BEGIN
:POST_BUILD_COMMON_BEGIN
ECHO Perform common post-build steps:
XCOPY lib\libFoo\include\foo_core.h %PREFIX%\include /Y /F /D
:POST_BUILD_COMMON_END
GOTO QUIT
:USAGE
ECHO Usage: %0 [configuration]
ECHO ----
ECHO [configuration] could be Debug or Release.
GOTO QUIT
:QUIT
IF EXIST %TMP_FILE% DEL /Q %TMP_FILE%
EXIT /B
由於在 Release mode 我們需要的是 libmmd.dll,而在 Debug mode 裡需要的是 libmmdd.dll,所以這個 script 吃一個參數,可以是 debug 或 release (大小寫不拘),以決定要 XCOPY 哪一個 DLL。
參考資料: