用CMake在建置時複製檔案到輸出執行檔的目錄

CMake有指令file(COPY …)可以用來copy檔案,可是如果將此指令寫在CMakeLists.txt中,該指令只會在configuration階段複製檔案。
有時我們會希望每次建置、make時都copy一次檔案。

自己遇到的使用案例 : 更新shader source file

OpenGL的shader是在Runtime用OpenGL的API將source code交給drive編譯的,因此在runtime需要有檔案提供shader的source。
這類檔案就像DLL一樣當程式啟動時需要能找到並載入,將檔案跟執行檔放在同一個目錄之下是最簡單的做法。

因此我的需求是要cmake每次都將source tree(原始碼目錄)中的shader file複製到build tree(建置目錄)中執行檔的目錄。

對於純粹想建置專案的人來說,把file(COPY …)寫在CMakeLists.txt中是OK的,因為檔案有copy到build tree就好。

但是對於專案開發者來說,需要能在每次建置時都從source tree複製shader到build tree中,以確保使用最新版本的shader file執行Debug。當然也可以手動,比較麻煩就是了。

如前所述,只將複製指令寫在CMakeLists.txt是不夠的。

利用add_custom_target或add_custom_command安插建置步驟,使任務在建置時執行

上StackOverflow找到的相關解答:

大致上,這方法額外寫個CMake檔案並利用add_custom_target安插執行此cmake檔案的步驟,該cmake腳本內的指令就是file(COPY …)

若想讓特定事件在每次建置時發生可利用這類方法

更好的方法

S.O.上找到的方法需要額外的cmake file,並使用script processing mode執行。我發現cmake除了processing mode也有command mode。該模式透過加入flag “-E” 切換:

cmake已經為我們準備了需多常用的指令,接下來只要如法炮製讓cmake在建置時觸發cmake的copy command就行了:

1
2
3
4
5
6
7
8
add_custom_target(
# 自訂的目標名稱
copy_shader_files
# 執行copy指令 cmake -E copy <file>... destination
${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/shaders/** ${CMAKE_BINARY_DIR}
)
# 用add_dependencies將自訂目標列為建置目標的dependency
add_dependencies(main copy_shader_files)

建立自訂目標後cmake會在指定的建置系統中建置對應的目標,如果建置系統為Unix make可以用以下指令執行copy指令:

問題: Xcode或Visual Studio的build type目錄

若你是使用Unix make或許到目前已經功德圓滿了,建置系統會在每次建置執行檔時幫我們複製shader。可是我遇到問題了。

Xcode或Visual Studio這類IDE內建build type而不同build type的執行檔通常會放在不同目錄裡:

Oh no..輸出的執行檔main在Debug下,而我們的shaders卻在Debug之外。可見CMAKE_SOURCE_DIR變數指向的位置跟實際執行檔輸出的位置是可能有出入的。

執行檔會輸出到哪是IDE決定的,而IDE的專案是CMake的Generator產生的,因此除非將路徑寫死在CMakeLists.txt,否則輸出路徑是generator決定的。

shader被複製到哪裡並不重要,只要他跟輸出的執行擋在同一個目錄即可。

對於Generating階段才做的決定cmake有一類像placeholder的特殊變數(`$’開頭,很像變數)代表generator給出的值。

使用 $> 代表目標檔案的目錄。

我的執行檔建置目標稱為main,因此 $ 指向main目標產生的檔案的所在目錄,也就是執行檔所在的目錄。

可是,單純的修改先前的code會得到cyclic depedencies錯誤。

建置目標的depdencies為我們的copy_shader_files目標,而要決定檔案目的地的卻又是建置目標,造成循環相依性。

add_custom_command

別灰心,除了add_custom_target,我們還可以透過add_custom_command叫cmake幫我們做事。只是add_custom_command不會在建置系統中建立任務或目標,沒辦法手動啟動,對於建置系統來說就是建置執行檔的一個環節。

1
2
3
4
5
6
7
8
9
add_custom_command(
# 指令依附的目標
TARGET main
# 自訂指令的執行時機
POST_BUILD
# COMMAND關鍵字後為執行指令
COMMAND
${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/shaders/** $<TARGET_FILE_DIR:main>
)

add_custom_command的語法稍微不同,不過指令依然是cmake的command mode。

目前這段code可以滿足我的需求: 在每次建置時把檔案複製到執行檔的目錄中。

C++在Class內定義常數(constant)的方法

要在class之中定義常數會遇到如何初始化該常數的問題。
C++11以前的標準不允許在class definition中初始化members。

嘗試直接在class definition中初始化變數

1
2
3
4
5
6
7
8
9
10
#include <string>
#include <iostream>
class Greeting {
public:
const std::string message = "Hello!";
void greet() {
std::cout << message << std::endl;
}
}

compile以上snippet會產生錯誤:

1
2
3
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:31:warning:</span> non-static data member initializers only available with -std=c++11 or -std=gnu++11
const std::string message = "Hello!";
^

編譯器說無法初始化non-static data member,還說該功能只能在c++11(以上?)使用

non-static不行 那麼 static呢?

將第五行改成這樣:

1
const static std::string message = "Hello!";

嘗試編譯:

1
2
3
4
5
6
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:38: error: invalid in-class initialization of static data member of non-integral type ‘const string {aka const std::__cxx11::basic_string<char>}’
const static std::string message = "Hello!";
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:38: error: call to non-constexpr function‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string (const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’

第二個錯誤訊息是語意錯誤(Semantic error)與C++標準有關。

第一個錯誤訊息則是說:”非intergal型別的class內靜態data member初始化是無效的”

看來std::string是非integral type所以雖然是static type仍然無法在class內初始化

甚麼是Integral type?

在Quora上的問題 What are integra types in C++?

從答案可以很清楚地了解到Integral types是C++型別系統中對於型別的分類,屬於這類的Type有:

type
布林變數 bool
字元
窄字元 char
寬字元 char16_t
char32_t
wchar_t
整數 int

(包含使用unsigned或short/long keyword衍生的型別)

從上面列出的type觀察可以發現這些type大多很簡單,能用整數表示,以幾個byte的形式存於記憶體。floating point,當作反例,則是需要特別的encoding(如:IEEE-754)才能表示所存數值的type。

再怎麼查,得到的答案都是一個list告訴你那些type是Integral type。可是為需要區分是否為Integral type呢?WHY?

我猜可能跟ISA或compiler實作有關吧?

* ISA => Instruction Set Architecture

常數被指定後就不能被變更,編譯器可以很安心的把它當作macro般在程式碼中展開、inline。在編譯階段任何參照到常數的地方直接抽換成該常數的值。如此一來,常數在runtime可以說是不存在的,因為他的值已經inline到text section而非可以被讀取的變數,也就代表常數沒有address。

char或bool是以整數的形態存於記憶體中,C++裡面可以對char或bool可以像對待數字一樣進行加減乘除。或許被歸類在Integral type的資料可以直接給ALU做運算並依照其type再進行解釋(例如:非0整數作為bool為true)。ISA提供了運算operation,因此編譯器可以很放心的直接把bytes交給機器處理。

floating point或其他user data type可能就無法直接交給ISA處理了,因此沒辦法直接inline,在runtime需要有一塊記憶體儲存其data member也需要address以供參照。

char 是integral type那麼char[]是嗎?

Nope!

1
2
3
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:33: error: invalid in-class initialization of static data member of non-integral type ‘const char []’
const static char message[] = "Hello!";
^

恩…我想也是。array本身代表連續的記憶體空間要怎麼inline呢?

Any other way?

C++之父 stroustrup的網站 提供了原因及方法。

原因

引自stroustrup 的網站

A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

通常class在header檔中定義,而header檔通常被許多翻譯單元include。為了避免複雜的linker發則C++要求每個物件都要有單一獨特的定義。若C++允許class內定義一個需要被儲存在記憶體的實體,那會打破此規則。

如果一個class的data member是另一個物件那麼可以說該class的定義相依於另一個物件,因為prototype definition主要給compiler決定要分配多少記憶體空間。因此要計算出此class所需的空間還得計算出其data member的空間。如果該data member也相依於其他物件,甚至是目前正在定義的物件…linker勢必要時做複雜的規則避免loop。

解決方法

要如何定義非intergal的data member?

解決方法即是不要使用in-class initialization。把初始化expression移到constructor或是class definition之外。

非static常數(將初始化移置constructor)

在constructor初始化常數。
跟java類似與許final/const留到constructor時才初始化,然而要在C++的constructor初始化常數必須透過initializer-list的方式。

1
2
3
4
5
6
7
8
9
10
11
class Greeting {
public:
//const static char message[] = "Hello!";
const std::string message;
Greeting() : message("Hello From constructor") {}
void greet() {
std::cout << message << std::endl;
}
};

static常數(使用out-of-class definition)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Greeting {
public:
//const static char message[] = "Hello!";
static const std::string message;
//Greeting() : message("Hello From constructor") {}
void greet() {
std::cout << message << std::endl;
}
};
const std::string Greeting::message = "Hello from outside";

高二的小小牢騷

在整理房間時,發現高中時用的鉛筆盒,裡面有張紙條,開頭寫著209 13號 <我的名字>,有班導批改的痕跡,應該是當時時常忘東忘西的我因為忘了帶週記簿而隨便找一張紙寫的。它一直在我高中用的鉛筆盒裡,放到高三直到畢業後被我丟在家裡的書桌上。大學畢業後回到家裡整理時才發現,儼然是高二寫給自己的一封信。內容為:


開學了,有寒假作業考。考試時發生了一些令我不愉快的事:我忘記考試當天英文科是第幾節考,而英文的考試範圍就是那本單字書。假設英文是接在數學科後面考,結果考完數學時我要去拿我的單字書,令人傷心的是,我的書包東倒西歪的,單字書也不翼而飛。一向不太整潔的我還特地在考前把我的書包擺正。而我們班就是有人那麼自私。我沒大肆宣揚,書包放在原位,希望考試結束時我的單字書會回來,但是至今仁不見其蹤影。我想我們都該努力提升自己的道德標準,不足的地方還煩請師長指正。畢竟有些沒品的事情,只是出自當事人的不經心。


班導的回應是:


<我的名字>:需要老師幫忙吧?!你別對班上的同學灰心,或許是每個人的書都很像,你可以在書包上,加註一些特殊符號…。總之,班導會在加強宣導!!

今天的我

我想這篇週記應該是我趁早自習或是下課時間加緊完成的,句子有些不通順。如今我再次讀了一遍後覺得自己當初想表達的重點只有三個,其他都是廢話XD甚至覺得讀起來有些八股:

  1. “我的單字書不見了” => 原由
  2. “有些人就是那麼自私” => 指責
  3. “還煩請師長指正” => 希望老師能管管班上同學

老師說的對,別人拿錯、或是翻到我的書包可能只是因為他們都長得差不多而不是動用想貪圖方便的私心。

當我發現自己的東西不見的時候,立馬咬死是他人所為而陷入憤怒的情緒,或許也算是自私的表現。

Cryptography - Chipher Definition

線上課程:密碼學 I

Cipher能加密message(訊息)為chiphertext(密文)

cipher能解密ciphertext為message(訊息)

加密定義

令加密演算法為$E$
解密演算法為$\mathcal{D}$
密鑰空間(key space)為$K$ (任一密鑰為$k$)
明文空間為$\mathcal{M}$ (任一訊息為$m$)
加密演算法產生的密文為$c$ => 所有加密演算法$\mathcal{D}$能產生的密文的集合為$\mathcal{C}$

Definition

$E:=(\mathcal{K},\mathcal{M})=\mathcal{C}$

解密定義

令解密演算法為$D$

$D:=(\mathcal{K},\mathcal{C})=\mathcal{M}$

加密解密寫一起

$\forall{m}\in{\mathcal{M}}$
$D\bigl(k,E(K,m)\bigr)=m$