0%

這篇文章主要是Rvalue References: C++0x Features in VC10, Part 2的閱讀筆記,在原文撰寫過程當中,規格有所變動,因此根據最新的規則做補充說明。

Copy Problems

C++98/03的時候,最令人詬病的問題,就是建立太多臨時物件,Value Semantics的意思就是複製出來的物件跟原先是獨立的,不會互相干擾

Lvalue and Rvalue

在C++98/03時期,有這麼一條規則Every C++ expression is either an lvalue or an rvalue.,Lvalue是在運算過後留下來的續存物件,而Rvalue是運算過後生命期就結束的臨時物件。
殂此之外,C++98/03裡還有一條規則A function call is an lvalue if and only if the result type is a reference

1
2
3
4
5
6
7
8
9
string one("one");
const string two("two");
string three() { return "three"; }
const string four() { return "four"; }

one; // modifiable lvalue
two; // const lvalue
three(); // modifiable rvalue
four(); // const rvalue
  • Type&可以繫結到一個modifiable lvalue,而如果要繫結到modifiable rvalue,C++規定禁止(Visual C++除外,可以把警告層級從3調到4,會警告你這樣很危險,而gcc跟clang則是輸出錯誤訊息。)
  • const Type&可以繫結到任何一種型態,不過不能對Rvalue做任何修改,因此不能對即將消滅的臨時物件採取任何行動

而在C++11之後,引進了Rvalue reference,解決了這個問題。

  • Type&&可以繫結到一個modifiable rvalue,而不能繫結到modifiable lvalue,需要強制轉型。
  • const Type&&可以繫結任何形態

每個reference都有一個名字,所以Bind到Rvalue的refernce,他是一個Lvalue,因此以下的程式碼。

1
2
3
4
void print(const string&) { cout << "print(const string&)" << endl; }
void print(string&&) { cout << "print(string&&)" << endl; }
void RvalueTest(string&& str) { print(str); }
RvalueTest(string());

在RValueTest執行結束之前,str是個合法的物件,因此被當作Lvalue,會執行第一個print。

為了學習Rvalue的觀念,自行打造move跟forward函數。

先看C++11之後引進的Reference Collapsing Rules

  • T& + & => T&
  • T&& + & => T&
  • T& + && => T&
  • T&& + T&& => T&&

而Move的用途就是明確指出不管物件是Lvalue或Rvalue,一律轉成Rvalue就是了。
而Forward的用途把外面的參數跟語意原封不動的傳進去內部,是Lvalue就是Lvalue,而Rvalue就是Rvalue。
先從比較簡單的Move開始看起,我門打造的第一版Move大概像這樣。
由於Move是個template function,必須進行Template Argument Deduction,此時引進了一條新的規則。
如果傳進來的是Lvalue的話,將會推導成T&,反之如果是Rvalue的話,就會推導成T(根據Reference Collapsing Rules,T和T&&都符合要求,為了解決歧異性,這邊強制要求推導成T)

1
2
3
4
5
template <typename T>
T&& Move(.... value)
{
return static_cast<T&&>(value);
}

這邊先來決定如何傳遞參數,Call by value第一個被否決,接著就是T&T&&的選擇,根據前面的Reference Collapsing Rules,如果是T&的話一律會摺疊成T&,而T&無法繫結至modifiable rvalue,而如果是T&&的話,不管Lvalue跟Rvalue都可以順利繫結。

接著我們來測試第一版的程式碼

1
2
3
4
quark(Move(up));
quark(Move(down));
quark(Move(strange()));
quark(Move(charm()));

印出的結果是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t: up
T: string&
T&&: string&

t: down
T: const string&
T&&: const string&

t: strange()
T: string
T&&: string&&

t: charm()
T: const string
T&&: const string&&

顯然結果錯了,原因在於實際參數是Lvalue的話,T會被推導成U&,而T&&的結果依然是U&,變成Move傳回去的語意是個Lvalue,因此導致上面的結果。
所以我們要做的,就是把U&或U&&一律轉成U&&,也就是std::remove_refernce存在的理由。改寫我們的程式,先用RemoveRefenece取得Primitive Type, 然後加上&&之後就可以得到正確的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
struct RemoveReference {
typedef T type;
};
template <typename T>
struct RemoveReference<T&> {
typedef T type;
};
template <typename T>
struct RemoveReference<T&&> {
typedef T type;
};
template <typename T>
typename RemoveReference<T>::type&& Move(T&& t)
{
return static_cast<RemoveReference<T>::type&&>(t);
}

