本來是打算寫一篇年終總結,隨便和以往一樣提一提自己的開源專案(長不大的plain framework)的一些進度,不過最近這一年對於這個專案實在是維護不多,實在難以用它作為醒目的標題。而最近由於使用了VS2022,微軟居然自動識別了專案中的cmake(看來我是很久沒有使用這個工具了),於是在想方設法將這個專案做到可以在windows平臺上儘快提供編譯支援,其中遇到了許多有關的技術問題,我覺得可以在這裡為大家提供一定的借鑑,特別是自己想要擁有快速編寫專案的技巧。分享雖然微不足道,但是也希望大家在此能夠有所收穫。
2022的新春就要到了,新的一年(手動狗頭,這是指舊曆),祝福大家能夠平安喜樂!
1、專案地址
https://github.com/viticm/plain
每次將地址放出來,感覺像是為自己的孩子做宣傳,真的是可憐天下父母心。雖然這個孩子看起來實在太平庸了,可是我想說的是它還是有一定潛力的,至少在大多數的網路應用中都能夠很好地發揮其作用。核心的框架並沒有過多依賴,只需要依賴於標準的C/C++庫即可,目前支援的語法為C++11。
核心的模組:基礎(basic)、網路(net)、檔案(file)、系統(system)、資料庫(database)、指令碼(script)
具體的我不再這裡描述了,我之前對這個專案寫過一些較為詳細的介紹(估計也不夠詳細大家將就看吧)。
2、windows下的cmake
接下來開始上主菜,一切都源於這張圖:
如果沒有更改VS中預設的設定,那麼它在開啟資料夾時會自動識別目錄下的CMakelist.txt,然後你就會發現這個頁面了。它的目的是為提醒我們進行cmake相關的設定,有點像是遊戲裡面的引導功能,在IDE裡微軟的VS還是很注重使用者體驗的。雖然它出現了這個頁面,但在跨平臺開發的時候我仍然習慣於直接到相應的系統下直接開發,者或許是因為還沒有真正體驗到一個IDE跨平臺開發的樂趣吧。但為了更好的開發編譯,最近半個月時間幾乎對於專案的維護都在了CMake這裡,可以看到提交最多的註釋為Update cmake。
plain下面的CMakelist(根目錄cmake/CMakelist.txt)
# Copyright 2017 Viticm. All rights reserved. # # Licensed under the MIT License(the "License"); # you may not use this file except in compliance with the License. # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 2.8.12) set(PROJECT_NAME PlainFramework) set(PF_VERSION 1.1.0) set(PROJECT_DESC "Plain framework, based on c++ for net applictions") if (CMAKE_VERSION VERSION_LESS 3.0) project(PlainFramework CXX C) else() cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0037 NEW) project(PlainFramework VERSION ${PF_VERSION} LANGUAGES CXX C) endif() # Call fplutil to get locations of dependencies and set common build settings. include("inc/find_fplutil.cmake") include("inc/common.cmake") include("inc/internal_utils.cmake") if (NOT dependencies_gtest_dir) set(dependencies_gtest_dir ${root_dir}/dependencies/googletest/googletest) endif() if (NOT has_output_path) # This is the directory into which the executables are built. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${root_dir}/plain/bin) # This is the directory into which the librarys are built. set(LIBRARY_OUTPUT_PATH ${root_dir}/plain/lib) set(has_output_path 1) endif() #For utf8 no boom. if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4819") endif() # Options that control the build configuration. # To configure PlainFramework flags per build target, see the # plainframework_configure_flags() function. option(plainframework_build_tests "Build PlainFramework unit tests." ON) # Build plain framework plugins. option(plainframework_build_plugins "Build PlainFramework plugins" ON) file(GLOB_RECURSE PLAINFRAMEWORK_HEADERS ${CMAKE_CURRENT_LIST_DIR}/framework/core/include *.h) set(VERSION_RC ${root_dir}/cmake/inc/version.rc.in) add_subdir(${plainframework_dir}/cmake plainframework plainframework) # Plugins. if (plainframework_build_plugins) add_subdir(${root_dir}/plain/plugins/cmake plugins plainframework) endif() if(plainframework_build_tests) add_subdir(${root_dir}/framework/unit_tests/cmake ${root_dir}/framework/unit_tests/cmake/build plainframework) if (NOT plainframework_no_app) add_subdir(${root_dir}/plain/app/cmake ${root_dir}/plain/app/cmake/build plainframework) endif() endif()
root_dir(根目錄)
這個變數是當前專案的絕對路徑,在PF專案中這個絕對路徑是相對於CMakelist而言,也就是在子專案所在的根目錄,這樣是為了每個專案設定可以獨立進行設定。
這個變數在inc/common.cmake中,每個專案都這樣設定:
set(root_dir ${CMAKE_CURRENT_LIST_DIR}/../.. CACHE INTERNAL "plainframework root directory")
設定的路徑為inc目錄的上兩級目錄,PF專案中的cmake結構如下:
如圖inc的上兩級目錄就是plain,這樣就獲取到了專案所在的根目錄,但這樣的設定因人而異,或許大家能夠想到更好的方式。
讓VS編譯的時候不提示編碼的警告(由於專案大膽的使用了google,因此整體的警告等級為最高4,而且所有警告都視為錯誤)
作為純粹的開發者,no boom的utf8檔案才是可選的,由於歷史原因微軟各種自己使用的utf8檔案都是加上了boom標記。
#For utf8 no boom. if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4819") endif()
has_output_path(是否指定輸出目錄)
這個變數的目的為控制每個專案的輸出路徑,在VS中有生成後事件,也可以將生成的檔案拷貝到自己想要的目錄,但我自認為不太方便,直接就編譯到指定目錄才是王道。
設定執行檔案生成目錄
# This is the directory into which the executables are built. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${root_dir}/plain/bin)
在windows下這個目錄輸出工程的exe和dll等檔案,在linux下輸出的是可執行檔案和so動態庫。
設定庫檔案生成目錄
# This is the directory into which the librarys are built. set(LIBRARY_OUTPUT_PATH ${root_dir}/plain/lib)
在windows下這個目錄輸出工程的lib和exp等檔案,在linux下輸出的是.a檔案。
add_dir(新增目錄)
在PF中為了保證每個子目錄或者專案的根目錄被正確設定,因此自己封裝了這個新增目錄的函式用以替換直接使用add_subdirectory。
# Safe add_subdirectory. function(add_subdir target target_build project) set_compiler_flags_for_external_libraries() set(saved_root_dir${project} ${root_dir} CACHE INTERNAL "root dir cache") add_subdirectory(${target} ${target_build}) set(root_dir ${saved_root_dir${project}} CACHE INTERNAL "root dir recover") restore_compiler_flags() endfunction(add_subdir)
其目的保證當前的root_dir在子目錄新增後不被更改,保證當前的編譯變數在新增之後和之前一樣(這裡或許有些問題),個人認為這樣暫時足夠使用而且還挺方便的。
下面的命令即是新增框架的所在目錄:
add_subdir(${plainframework_dir}/cmake plainframework plainframework)
在windows上使用cmake進行編譯(是一個動圖)
執行測試(這個測試是自從編寫db模組時才加入的,因此不會太多,在後續大版本中會堅持每一個介面增加):
關於測試遇到的問題
我這裡要說的這個問題是windows上的,以前沒有寫測試用例的時候根本沒有關注這個問題,其罪魁元首我先直接貼在最前面(internal_utils.cmake):
if (NOT BUILD_SHARED_LIBS AND NOT pf_force_shared_crt) # When Plain Framework is built as a shared library, it should also use # shared runtime libraries. Otherwise, it may end up with multiple # copies of runtime library data in different modules, resulting in # hard-to-find crashes. When it is built as a static library, it is # preferable to use CRT as static libraries, as we don't have to rely # on CRT DLLs being available. CMake always defaults to using shared # CRT libraries, so we override that default here. string(REPLACE "/MD" "-MT" ${flag_var} "${${flag_var}}") endif()
這段程式碼是谷歌的,我之前一直連線的時候都是使用谷歌的靜態庫,其實都是為了方便。作為第三方的gtest,我直接將它作為自己的子模組,而且不能修改的子模組,用靜態庫我就不用在生成的時候去特意拷貝到自己的執行目錄了(windows)。可是最後發現,執行測試的時候直接產生了一個異常斷點,提示的是acrt_first_block==header。說實話對windows開發還缺少經驗的我來說,遇到這個問題第一時間只能搜尋查詢資料,但是你會發現與此相關的都是記憶體洩漏。但轉念我想到過,對於記憶體問題,PF是經過一段優化的,因此還是心存懷疑,於是使用vs進行除錯這次提示的是記憶體訪問衝突。
最後讓我懷疑是動態庫的原因,是看到了一篇文章,這是無意發現的,這也許是經過了幾天摸不著頭腦,老天可憐的緣故吧。於是我仔細檢查了所有的cmake編譯指令碼檔案,很快就定位到了上述懷疑的地方。想不到當初為了偷懶,到頭來卻為自己帶來了幾天的麻煩,關於windows的記憶體分配可以搜尋HeapAlloc關鍵字,裡面有詳細關於dll的記憶體分配。為了節省時間,加上本身不願意再去做修改,因此加上了下面的編譯指令碼(當初只是為了不做這一步)。
# Copy gtest libraries. if (MSVC AND pf_build_shared AND BUILD_SHARED_LIBS) if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") add_custom_command(TARGET core_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/bin/gtestd.dll ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ) else() add_custom_command(TARGET core_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/bin/gtest.dll ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ) endif() endif()
上面的目的很簡單,在不同的型別下拷貝不同的gtest動態庫。
預設示例
如果你使用PF進行開發,那麼可以先從這個簡單的示例開始(這裡有點跑題,不過目前在cmake中遇到的問題已經差不多講完了,那麼就說說相關的題外話)。
配置的細節就不用說了,新建一個cpp檔案就可以快速開始使用PF了,如下面動圖的開始那樣(是不是很簡單?)。
3、1.1.0
在我編寫這篇有關釋出文章的時候,其實自己也在準備PF第一個版本的釋出,以前沒有正經的做過釋出,這次釋出出來是為了能夠同大家一起研究和學習,不足之處還請指正。開源專案位於github,不過這個網站這兩年很不穩定,還希望大家多一點耐心等待,要麼就是用一下科學的工具吧。
plain專案(提供了框架庫和簡單的示例)
plain-simple(框架稍微詳細的示例,裡面包含了一個目前上線應用的例子)
寫在最後
在這裡再次祝福大家新年快樂,希望所有困擾我們的通通都消散,希望全世界和平美好!
如果有需要可以加入我們的QQ群(348477824),這是一個潛水專用群,群主基本上已經是潛水幾年了,但是如果你需要進行技術交流,那麼可以到群裡來閒聊。