在Windows下使用nmake+Makefile+编译ZThread库(附例子)
----------2015/01/09/23:21更新-----------------------------------
关于保留DEBUG信息的一个简单例子,见这篇随笔
----------2014/12/18/17:53更新-----------------------------------
前段时间写了这篇随笔,现在回过头来看感觉有很多问题,因此打算修正和补充一下,以前写的内容也没删,就留在这篇随笔的最下面了,可以对比着看看
目的:编写使用ZThread库的多线程程序
Windows: (Win7)
因为ZThread是支持Windows平台的,所以windows下,最好用visual studio的编译器,确保最好的兼容性
安装visual studio之后,通过开始>所有程序>Visual Studio 2013>Visual Studio Tools>VS2013 开发人员命令提示就可以使用CL、LINK、LIB、NMAKE这几个Windows下的编译和生成工具了。(相当于GNU的g++、ar、make)。
编译ZThread
从sourceforge.net下载ZThread-2.3.2.tar.gz,解压到F:/libs/ZThread-2.3.2
查看README>查看BUILDING
* Any other method is up to you. There are simply too many compilers,
too many versions and too many platforms to maintain separate build files
for.BUT, this doesn't mean you are out of luck.
I have structured the code so that it is very simple to compile the library
however suits your needs best. All you need to do is include the .cxx files
in the src/ directory (not the subdirectories) in your build rule, and add the
include directory to your compilers include/ path.
打开terminal,切换到目录F:/libs/ZThread-2.3.2/src
输入命令:CL /c /I..\include *.cxx
也就是编译所有的cxx文件,并生成对应的obj文件,但是不调用LINK对obj文件进行链接(比如somebody.cxx会被编译为somebody.obj)
等价于GNU的:g++ -c -I ../include *.cxx
然后输入命令:LIB /OUT:ZThread_win32.lib *.obj
也就是将所有的obj文件打包为lib文件
等价于GNU的:ar -r ZThread.a *.o
注意此处必须用CL编译的obj文件,并且由LIB打包成lib文件,如果是其他编译器编译的.a文件,你改后缀名改成.lib在windows下是用不了的,会报很多undefined reference to xxx错误。总而言之你坚持一个原则:用GNU的工具生成的库,那么引用这个库的代码就必须用GNU的编译器来编译,如果是用windows的工具生成的库,那么引用这个库的代码就必须用windows的编译器来编译!一般来说,如果一个库只使用C++ Standard里面的东西,就是平台无关的,比如ZThread就是这样的库,这样的库在几乎任何支持C++ Standard的平台上都是可以编译的,只不过,你选择了哪个平台,你最好就用哪个平台的编译器!
好,第二步,写代码,代码结构如下(加粗的就代表文件夹)
zthreaddemo
libs
zthread
ZThread_win32.lib
include
zthread
// zthread的所有头文件
LiftOff.h
Test.h
src
LiftOff.cpp
Test.cpp
main.cpp
Makefile
代码我就不贴了,只贴Makefile的,你可以下载项目文件夹来看看
Makefile
# See # http://msdn.microsoft.com/zh-cn/library/f35ctcxw.aspx # for more info about Microsoft Visual C++ Compiler and Linker options # Microsoft Visual C++ Compiler && Linker tool CC = cl # Microsoft Visual C++ Linker LINK = link # LIB_ZTHREAD is the ZThread static link library path # /LIBPATH:<dir> Specifies a path that the linker will search before it searches the path specified in the LIB environment option. If you want to specify more than one directory, you must specify multiple /LIBPATH options. # here we need the ZThread_win32.lib LIB_ZTHREAD = /LIBPATH:libs\zthread # tells the linker where to find object files OBJ_PATH = /LIBPATH:obj # '/I<PATH_NAME>' or '/I <PATH_NAME>' specifies a header search path # for example: CL /I \xxinclude /I\my\include main.c # tells the compiler where to find my own header files # *** This makes you free from the burden to write things like #include "../include/xxx" in your cpp files *** HEADER_PATH = /I include # See this page, search '/EHsc' # http://wenku.baidu.com/view/04a34101de80d4d8d15a4ff2.html EHSC = /EHsc # Compiles without linking. COMPILATION_ONLY = /c # Compiler output object file: /Fo<PATH_NAME>, for example: # cl /c hello.cpp /Foobj\hello.obj put the hello.obj file into the folder 'obj' C_OUT = /Fo: # Linker output executable file # note that the comma must be followed by the path WITHOUT any white-characters L_OUT = /OUT: bin\test.exe: bin obj obj\main.obj obj\Test.obj obj\LiftOff.obj $(LINK) $(LIB_ZTHREAD) $(OBJ_PATH) $(L_OUT)bin\test.exe main.obj Test.obj LiftOff.obj zthread_win32.lib obj\main.obj: src\main.cpp include\Test.h $(CC) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\main.cpp $(C_OUT)obj\main.obj obj\Test.obj: src\Test.cpp include\Test.h $(CC) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\Test.cpp $(C_OUT)obj\Test.obj obj\LiftOff.obj: src\LiftOff.cpp include\LiftOff.h $(CC) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\LiftOff.cpp $(C_OUT)obj\LiftOff.obj obj: mkdir obj bin: mkdir bin # PHONY means 'clean' is a fake target # use 'make clean' to remove all .obj files # before rebuilding # '-' means continue execute next command even if something goes wrong with this command # type 'help' to get info about 'rmdir' # type 'help rmdir' to get info about '/s' and '/q' .PHONY: clean clean: -rmdir /s /q bin -rmdir /s /q obj
这里面写了一些注释,看看可以帮助理解。总的思想就是:
先创建bin和obj文件夹,调用CL编译src里面的cpp文件,把生成的obj文件放到obj文件夹里,接着调用LINK链接所有的obj文件和lib文件,生成test.exe放到bin目录下.
要生成代码,所要做的工作就是打开VS2013 开发人员命令提示,然后切换到zthreaddemo目录中,输入命令nmake即可
里面有几个要点:
1、我将很多编译器和连接器的选项都写成了宏调用的形式,希望看起来可读性更好一些
2、注意文件的组织方式,src、include、libs、obj、bin这样的组织方式更整洁,src里面根据你的模块要求还可以设置子文件夹,include也是,libs里面根据你的需要也可以设置子文件夹,obj的组织结构一般与src是一一对应的(也就是说,如果你有src/xxx/yyy.cpp,那么生成obj文件的时候也应该有obj/xxx/yyy.obj)
3、类似于/OUT:这样的选项,冒号后面必须紧跟路径,中间不能有空格,而/I这个选项与路径之间,可以有空格也可以没空格
Linux: (Ubuntu)
编译ZThread,查看README, BUILDING
一般来说在类Unix环境下安装一个库的流程都是先./configure,再make,再make install。这样就可以
把头文件安装到/usr/local/include
把库文件(.a文件)安装到/usr/local/lib
把可执行文件安装到/usr/local/bin
但是我不知道是我的问题还是ZThread的configure代码的问题,./configure总是报错
所以我还是采取直接编译的方法,到/ZThread-2.3.2/src下运行命令g++ -I../include -fpermissive -c *.cxx
选项-fpermissive的作用是把一些error给改成warning.原因是ZThread的代码是比较陈旧的代码了,在目前的C++ Standard中过去的某些语法可能被编译器视为error,但是-fpermissive选项的意思就是告诉编译器:我确定这个代码格式没有语法或其他错误,只是比较陈旧不符合现代的标准而已。可以参考这个解释
选项-I../include是说zthread的头文件在上一级目录的include文件夹中
然后运行命令ar -r ZThread.a *.o
得到zthread.a
文件的组织方式仍然如前面所述
zthreaddemo
libs
zthread
ZThread.a
include
zthread
// zthread的所有头文件
LiftOff.h
Test.h
src
LiftOff.cpp
Test.cpp
main.cpp
Makefile
代码不变,只修改Makefile,如下所示(注意这里的Makefile与项目文件夹里的不太一样,以这里的为准)
# See # http://www.gnu.org/software/gcc # for more info about GNU C++ Compiler and Linker options # GNU C++ Compiler && Linker tool CC = g++ # GNU C++ Linker LINK = g++ # LIB_PATH tells the linker where to find library files # -L <path> Specifies a path that the linker will search before it searches the path specified in the LIB environment option. If you want to specify more than one directory, you must specify multiple -L options. # here we need the ZThread.a LIB_PATH = -Llibs/zthread # -l<LIBRARY_NAME> specifies a library file to link # note that to use the '-l' flag, you must name # your static library file libLIBRARY_NAME.a (the prefix 'lib' # cannot be omitted) and use '-Ldir' # before you actually using the '-l' flag, where 'dir' # is the directory that your libxxx.a can be found # immediately (can't be parent directory). # You might think that it's verbose to say '-Llibs/zthread -lZThread', # why not just say 'libs/zthread/ZThread.a' ? # Yes in this case it's not a good example. But what if # you put all your library files DIRECTLY in the folder 'libs'? # Suppose you put libraryA.a libraryB.a libraryC.a DIRECTLY # in the folder 'libs', you just need to specify '-Llibs' once, # then you just say '-llibraryA' '-llibraryB' '-libraryC' to link # all three library files. LIB_FILE = -l # '-I<PATH_NAME>' specifies a header search path # for example: g++ -I /xxinclude -I /my/include main.c # tells the compiler where to find my own header files # *** This makes you free from the burden to write things like #include "../include/xxx" in your cpp files *** HEADER_PATH = -Iinclude # Compiles without linking. COMPILATION_ONLY = -c # Compiler output object file: -o <PATH_NAME/object_file.o>, for example: # g++ -c hello.cpp -o obj/hello.o put the hello.o file into the folder 'obj' C_OUT = -o # Linker output executable file L_OUT = -o bin/test: bin obj obj/main.o obj/Test.o obj/LiftOff.o $(LINK) obj/main.o obj/Test.o obj/LiftOff.o $(LIB_PATH) -lZThread -lpthread $(L_OUT) bin/test obj/main.o: src/main.cpp include/Test.h $(CC) $(HEADER_PATH) $(COMPILATION_ONLY) src/main.cpp $(C_OUT) obj/main.o obj/Test.o: src/Test.cpp include/Test.h $(CC) $(HEADER_PATH) $(COMPILATION_ONLY) src/Test.cpp $(C_OUT) obj/Test.o obj/LiftOff.o: src/LiftOff.cpp include/LiftOff.h $(CC) $(HEADER_PATH) $(COMPILATION_ONLY) src/LiftOff.cpp $(C_OUT) obj/LiftOff.o obj: mkdir obj bin: mkdir bin # PHONY means 'clean' is a fake target # use 'make clean' to remove all .obj files # before rebuilding # '-' means continue execute next command even if something goes wrong with this command # type 'rm --help' to get info about '-r' and '-f' .PHONY: clean clean: -rm -r -f bin -rm -r -f obj
---------------------------------------
2014/12/18更新之前的内容(已陈旧,有部分误区。尽量别看这个)
用mingw32-make就行了,语法跟GNU make基本上是一样的,只是要针对windows写命令,比如linux下的rm指令(删除文件)在windows下需要换成del指令
为什么不用Cygwin?——老爱报些莫名其妙的错误。下面举个例子
下面用LIB_ZTHREAD代指Windows下的F:/libs/zthread_win32.a或者Ubuntu下的/home/admin/libs/zthread.a
用HEADER_ZTHREAD代指Windows下的F:/libs/ZThread-2.3.2/include或者Ubuntu下的/home/admin/libs/ZThread-2.3.2/include
点此下载zthread_win32.a
点此下载zthread.a
文件结构:所有的.cpp文件.h文件Makefile都在一个文件夹里,假设其目录为TEST_DIR
源代码:
main.cpp
1 #include "Test.h" 2 3 using namespace std; 4 5 int main() 6 { 7 Test::testLiftOff(); 8 return 0; 9 }
Test.h
1 #ifndef TEST_H 2 #define TEST_H 3 4 5 class Test 6 { 7 public: 8 static void testLiftOff(); 9 10 private: 11 Test(); 12 ~Test(); 13 }; 14 15 #endif // TEST_H
Test.cpp
1 #include "Test.h" 2 3 #include "LiftOff.h" 4 5 #include <zthread/Thread.h> 6 7 #include <iostream> // std::cout 8 9 void Test::testLiftOff() 10 { 11 using namespace ZThread; 12 13 try { 14 for (int i = 0; i < 5; ++i) 15 { 16 Thread th(new LiftOff(10, i)); 17 } 18 std::cout << "waiting for lift off" << std::endl; 19 } catch (Synchronization_Exception &e) { 20 std::cerr << e.what() << std::endl; 21 } 22 } 23 24 Test::Test() 25 { 26 //ctor 27 } 28 29 Test::~Test() 30 { 31 //dtor 32 }
LiftOff.h
1 #ifndef LIFTOFF_H 2 #define LIFTOFF_H 3 4 #include <zthread/Runnable.h> 5 6 class LiftOff : public ZThread::Runnable 7 { 8 public: 9 LiftOff(int countDown_, int id_); 10 ~LiftOff(); 11 void run(); 12 private: 13 int countDown; 14 int id; 15 }; 16 17 #endif // LIFTOFF_H
LiftOff.cpp
1 #include "LiftOff.h" 2 3 #include <iostream> 4 5 using namespace std; 6 7 LiftOff::LiftOff(int countDown_, int id_) 8 :countDown(countDown_), id(id_) 9 { 10 // do nothing 11 } 12 13 LiftOff::~LiftOff() 14 { 15 cout << "LiftOff" << id << " destroyed" << endl; 16 } 17 18 void LiftOff::run() 19 { 20 while (countDown--) 21 cout << id << " count: " << countDown << endl; 22 cout << id << "LiftOff!" << endl; 23 }
1. Ubuntu (linux) + GNU make
Makefile
1 # ZTHREAD_A the static link library file of ZThread 2 ZTHREAD_A = /home/admin/libs/zthread.a 3 # ZTHREAD_H is the directory that has all the header 4 # files of the ZThread library 5 ZTHREAD_H = /home/admin/libs/ZThread-2.3.2/include 6 7 test.exe: main.o Test.o LiftOff.o 8 g++ -o test.exe main.o Test.o LiftOff.o -s $(ZTHREAD_A) -lpthread # -lpthread is necessary to link pthread library, which is not part of the default library in Ubuntu, ZThread need pthread support 9 main.o: main.cpp 10 g++ -c main.cpp -o main.o 11 # '-I' specifies the header search directory 12 Test.o: Test.cpp Test.h 13 g++ -I $(ZTHREAD_H) -c Test.cpp -o Test.o 14 LiftOff.o: LiftOff.cpp LiftOff.h 15 g++ -I $(ZTHREAD_H) -c LiftOff.cpp -o LiftOff.o 16 17 # PHONY means 'clean' is a fake target 18 # use 'make clean' to remove all .o files 19 # before rebuilding 20 .PHONY: clean 21 clean: 22 -rm test # '-' means continue execute next command even if something goes wrong 23 -rm *.o
make clean
make -f Makefile
运行成功
2. Windows + mingw32-make
Makefile
1 # ZTHREAD_A the static link library file of ZThread 2 ZTHREAD_A = F:/libs/ZThread-2.3.2/lib/zthread_win32.a 3 # ZTHREAD_H is the directory that has all the header 4 # files of the ZThread library 5 ZTHREAD_H = F:/libs/ZThread-2.3.2/include 6 7 test.exe: main.o Test.o LiftOff.o 8 g++ -o test.exe main.o Test.o LiftOff.o -s $(ZTHREAD_A) 9 main.o: main.cpp 10 g++ -c main.cpp -o main.o 11 # '-I' specifies the header search directory 12 Test.o: Test.cpp Test.h 13 g++ -I $(ZTHREAD_H) -c Test.cpp -o Test.o 14 LiftOff.o: LiftOff.cpp LiftOff.h 15 g++ -I $(ZTHREAD_H) -c LiftOff.cpp -o LiftOff.o 16 17 # PHONY means 'clean' is a fake target 18 # use 'make clean' to remove all .o files 19 # before rebuilding 20 # '-' means continue execute next command even if something goes wrong with this command 21 .PHONY: clean 22 clean: 23 -del test.exe 24 -del *.o
mingw32-make clean
mingw32-make -f Makefile
运行成功
3. Windows + Cygwin
Makefile
1 # ZTHREAD_A the static link library file of ZThread 2 ZTHREAD_A = F:/libs/ZThread-2.3.2/lib/zthread_win32.a 3 # ZTHREAD_H is the directory that has all the header 4 # files of the ZThread library 5 ZTHREAD_H = F:/libs/ZThread-2.3.2/include 6 7 test.exe: main.o Test.o LiftOff.o 8 g++ -o test.exe main.o Test.o LiftOff.o -s $(ZTHREAD_A) 9 main.o: main.cpp 10 g++ -c main.cpp -o main.o 11 # '-I' specifies the header search directory 12 Test.o: Test.cpp Test.h 13 g++ -I $(ZTHREAD_H) -c Test.cpp -o Test.o 14 LiftOff.o: LiftOff.cpp LiftOff.h 15 g++ -I $(ZTHREAD_H) -c LiftOff.cpp -o LiftOff.o 16 17 # PHONY means 'clean' is a fake target 18 # use 'make clean' to remove all .o files 19 # before rebuilding 20 # '-' means continue execute next command even if something goes wrong with this command 21 .PHONY: clean 22 clean: 23 -rm test.exe 24 -rm *.o
make clean没问题
make报错(貌似是找不到__assert的实现,真心无语),报错的详细信息见这篇随笔