gcc的實作就類似於這樣。
接著來討論forward該怎麼做,從上面我們可以知道,我們只能用T&&來傳遞參數。
先來一組helper function來驗證程式的正確性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void inner(std::string &str)
{
cout << "inner(std::string &str)" << endl;
}
void inner(const std::string &str)
{
cout << "inner(const std::string &str)" << endl;
}
void inner(const std::string&& str)
{
cout << "inner(const std::string&& str)" << endl;
}
void inner(std::string&& str)
{
cout << "inner(std::string&& str)" << endl;
}
template <typename T>
void outer(string&& value)
{
return inner(Forward(value));
}
outer(up);
outer(down);
outer(strange());
outer(charm());

而我們第一版的Forward的實作

1
2
3
4
5
template <typename T>
T&& Forward(T& value)
{
return static_cast<T&&>(value);
}

輸出結果則是

1
2
3
4
inner(std::string&& str)
inner(const std::string&& str)
inner(std::string&& str)
inner(const std::string&& str)

從上面知道,value是個左值,所以Type是U&,T被推導成U,T&&被強制轉換成右值,所以輸出的結果如上。避免的方法就是強迫加上template參數。

1
2
3
4
5
template <typename T> 
void outer(string&& value)
{
return inner(Forward<T>(value));
}

重新執行程式,這下結果符合我們的需求了

1
2
3
4
inner(std::string &str)
inner(const std::string &str)
inner(std::string&& str)
inner(const std::string&& str)

這邊還有幾點要說明的
之前的範例,value是左值,而T可能是U&或是U,T&的結果是U&,可以繫結住左值沒有問題。萬一Forward的參數是個右值怎麼辦?

1
inner(Forward<T>(string()));

我們需要另外一個function,解決function resolution的問題。

1
2
3
4
5
template <typename T>
T&& Forward(T&& value)
{
return static_cast<T&&>(value);
}

在傳進來的是個右值時,T被推導成U。T&&正好可以綁定一個Rvalue,解決上面的問題。不過問題又來了,如果是左值的話,T是U&,T&是U&,T&&還是U&,變成兩個function擁有兩個一模一樣的參數型態,Compiler不知道該選哪個。
解決方案就是套用上面Move所引進的RemoveReference,還原成Primitive Type。

1
2
3
4
5
6
7
8
9
10
template <typename T>
T&& Forward(typename RemoveReference<T>::type& value)
{
return static_cast<T&&>(value);
}
template <typename T>
T&& Forward(typename RemoveReference<T>::type&& value)
{
return static_cast<T&&>(value);
}

這樣子有另外一個好處,這個方案禁止了型別推導,不會再有Forward(value)的存在,編譯時期就能指出錯誤。

C++0x引進了Lambda Expression,這功能其實也不新鮮了,在其他語言都有類似的觀念。
為了處理Callback function,做了很多的改進
同樣的,我們從C語言時期看起。

C時期

C語言只有一種方式處理callback function,function pointer
像這個樣子

1
2
3
4
5
6
int add2(int a, int b) { return a + b; }
int operate(int a, int b, op2 func)
{
return func(a, b);
}
operate(3, 2, add2);

Function Pointer的方式處理Callback function非常經典,幾乎可以在所有純C語言開發的程式碼看到他。不過還是有可以改進的地方,例如

  • 速度: 由於傳進來的是指標,只能間接呼叫,無法有足夠的資訊做最佳化。
  • 狀態: Function比較難保存運行中的特定狀態。

C++98時期

C++的Functor(仿凾式)就是因此而生的,透過overload operator(),來改進上述所無法缺憾。搭配template的使用,使其應用更具彈性。可以同時支援Callback function跟Functor兩種形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Add2 {
int operator()(int a, int b)
{
return a + b;
}
};
int sub2(int a, int b) { return a - b; }
template <typename T>
int operate(int a, int b, T callback)
{
return callback(a, b);
}
operate(3, 2, Add2());
operate(3, 2, sub2);

