用Boost.Python + CMake + wxPython構建跨語言GUI程序<一>
單純用C/C++做UI程序的盛況已經成為過去時,如果要做跨平臺的UI程序,那么可供選擇的更是為數不多。如果不考慮跨平臺的因素,那么Windows上邊,c#絕對是最佳的選擇,因為微軟的.NET投資大部分在這個上頭,而且有WPF的支持,至少比Java的Swing/SWT/FlashFX要高效和漂亮很多。 如果想跨Posix系統和Windows這兩個平臺,不嫌麻煩的話可以選擇Java (MONO的法律協議還是個問題);但是動態語言的特性決定了它來做界面更有優勢,譬如函數可以隨便做參數傳遞,變量類型可以隨便變換等。
最近在關注這個方面的東東,恰好就發現了和自己的經驗、興趣很相合的一個選擇:wxWidgets + Python = wxPython.這個項目底層部分是用c++來實現的,基本架構據說和MFC很像,我的那一點可憐的UI經驗都來源于MFC,因此必要的時候查下代碼應該沒大的難度。上層的部分有很多語言的bingding,最成熟的部分則是python綁定的wxPython. 找到了切合點,另外一個問題又隨之而來,UI畢竟只是負責來做用戶交互的,底層的邏輯(網絡、業務)大多還是要c++來寫,或者已經有的C++代碼最好能少改動就可以重復利用。
突然靈機一動想起來,Boost庫里邊剛還有這么一個東東來處理跨c++ 和 Python 的,可以導出或者內嵌, 于是翻出來體驗一番:
- 編譯Boost.Python
不經意發現Boost已經有了1.39版本了,我機器上的還是1.37,向來樂于追新的我對于這種自我學習的東西更是不會吝惜時間了,馬上將其7z包拖了下來,居然驚奇的發現里邊已經有了我很喜歡的CMakeLists.txt文件了,原來可以用CMake編譯,boost里邊我最討厭的就是那個bjam了,學了幾次都沒有完全琢磨明白,這次就下定決心也要把CMake版本的編出來。
這里需要編譯的是libboost_python.so,其它的東西,出于更新的目的,還是把常用的幾個也編一下好了。
7z x boost_1_39_0.7z cd boost_1_39_0/ mkdir mybuild cd mybuild cmake ../
居然報錯了:
-- ########################################################################## -- -- Only Boost.Build is officially supported. -- -- This is not Boost.Build. -- -- This is an alternate, cmake-based build system that is currently under development. -- To try it out, invoke CMake with the argument -- -DCMAKE_IS_EXPERIMENTAL=YES_I_KNOW -- Or use the gui to set the variable CMAKE_IS_EXPERIMENTAL to some value. -- This will only be necessary the first time. -- -- For more information on boost-cmake see the wiki: -- https://svn.boost.org/trac/boost/wiki/CMake -- -- Subscribe to the mailing list: -- http://lists.boost.org/mailman/listinfo.cgi/boost-cmake -- -- NOTE: Please ask questions about this build system on the boost-cmake list, -- not on other boost lists. -- -- And/or check the archives: -- http://news.gmane.org/gmane.comp.lib.boost.cmake -- -- ##########################################################################
還好問題不大,頂多加上那個選項就是了,有問題自己搞定:
cmake -DCMAKE_IS_EXPERIMENTAL=YES_I_KNOW ../
接下來OK了,看看那些選項可以改動,于是ccmake . , 去掉不需要的variants,將動態庫標上版本號,就是這么幾個:
BUILD_DEBUG OFF BUILD_STATIC OFF BUILD_VERSIONED ON
然后,c/g重新生成cache就可以了。接下來是make,不過照例看一下有哪些可以編, make help之后好大的一堆,這里就選需要的吧:
make help | grep boost ... boost_date_time ... boost_date_time-mt-shared ... boost_regex ... boost_regex-mt-shared ... boost_serialization ... boost_serialization-mt-shared ... boost_wserialization ... boost_wserialization-mt-shared ... boost_graph ... boost_graph-mt-shared ... boost_python ... boost_python-mt-shared ... boost_system ... boost_system-mt-shared ... boost_prg_exec_monitor ... boost_prg_exec_monitor-mt-shared ... boost_test_exec_monitor ... boost_unit_test_framework ... boost_unit_test_framework-mt-shared ... boost_filesystem ... boost_filesystem-mt-shared ... boost_iostreams ... boost_iostreams-mt-shared ... boost_program_options ... boost_program_options-mt-shared ... boost_signals ... boost_signals-mt-shared ... boost_thread ... boost_thread-mt-shared ... boost_wave ... boost_wave-mt-shared
需要的就是這么幾個了:
make boost_date_time boost_regex boost_python boost_system boost_unit_test_framework boost_filesystem boost_thread boost_signals
編譯的過程中,發現有些不對,因為有:
Linking CXX shared library ../../../lib/libboost_date_time-gcc44-mt-1_38.so
不是1.39嗎,怎么版本號不對?趕緊查一下 ../CMakeLists.txt,發現:
grep BOOST_VERSION ../CMakeLists.txt set(BOOST_VERSION_MAJOR 1) set(BOOST_VERSION_MINOR 38) set(BOOST_VERSION_SUBMINOR 0) set(BOOST_VERSION "${BOOST_VERSION_MAJOR}.${BOOST_VERSION_MINOR}.${BOOST_VERSION_SUBMINOR}") if(BOOST_VERSION_SUBMINOR GREATER 0) "include/boost-${BOOST_VERSION_MAJOR}_${BOOST_VERSION_MINOR}_${BOOST_VERSION_SUBMINOR}") else(BOOST_VERSION_SUBMINOR GREATER 0) "include/boost-${BOOST_VERSION_MAJOR}_${BOOST_VERSION_MINOR}") endif(BOOST_VERSION_SUBMINOR GREATER 0) # install(EXPORT boost-targets DESTINATION "lib/Boost${BOOST_VERSION}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Boost ${BOOST_VERSION}") set(CPACK_PACKAGE_VERSION "${BOOST_VERSION}") set(CPACK_PACKAGE_VERSION_MAJOR "${BOOST_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${BOOST_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${BOOST_VERSION_SUBMINOR}") set(CPACK_NSIS_DISPLAY_NAME "Boost ${BOOST_VERSION_MAJOR}.${BOOST_VERSION_MINOR}.${BOOST_VERSION_SUBMINOR}") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-vc6") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-vc7") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-vc71") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-vc8") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-vc9") set(CPACK_PACKAGE_FILE_NAME "Boost-${BOOST_VERSION}-borland") "http://www.osl.iu.edu/~dgregor/Boost-CMake/${BOOST_VERSION}/"
將那個38改正成為39就可以了。重復make,生成的庫全在lib目錄下了。
編譯完了之后,才發現make install并不能將這些安裝到系統里邊去,因為它有開始編譯沒有選擇的庫了,真浪費時間,也許這個是它們要堅持其為Experimental Support的緣故吧,而且accumulators庫的編譯還有個問題導致編譯不通過。
到這里只能再用bjam投機一下,讓它的install命令把頭文件拷貝過去,然后自己手工拷貝庫文件了:
cd ../ ./bootstrap ./bjam --with-system --libdir=/usr/local/lib threading=multi variant=release link=shared runtime-link=shared toolset=gcc install
這里之所以指定libdir是因為默認bjam會將庫文件安裝到/lib/下邊,不知道是不是個bug。好處是只需要指定一個庫的編譯,bjam就可以在install的時間將所有的頭文件準備就緒了。
查看一下:
ls -l /usr/local/include/boost-1_39/boost/python
文件已經在了,再次回到cmake的臨時目錄:
cd mybuild/lib su xxxxx cp libboost* /usr/local/lib/
ls -lh /usr/local/lib/libboost_python-gcc* -rwxr-xr-x 1 root root 5.2M 2009-08-09 09:57 /usr/local/lib/libboost_python-gcc44-mt-1_39.so
OK,boost.python環境準備妥當了,下一步體驗一下其Helloworld。
- 初試Boost.Python<CMake項目>
新建一個目錄,并用CMake搭建項目環境:
cd mkdir study cd study mkdir boost.python cd boost.python mkdir build touch CMakeLists.txt mkdir HelloWorld
這里的boost.python作為一個根目錄,build目錄用于編譯和測試,CMakeLists.txt用于組織各個子項目,剩下的就是每個項目一個子目錄了,起步的這個就是boost.python文檔提供的HelloWorld了。根目錄下的CMakeLists.txt如下:
cmake_minimum_required(VERSION 2.6) set(BOOST_INCLUDEDIR /usr/local/include/boost-1_39) set(BOOST_LIBRARYDIR /usr/local/lib) set(Boost_FIND_VERSION_EXACT TRUE) set(Boost_Debug TRUE) set(Boost_ADDITIONAL_VERSIONS "1.39" "1.39.0") include(FindBoost) find_package(Boost 1.39.0 COMPONENTS python thread unit_test_framework) if (NOT Boost_FOUND) message(FATAL " Boost library not found!") endif() set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) include_directories(${Boost_INCLUDE_DIRS} /usr/local/include/python2.6/) add_subdirectory(HelloWorld)
中間的一大堆是用于保證能夠找到1.39版本的庫的,因為cmake 2.6.4并不能很好的找到,默認總是找到1.38的,如果沒有安裝多個版本的boost,可能沒有這么麻煩。接下來就是把HelloWorld那個子目錄包進來。
cd HelloWorld cat CMakeLists.txt 具體內容如下(這里將默認CMake生成的動態庫的lib前綴去掉,因為python不喜歡這個,:-)): project(HelloWorld) include_directories(${Boost_INCLUDE_DIRS}) add_library(hello SHARED test.cpp) set_target_properties(hello PROPERTIES PREFIX "") target_link_libraries(hello ${Boost_LIBRARIES})
編輯test.cpp,將測試代碼拿進來
#include <string> #include <boost/python.hpp> using namespace boost::python; struct World { void set(std::string msg) { this->msg = msg; } std::string greet() { return msg; } std::string msg; }; //Wrapper BOOST_PYTHON_MODULE(hello) { class_<World>("World") .def("greet", &World::greet) .def("set", &World::set) ; }
編譯之:
cd ../build cmake ../ make
輸出如下:
Scanning dependencies of target hello
[100%] Building CXX object HelloWorld/CMakeFiles/hello.dir/test.cpp.o
Linking CXX shared library ../lib/hello.so
[100%] Built target hello
查看生成的庫文件:
cd lib ls -lh hello.so file hello.so
接下來用測試一番,啟動python:
python ............... >>> import hello >>> help(hello.World.set) Help on method set: set(...) unbound hello.World method set( (World)arg1, (str)arg2) -> None : C++ signature : void set(World {lvalue},std::string) >>> obj = hello.World() >>> obj.set("hello world!") >>> obj.greet() 'hello world!' >>> obj.set("Another string") >>> obj.greet() 'Another string' >>> del obj >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'hello', 'readline', 'rlcompleter'] >>> quit()
這里嘗試的幾個方法都已經湊效:構造、析構、greet/set,剛好就是前邊expose的那些了:
class_<World>("World") .def("greet", &World::greet) .def("set", &World::set) ;
下一步再嘗試一些復雜的東東。
posted on 2009-08-09 11:34 skyscribe 閱讀(4761) 評論(0) 編輯 收藏 引用