AtomAlpaca
本文所写的与最终合并进 ncnn
仓库的代码有所出入,切后续可能进行更改,本文内容仅供参考,以实际代码为准。
笔者很菜,第一次做类似的工作,求轻点喷。
首先几个主要的难点:
32 位的 Windows XP 很常见,我们不得不考虑编译成 32 位程序
现代的某些 Windows 系统函数在 Windows XP 时代不存在
也有好消息:Windows XP 从系统内核层面上不支持 AVX 指令集,否则不知道又要多出多少个坑。
首先我们需要一个能编译至 32 位的版本。我们来到 mingw-w64 的
sourceforge,一路找到
oolchains targetting Win32/Personal Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z
这里的 threads
必须位 posix 而不是 win32,不然会无法使用
std::thread
。
为了方便找到特定的工具链我们不妨设置一个环境变量
MINGW32_ROOT_PATH
到这个工具链的根文件夹,然后在
toolchains
下创建一个 cmake 文件
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
if(DEFINED ENV{MINGW32_ROOT_PATH})
file(TO_CMAKE_PATH $ENV{MINGW32_ROOT_PATH} MINGW32_ROOT_PATH)
else()
message(FATAL_ERROR "MINGW32_ROOT_PATH env must be defined")
endif()
if(DEFINED ENV{OPENCV_MINGW_DIR})
file(TO_CMAKE_PATH $ENV{OPENCV_MINGW_DIR} OpenCV_DIR)
endif()
set(MINGW32_ROOT_PATH ${MINGW32_ROOT_PATH} CACHE STRING "root path to mingw toolchain")
set(CMAKE_C_COMPILER "${MINGW32_ROOT_PATH}/bin/i686-w64-mingw32-gcc.exe")
set(CMAKE_CXX_COMPILER "${MINGW32_ROOT_PATH}/bin/i686-w64-mingw32-g++.exe")
set(CMAKE_FIND_ROOT_PATH "${MINGW32_ROOT_PATH}/i686-w64-mingw32")
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()
对于 Windows XP,我们需要指定几个比较关键的编译参数:
-D_WIN32_WINNT=0x0501
,_WIN32_WINNT
指定了所使用的最晚 Windows SDK 版本,0x0501
是 Windows XP
的代码,这个参数能禁用所有 Windows XP 不支持的 Windows 提供的函数。Content。
-march=i686
指定编译的目标架构为
i686
(即 32 位平台)。
-static
尽量静态编译减少对第三方库的依赖
其次链接的时候要加上 -static -fopenmp
,否则会报找不到
libgomp-1.dll
。
其次是代码中几处需要调整的地方: platform.h.in 中的
Mutex
和 ConditionVariable
分别封装了
SRWLOCK
和 CONDITION_VARIABLE
,而这两个东西到
Windows 7
才出现。于是我们需要替代一下。直接替换成空实现不太道德,我们使用
CRITICAL_SECTION
和事件简单实现一下:
class NCNN_EXPORT Mutex
{
public:
Mutex() { InitializeCriticalSection(&cs); }
~Mutex() { DeleteCriticalSection(&cs); }
void lock() { EnterCriticalSection(&cs); }
void unlock() { LeaveCriticalSection(&cs); }
private:
friend class ConditionVariable;
CRITICAL_SECTION cs;
};
class NCNN_EXPORT ConditionVariable
{
public:
ConditionVariable() { event = CreateEvent(0, FALSE, FALSE, 0); }
~ConditionVariable() { CloseHandle(event); }
void wait(Mutex& mutex)
{
mutex.unlock();
WaitForSingleObject(event, INFINITE);
mutex.lock();
}
void broadcast() { SetEvent(event); }
void signal() { SetEvent(event); }
private:
HANDLE event;
};
再改一下宏的判断就行了,应该没什么问题。
test_cpu.cpp
里,原本写得是
#if _WIN32_WINNT >= _WIN32_WINNT_WIN7‘
,但是似乎没有用到
Windows XP 不支持的操作,直接改掉就好了。 3. 如果你还想用 vlukan
的话,glslang
里用到了 _itoa_s
和
_vsnprintf_s
,这些是 Windows XP
下没有的、后增加的安全版本的函数。我们注意到这里是否使用安全函数是一个宏
MINGW_HAS_SECURE_API
控制的,一般来讲这个宏又会被
_CRT_SECURE_NO_DEPRECATE
控制,但是不知道为什么我们这里的
MINGW_HAS_SECURE_API
被写死成了
1,我们可以稍微修改一下这里的判断,改成类似
#if !(defined(_WIN32_WINNT) && _WIN32_WINNT <= 0x0501) && (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API)
。感觉这个很
dirty,但是他工作了。
似乎也没改什么东西。
至此我们可以开始着手编译了。注意虽然 Windows XP 是不支持
avx
的但是我们选择的编译器支持,于是我们要手动把
-DNCNN_AVX
和 -DNCNN_AVX
设置为
OFF
。
cmake -DCMAKE_TOOLCHAIN_FILE="../toolchains/windows-xp-mingw.toolchain.cmake" -DCMAKE_BUILD_TYPE=debug -DNCNN_RUNTIME_CPU=OFF -DNCNN_BUILD_BENCHMARK=ON -DNCNN_BUILD_EXAMPLES=OFF -DNCNN_BUILD_TESTS=OFF -DNCNN_SIMPLEOCV=ON -DNCNN_AVX2=OFF -DNCNN_AVX=OFF -DNCNN_VULKAN=ON .. -G "MinGW Makefiles"
cmake --build . -j 4
这里直接使用了 -DNCNN_SIMPLEOCV
,就不用在 Windows XP
上配置 OpenCV
了,我们之后也会使用这个编译选项。
别吧。
笔者折腾了半天没折腾出来,决定放一放。折腾出来了再补。
其实不建议这么做。Clang 发行时默认不包含 C 运行时库,也不包含构建 Windows XP 应用程序所需的头文件和库,我们需要借用 Mingw 的 32 位库进行构建。因此其实直接用 mingw 构建更为方便。
官方明确了最后一个支持 Windows XP 的版本是 3.7.0,详见 Release Note。我们下载这个版本,安装并且添加环境变量。 (后记:后面证实了其实更晚一些的版本仍然能正常工作)
和 mingw 不同的几个点:
target
选项改成
–target=i686-pc-windows-gnu
添加 –sysroot=${MINGW32_ROOT_PATH}
强制使用 Mingw32
的库
由于不支持
__float128‘,需要加上 ‘-D__STRICT_ANSI__‘
由于这个版本下 openmp 实在无法正常工作,不得不使用 SAMPLEOMP,将
-fopenmp
移除。
这个版本的 clang 不支持
const T t;
这种写法,必须提供一个构造器如
const T t = ;
。这部分问题出在
src/layer/binaryop.cpp
和
src/layer/x86/binaryop_x86.cpp
两个文件中,把这里的
const Op op;
改成
const Op op = ;
。虽然这样做会多出两条指令,但是开启优化之后能够优化掉。你可以试试
上手玩玩。
之后就可以正常编译了:
“‘bash
cmake -DCMAKE_TOOLCHAIN_FILE="../toolchains/windows-xp-clang.toolchain.cmake" -DCMAKE_BUILD_TYPE=debug -DNCNN_RUNTIME_CPU=OFF -DNCNN_BUILD_BENCHMARK=ON -DNCNN_BUILD_EXAMPLES=ON -DNCNN_BUILD_TESTS=ON -DNCNN_SIMPLEOCV=ON -DNCNN_SIMPLEOMP=ON -DNCNN_SSE2=OFF -DNCNN_AVX2=OFF -DNCNN_AVX=OFF .. -G "MinGW Makefiles"
cmake --build . -j 4
编译出的 example 和 benchmark 应当都能在 Windows XP 上正常运行了。
官方给出的最后一个支持 Windows 开发的版本时 VS2017,详情点击即看。此外我们还需要额外安装
v141_xp
工具集才能正常构建。
几个要注意的点:
MSVC 传入参数的方式不同,应该使用
/D_WIN32_WINNT=0x0501
这种形式;
一个可有可无的点:MSVC 会觉得你没有正常处理异常,于是会报很多
warning。我们用 /EHsc
钦定异常只在 throw
语句或函数调用处发生他就安静了。
需要静态链接运行时库,可以直接传一个 /MT
(或者 debug
版本 MTd
),或者 NCNN 也提供了
DNCNN_BUILD_WITH_STATIC_CRT
cmake 选项。
要用 -A WIN32
指定生成平台
然后就能正常编译了:
mkdir build
cd build
cmake -A WIN32 -G "Visual Studio 15 2017" -T v141_xp -DNCNN_SIMPLEOCV=ON -DNCNN_OPENMP=OFF -DNCNN_AVX2=OFF -DNCNN_AVX=OFF -DNCNN_BUILD_WITH_STATIC_CRT=ON -DCMAKE_TOOLCHAIN_FILE="../toolchains/windows-xp-msvc.toolchain.cmake" ..
cmake --build . --config Release -j 2
cmake --build . --config Release --target install
速度比 mingw 快多了。