之後有人想要將class member method跟static class method也包裝成Functor的形式,有LokiBoost兩大主流為主。這裡就不多談了,在C++0x中也將boost的bind跟function列入TR1裡,可以很方便的產生Functor。

C++0x時期

Functor雖好,不過每要新增一個新功能,就要寫一個function object,如果是只被使用一次的functor,維護這段程式碼需要付出成本,
因此就產生了一個暱名函數(anonymous function),像這樣。

1
2
3
4
5
6
7
8
for_each(v.begin(), v.end(), [](int n) {
cout << n;
if (n % 2 == 0) {
cout << " even ";
} else {
cout << " odd ";
}
});

詳細的語言規範可以參考MSDN的資料。透過跟程式碼上下文互動,達到非常有趣的效果。

Lambda Trick

之前我們介紹過unique_ptr,用來管理動態記憶體,非memory的Resource該怎麼處理,此時就是ScopeGuard登場的時候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ScopeGuard
{
public:
explicit ScopeGuard(std::function<void()> onExitScope)
: onExitScope_(onExitScope), dismissed_(false)
{ }

~ScopeGuard()
{
if(!dismissed_)
{
onExitScope_();
}
}

void Dismiss()
{
dismissed_ = true;
}

private:
std::function<void()> onExitScope_;
bool dismissed_;

private: // noncopyable
ScopeGuard(ScopeGuard const&);
ScopeGuard& operator=(ScopeGuard const&);
};
int main()
{
HANDLE h = CreateFile(...);
ScopeGuard onExit([&] { CloseHandle(h); });
// do smoething
return 0;
}

或者將不同function signature,由於要經過相同的處理流程,於是用lambda expression隱藏了細節,以下是DirectX ToolKit的部分程式碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<typename T, typename TCreateFunc>
static T* DemandCreate(Microsoft::WRL::ComPtr<T>& comPtr, TCreateFunc createFunc)
{
T* result = comPtr.Get();

if (!result)
{
// Create the new object.
ThrowIfFailed(
createFunc(&result)
);
comPtr.Attach(result);
}
return result;
}
ID3D11SamplerState* CommonStates::LinearClamp() const
{
return DemandCreate(pImpl->linearClamp, [&](ID3D11SamplerState** pResult)
{
return pImpl->CreateSamplerState(D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_CLAMP, pResult);
});
}
ID3D11DepthStencilState* CommonStates::DepthRead() const
{
return DemandCreate(pImpl->depthRead, [&](ID3D11DepthStencilState** pResult)
{
return pImpl->CreateDepthStencilState(true, false, pResult);
});
}

可以看得出來,DemandCreate處理的流程相同,但是船進去的type跟Callback function不同,就能達到一般化的效果,這也是我目前看到最有趣的例子。

C語言時期

在C語言中,最常遇到的情況就是忘了釋放記憶體,然後造成Memory Leak的問題。
例如以下這段程式

1
2
3
4
5
6
7
8
9
10
11
void LeakDemo(char path1, char path2)
{
void *alloc = malloc(16);
if (path1) {
free(alloc);
return;
}
if (path2) return;
free(alloc);
return;
}

在上面這段程式裡面,path2就忘了釋放記憶體,然後造成Leak。
這是語言上的侷限,只能靠多檢查source code跟使用工具來減少這種問題。

C++98時期

由於C++有RAII idiom之後,對於釋放記憶體的事情就變得簡單很多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct StackObject {
void *alloc;
StackObject(int size) {
alloc = malloc(size);
}
~StackObject() {
free(alloc);
}
};
void NoLeakDemo(char path1, char path2)
{
StackObject s(16);
if (path1) return;
if (path2) return;
return;
}

而為了一般化,STL裡面時做了一個auto_ptr,利用Template跟RAII的觀念來管理記憶體。

auto_ptr的問題

如果把auto_ptr侷限於上面的用法,不會遇上什麼問題,一旦要搭配現有的程式碼,當參數傳來傳去,問題就出現了其中最嚴重的問題莫過於

1
2
3
4
5
6
7
void Test(auto_ptr<int> v) {}
int main()
{
auto_ptr<int> v(new int(4));
Test(v);
printf("%d\n", *v);
}

以上的程式碼,看起來沒什麼問題,不過實際執行就Crash了。
auto_ptr的問題是沒有Copy Semantics,他的Copy Constructur裡面做的是Move的動作。
以下是auto_ptr的Conpy Constructor實作簡化版

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename _Tp>
class auto_ptr {
private:
_Tp* _M_ptr;
public:
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
_Tp* release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
};

於是在執行完Test之後,main中的v裡面的pointer就被清空,無法正常使用。
由於這個緣故,STL的各種容器跟演算法,搭配上auto_ptr或多或少都有問題。在經過多次修改之後還是無法修復,於是auto_ptr在C++0x之後就標繼承deprecated了,不建議使用。
由於C++0x引進了Rvalue reference,因此新的unique_ptr就此登場。

unique_ptr

unique_ptr能做的事幾乎跟auto_ptr一樣,除了少數例外。

1
2
3
4
auto_ptr<int> a(new int);
auto_ptr<int> b(a); // Compile success, but nosafe
unique_ptr<int> c(new int);
unique_ptr<int> d(c); // Compile error

不過你也可以利用C++0x新增的Move Semantics,手動進行Move動作。

1
2
unique_ptr<int> c(new int);
unique_ptr<int> d(std::move(c)); // OK

靠著C++0x新增的Rvalue Reference,區分出Copy跟Move的差異。看著unique_ptr的實作,他只允許Move Constructor,而不允許Copy Constructor。

1
2
3
4
5
6
7
8
9
10
template<class _Ty, class _Dx>	
class unique_ptr
{
private:
unique_ptr(const _Myt&); // not defined
unique_ptr& operator=(const _Myt&); // not defined
public:
unique_ptr(unique_ptr&& _Right) _NOEXCEPT;
unique_ptr& operator=(unique_ptr&& _Right) _NOEXCEPT;
};

如果要得到更多的Rvalue Refenece的相關內容,請參考這篇Rvalue References: C++0x Features in VC10, Part 2

unique_ptr還能做些什麼

由於C++0x引進了Move Semantics,連帶的STL所有Container跟Algorithmer都支援Move Semantics了,因此這樣的程式碼就變得可行了。

1
2
3
4
5
6
7
8
typedef unique_ptr<char> UniCharPtr;
vector<UniCharPtr> vc;
vc.push_back(UniCharPtr(new char('c')));
vc.push_back(UniCharPtr(new char('b')));
vc.push_back(UniCharPtr(new char('a')));
sort(vc.begin(), vc.end(), [] (const UniCharPtr &a, const UniCharPtr &b) -> bool {
return *a < *b;
});

在這裡同樣用上了lambda expression,留待有空再寫。

有個手法稱作source and sink idiom
因為有了Move Semantics更容易的實現。

1
2
3
4
unique_ptr<int> Source() { return unique_ptr<int>(new int); }
void Sink(unique_ptr<int> s) {}
auto source = Source();
Sink(std::move(source));

除此之外,還能夠自訂Destructor。

1
2
3
4
5
auto del = [](int *p) { 
cout << "Deleting x, value is : " << *p << endl;
delete p;
};
std::unique_ptr<int, decltype(del)> px(new int(5), del);

以及透過partial specialization來管理一個動態產生的Array。

1
2
auto del = [](int p[]) { delete [] p; };
std::unique_ptr<int [], decltype(del)> px(new int[5], del);

要從一個singly lisked linerar list中刪除文件,最簡單的方式莫過於用一個指標指向前一個node,如果現在的item需要刪除,將前面一項item指向後面的item即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct node  
{
struct node * next;
} node;

node *remove_if(node * head, remove_fn rm)
{
for (node *prev = NULL, *curr = head; curr != NULL; )
{
node * next = curr->next;
if (rm(curr))
{
if (prev)
prev->next = curr->next;
else
head = curr->next;
free(curr);
}
else
prev = curr;
curr = next;
}
return head;
}

有人提出了更精巧的方法,利用two star pointer來達成,也就是指標的指標。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void remove_if(node **head, remove_fn rm)
{
for (node** curr = head; *curr; )
{
node * entry = *curr;
if (rm(entry))
{
*curr = entry->next;
free(entry);
}
else
curr = &entry->next;
}
}

在C++11的年代,增加了forward_list,省得自己造輪子的麻煩。
以下是forward_list的介紹。

std::forward_list is a container that supports fast insertion and removal of elements from anywhere in the container. Fast random access is not supported. It is implemented as singly-linked list and essentially does not have any overhead compared to its implementation in C. Compared to std::list this container provides more space efficient storage when bidirectional iteration is not needed.

以下是一個示範程式,裡面同時使用了lambda expressionRange-based for loop,日後有時間在寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <forward_list>
int main()
{
std::forward_list<int> list = {8, 7, 5, 3, 2, 6, 4};
list.remove_if([](int n) { return n % 2 == 0; });

for (int n : list) {
std::cout << n << ' ';
}
std::cout << '\n';
}

安裝openssh-server

1
$ apt-get install openssh-server

現在就可以用Putty從遠端登入了,如果要有更佳的安全性,需要做進一步的設定。

修改sshd_config

這個檔案位在/etc/ssh/sshd_config

禁止root帳號login

將設定檔裡面的 PermitRootLogin yes 改成 no

更改login port

修改Port 22

允許特定IP登入

修改/etc/hosts.allow這個檔案

新增 sshd: xxx.xxx.xxx.xxx allow允許特定的IP放行。

拒絕所有IP登入,除了上面已經允許的部分

修改/etc/hosts.deny

新增sshd: all deny

修改設定過後,重新啟動服務

1
$ /etc/init.d/ssh (stop / start / restart)

在Stackoverflow看到類似的問題,紀錄一下
假設我門需要把一個resource加入執行檔或Library,可以使用以下的方法
假設我們現在有個檔案,叫做data
裡面的內容還是我們的老朋友Hello world!
有兩個方式可以達成

將檔案變成 object file

1
$ objcopy -I binary -O elf64-x86-64 -B i386 data data.o

如果我們用nm來觀看其內容

1
2
3
4
$ nm data.o
000000000000000d D _binary_data_end
000000000000000d A _binary_data_size
0000000000000000 D _binary_data_start

其中的A代表在之後的連結過程,其數值不會再之後的動作所更改。接著我們寫另外一段程式

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

extern char my_data[] asm("_binary_data_start");
extern char my_data_size[] asm("_binary_data_size");
extern char my_data_end[] asm("_binary_data_end");

int main()
{
char *c;
for (c = my_data; c != my_data_end; c++) putchar(*c);
printf("String length: %ld\n", (long)(void *)my_data_size);
return 0;
}

編譯且執行他

1
2
3
4
$ gcc test.c data.o -o test
$ ./test
Hello world!
String length: 13

從code可以看出,我們的my_data就是對應_binary_data_start的部分
please reference Controlling Names Used in Assembler Code

重新用nm看產生的執行檔,關切我們在意的部分

1
2
3
4
$ nm test
0000000000601035 D _binary_data_end
000000000000000d A _binary_data_size
0000000000601028 D _binary_data_start

發現_binary_data_start跟_binary_data_end在連結之後,都被重定位了。而_binary_data_size的位置保持不變,因此可以做為一個sentinel使用。

將檔案變成單一header file (適用於各種編譯器)

利用工具將Binary Data轉成Header file,這邊用linux的xxd來做示範。

1
$ xxd xxd -i data data.h

重寫我們的程式

1
2
3
4
5
6
7
8
9
10
11
nclude <stdio.h>
#include "data.h"

int main()
{
int i;
for (i = 0; i < data_len; i++) putchar(data[i]);
printf("String length: %d\n", data_len);
return 0;
}

編譯且執行他

1
2
3
4
$ gcc test.c -o test
$ ./test
Hello world!
String length: 13

可以得到跟上面一樣的結果。

在網路上找了一下ranlib的用法,發現這算是歷史共業之一。
一開始的ar只是像tar一樣,負責打包所有的object file。而不用來處理linking時所需要的Archive index,這個部分交由ranlib來負責。
後來發現這兩個部份可以合而為一,再加入或更新object file的時候,一同更新所需要的Archive Index。ranlib的功用就如同ar -s,為了維持相容性而留下來。

以下是一個需要使用ranlib的例子,為了示範,使用特殊的設定,一般不會遇到這種問題。假設我們現在有個foo.o了,接著建立一個archive file。

1
$ ar rcS libTest.a foo.o

注意這邊的S選項,我們強迫不讓他產生Symbol table,現今的環境就算不加s也會產生Symbol tabl。

1
2
3
$ gcc main.c libTest.a -o main
libTest.a: could not read symbols: Archive has no index; run ranlib to add one
collect2: ld returned 1 exit status

用nm看一下libTest.a

1
2
3
4
5
$ nm -s libTest.a

foo.o:
0000000000000000 T foo

nm對-s的說明如下

When listing symbols from archive members, include the index: a mapping (stored in the archive by ar or ranlib) of which modulescontain definitions for which names.

以上的結果表示我們在archive file裡面沒有archive index的存在,這時候就該ranlib登場了

1
2
3
$ ranlib libTest.a
$ gcc test.c libTest.a -o test
$ ./test

再度用nm來看結果

1
2
3
4
5
6
$ nm -s libTest.a
Archive index:
foo in foo.o

foo.o:
0000000000000000 T foo

現在看到Archive index被成功建立了

gcc 常用參數介紹

  • 什麼參數都不加

    1
    $ gcc hello.c

    直接產生a.out

  • 無連結選項

    1
    $ gcc hello.c -o hello

    直接產生可執行文件hello

  • -x language filename
    設定文件所使用的語言,使預設的附檔名失效,對之後的檔案也有效,除非你再次設定這個參數。可以使用的語言有C/C++等。

    1
    $ gcc -x c hello.pig

    上面這個例子,就是將附檔名pig的檔案當作C語言來編譯。如果在後面加上了-x none filename,則會抵銷前面的作用,回復復育社規則來編譯。

    1
    $ gcc -x c hello.pig -x none hello2.c
  • -c
    將程式碼生成至obj file後停止,預設附檔名是.o。

  • -S
    將程式生成至Assembler Code後停止,預設附檔名是.s。

  • -E
    只處理Preprocess的部份,跟上面兩者不同,不會產生檔案,需要用Redirect的方式產生文件。

    1
    2
    $ gcc -E hello.c > hello.i
    $ gcc -E hello.c | less
  • -o
    指定輸出檔案名稱,可以跟上面參數合併使用。

    1
    2
    3
    $ gcc -S hello.c -o hello.asm
    $ gcc -c hello.c -o hello.obj
    $ gcc hello.c -o hello.exe
  • -ansi
    關閉GNU C和ANSI C不相容的部分,在C模式相當於c90,在C++模式相當於C++98。如asm、``inlinetypeof等。如果你不考慮寫個標準的ISO C語言的話,在-ansi參數下還是能夠用asminlinetypeof`等GCC拓展語法繼續工作。如果要避免這種情況,可以配合底下這個參數。

  • -pedandic
    對所有不合乎C/C++ Standard的與法發出嚴重警告,

一直到今天才成功實驗出如何把WinMain放在DLL裡面,紀念一下。

首先,把WinMain放在DLL裡面。注意是WinMain而不是wWinMain,這邊也只有WinMain可以正常運作。

第二步,新增一個def檔,裡面加入

EXPORTS
WinMain

將WinMain輸出到Export Table,只有WinMain需要這樣做,用__declspec(dllexport)輸出的函數跟類別不受影響,且WinMain不能這方式輸出。視為特例。

第三步,直接讓Application連結DLL產生的Library即可。

Linux 3.7 kernel出了,順便記憶編譯核心過程

The Linux Kernel Archives 抓取想要的核心版本,這裡我們拿3.7當範例,下載完並解開。

1
2
$ tar xjf linux-3.7.tar.bz2
$ cd linux-3.7

拿之前已經設定過的.config為基礎繼續修改

1
2
$ cp /boot/config-x.x.x .config
$ make menuconfig

修改完之後就可以下 make 開始編譯

等到結束之後,就能下

1
2
3
$ make install
$ make modules_install
$ make headers_install

來安裝核心,模組,和對應的header files。

更新 System.map

1
$ depmod -eF -m  /boot/System.map-3.7.0 3.7.0