一般全局變量應該用比較長,詳細的名稱,而本地變量則簡單明了為宜。
GetSystemMetrics可以獲得很多系統信息。
#i nclude <>先搜索/I編譯選項的路徑,然后搜索環境變量INCLUDE,""先搜索父文件的路徑,然后才是前面兩個路徑。
用C設計一個動態增長的數組,需要用到MALLOC和REALLOC,對與同一個指針,如果內存不夠存儲數據,就用REALLOC重新分配多一倍的內存。并且返回的指針不能直接給原來的指針,因為如果分配失敗,那么原來的數據就會丟失。
傳遞函數地址作為參數
首先定義以函數地址作為參數的函數
fun1(返回類型(*fun2)(參數))
{
...=(*fun2)(參數);
}
fun2正常定義
調用的時候 fun1(函數名);
int fun2(int n)
{
return n+1;
}
int fun1(int n,int(*f)(int b))
{
return (*f)(n);
}
int main(int argc, char* argv[])
{
int a(B),b;
b=fun1(a,fun2);
cout<<b;
return 0;
}
實現散列表:使用一個散列函數,將項散列到一個數組里面,每個數組元素是一個鏈表,記錄這個散列值的所有項。
CMemoryState 類可用于檢查內存泄露。
構造函數和析構函數都沒有返回值
類的成員缺省是私有的.
如果定義了構造函數和析構函數,必須將它們設置為PUBLIC,否則無法訪問.
(事實上也可以是private,但只能訪問類的靜態成員,這時不能生成對象,如果要生成對象,需要一定的技巧)
重載依靠參數不同而不是返回值的不同,因為我們可以在調用函數的時候忽略返回值,這個時候編譯器無法確定該調用那個函數.
構造函數可以根據參數類型不同區分,也可以根據參數個數不同區分,但如果一個構造函數的參數有缺省值而且前面的參數與其它構造函數相同,那么如果在調用的時候用到了缺省值,編譯器無法分辨該調用那個函數,會報錯.
可以在定義函數的時候,讓某個參數只有類型名而沒有標識符,這樣做的目的是為了將來可能需要插入一個參數,在調用的時候隨便給這個位置一個值就可以了.
int temp(int a,int,int b)
{}
~按位求反
const 存放在符號表里,不會被分配內存.如果使用&符號,就會強迫編譯器為常量分配地址,const數組也會被分配內存.
const int* p;int const* p;指向常量的指針,它的內容(*p)不能被改變,也不能將它的值賦int * const p,常指針,值不變。給非常量指針。
對臨時對象要使用常值引用接收,因為臨時對象是常量。
對于const成員變量,必須在構造函數前(構造函數初始化表)對它賦初值(莄onst也可以在這里賦值,但沒有必要,可以轉到構造函數體內,雖然在初始化表里效率更高)。任何類型的成員變量都不能在聲明的時候賦值。
類中的常量const是對某個具體對象而言的,如果需要對整個類的常值,使用enum.
char *p="123456";
cout<<*p++<<*p++;
*p=3;
char *t;
t=p;
輸出結果為:21,因為求值順序是從右到左,第三個語句是錯的,*p的內存是不可寫的,似乎p應該為一個常量指針,但確實可以將p賦給一個非常指針,語句5不報錯.
如果成員函數被聲明為const,那么其中不能含有改變成員變量值的語句(但可以改變被mutable修飾的成員變量)),也不能調用非const成員函數,如果對象被聲明為const,那么它只能調用const成員函數.
volatile的用法同const,甚至可以使用const volatile做修飾沒,volatile標識數據可能被別的進程改變,因此有必要在每次使用的時候重讀這個數據,這在優化期間特別重要,防止編譯器做一些假設.
給宏的參數最好是簡單變量,如果不是,如a++,那么變量在宏中出現幾次,a就會被加多少次。最好不用宏做類似函數的事情,在類中,用內聯函數代替宏,一樣可以得到高效率。
在類中定義的函數自動成為內聯函數,在類外,使用inline關鍵字。
不應該使用public成員變量,而是應該使用內聯函數存取這些變量,使用重載可以只用一個函數名字完成存取。
一個程序的所有文件,所有的名字(不在函數或類中)缺省都是外部連接,這意味著不同文件中相同的名字(不在函數或類中)會引起沖突,如果對這些名字用static修飾,就會變成內部連接,名字僅在編譯單元內可見。extern是static的反義詞,表示外部連接,同缺省的意義相同。兩種連接的名字都會存儲在靜態存儲區。
一旦用于修飾局部變量,static就不再表示可見性,而只改變變量的存貯類型。
register表示希望這個變量被放在寄存器里,因為它經常被用到,應該避免使用這個關鍵字,因為這方面通常機器比人更擅長。
類中的static變量(包括用static修飾的const)必須在類和函數外的全局位置初始化,初始化的語法是:static 類型 類::變量名=值;
存在嵌套類和局部類:類中定義的類和函數中定義的類,后者不能有靜態成員變量(顯然,沒有辦法初始化這種static變量).
調用c庫,在聲明的時候要使用extern "C" 函數聲明,指明這是一個c連接.因為c++同c的編譯器不同,會為函數產生不同的內部名,按照c++的方式連接c函數,會找不到庫中的函數體.當然,通常情況下庫的開發上已經為我們做好了這些.
引用必須被初始化,且引用的對象不能改變.不能引用null.
int f(const int&)
上面是一個常量引用,使用常量引用是為了保證外部變量不被修改,另外,如果傳入的是常量或者臨時對象,不使用常量引用的參數將出錯.因為二者都是常量.
無返回值的函數是void類型.
void inf(int*&i){i++;} 調用:int *i=0; inf(i);
上面的函數是以指針引用做參數,改變指針的值.還可以使用指向指針的指針,要麻煩一些,不過表達更明確:void inf(int **i){(*i)++}; 調用時:int *i=0; inf(&i);
通過值傳遞給函數,或者函數返回一個對象,是使用位拷貝建立對象,這種情況下編譯器會調用拷貝構造函數(如果沒有編譯器會建立一個缺省的),但對象銷毀時會調用析構函數.
如果想禁止通過值傳遞某個對象,只要聲明一個私有的拷貝構造函數,此時編譯器認為用戶接管了這項工作,不會建立缺省的拷貝構造函數,而用戶建立的函數是私有的,沒法調用,編譯器就會報錯.
可以定義指向類的成員變量和成員函數的指針,程序不必使用函數的名字就可以調用它,想起了高通的CDMA程序框架.c++編程思想第10章。
一個指向函數的指針:
void inf(int *&i){i++;}
int main(int argc, char* argv[])
{
int *i=0;
cout<<i<<endl;
void (*pf)(int *&);
pf=&inf;
(*pf)(i);
cout<<i<<endl;
}
運算符重載
重載僅是對用戶類型的數據來說的,對內置的數據類型是不可以重載運算符的。
.和.*都不能重載.可以將運算符重載看作另外一種形式的函數調用,函數的名字是operator@,@代表運算符,參數的個數取決于兩個因素:
1 運算符是一元還是二元
2 運算符是全局函數(一元是一個參數,二元是兩個參數),還是成員函數(一元沒有參數,二元一個參數----對象變為左側參數)
可以重載幾乎所有的運算符,但對于現在c中沒有意義的運算符是不能重載的,也不能改變運算符的參數個數和優先級.
重載運算符的返回值:如果需要返回對象本身,根據需要返回
對象的指針或者引用,如果是返回臨時生成的對象,那么返回對象.
重載運算符的返回值是否常量:當返回的是一個臨時值得時候,如:%,&,>>,這些運算符得到的結果要賦給另外一個變量,這時返回值是const,如果返回值直接用于變量,如-=,+=,這是返回值不要加const.
函數返回對象的時候,返回一個臨時對象比新建一個對象在返回效率要高很多,因為這時調用的是普通構造函數而不是拷貝構造函數,而且不需要調用析構函數,雖然新建一個對象再返回返回的也是一個臨時對象.
智能指針(smart pointer):對象,包容器,迭代器.
自動類型轉換:可以編程實現自動類型轉換.如需要從對象one到two,那么只需要為two定義一個以one&為參數的構造函數,當編譯器發現需要進行從對象one到two的轉換的時候,會自動檢查two的定義,找到這個構造函數,構造一個two對象.如果需要顯式類型轉換,在構造函數前加一個:explicit
還有一種自動類型轉換方法是:為需要轉換的對象重載一個運算符,運算符以要轉換到的對象的名字命名.無須聲明返回值.
operator one() const{ return one(x);}
不過并不提倡隱式類型轉換,這樣容易隱藏錯誤,也會降低調用時的效率.
使用全局重載運算符而不是成員運算符的好處是可以對左右操作書都自動作類型轉換,而成員運算符的操作數左側的必須是正確的對象
重載賦值操作符"=",返回可以是引用也可以是值,前者效率較高,但要記得此時返回的引用不能是屬于局部對象的.通常返回*this.
return String(s1+s2); 與String temp(s1+s2);return temp;的效率是不同的,后者要進行對象拷貝,而前者直接將臨時對象創建在函數的返回區。同時也更加簡潔。
函數中少用static變量。讓相同的輸入產生相同的輸出,這樣的代碼便于使用和維護。
對函數的參數和返回值的有效性進行檢查。
積極使用斷言(ASSERT),同時要加上注釋,防止將來忘記ASSERT的目的。
之所以有了指針還要引入引用,是為了對功能加以限制,防止發生意外,就像對參數加上const限定的目的一樣。
動態分配內存的原則:
1 分配后要檢查是否分配成功,即if(p==NULL)
2 釋放內存后要記得令p=NULL,防止產生野指針.野指針會讓我們在使用指針前的if(p==NULL)檢查形同虛設.
要申請一塊內存復制數組char a[]的內容,應該申請的內存大小是sizeof(char)*(strlen(a)+1);
如果通過參數傳遞數組,數組名自動退化為一個指針.
main()
{
char a[100];
cout<<sizeof(a);
fun(a);
}
void fun(char a[100])
{
cout<<sizeof(a);
}
輸出100 4.
對內存分配失敗進行處理有兩種方法:
1 if(p==NULL) 適用于內存分配語句較少的情況
2 _set_new_handler _set_new_mode 適用于內存分配語句較多的情況
unsigned與沒有unsigned 類型只是表示范圍不同,大小相同.
如果不給類定義拷貝構造函數和賦值函數,如果類中有指針變量,就會導致錯誤,如果指針指向動態內存區,那這塊內存會丟失,而兩個指針相同一個塊內存,導致其值無法判定,而且兩個函數的析構函數會將這塊內存釋放兩次,導致出錯。
String a("hello");
String b("world");
String c(a); //調用拷貝構造函數,還可以寫成:String c=a;但風格較差。
c=a; //調用賦值函數(operator =) 賦值函數中注意先檢查自賦值。
在繼承當中,構造函數,析構函數,賦值函數都不能被繼承,在編寫子類時要注以下幾點:
1子類必須在構造函數的初始化表調用基類的構造函數。
2父類和子類的析構函數都必須是virtual.//用于多態。
3子類賦值函數要調用父類的賦值函數:Base::operater=(other);
對函數參數和返回值進行const限定僅對指針和引用有意義,對值傳遞沒有意義,對輸出參數一定不要用const,不然無法輸出參數。
重載new和delete的原因有兩個:需要反復分配內存,需要親自做這個工作提高效率,還有就是減少內存碎片,比如可以首先使用靜態成員指針保留很大一塊內存(在靜態存儲區),在其中完成內存的分配,并自己標記分配和釋放,釋放的時候,只是標記內存,而不free釋放。
重載的new和delete只完成內存的分配和回收工作。new接受size_t函數,完成內存的分配,返回一個void*指針,delete接受一個void*指針,將它釋放。
注意重載new和delete有兩種不同的形式,一個用于每次創建一個對象,另一個用來創建一個對象數組,需要加上[]。如果重載了前者,那么在創建對象數組的時候,系統會調用全局的new和delete.
發現一個有趣的現象,可以使用值為NULL的指針調用任意對象的成員函數,只要先強制轉換到這個對象,并且調用的是純代碼。
成員對象的初始化可以和父類構造函數的調用并排放在初始化表。
在進入構造函數的左括號前,所有的成員變量都必須被初始化。
構造函數,析構函數,賦值運算賦不被繼承。
類的友元能夠訪問其private,protected成員,子類能訪問類的protected成員。
不要在析構函數中拋出異常,因為異常處理函數在獲得異常后要調用析構函數清理對象,此時再發生異常會導致程序無法再捕獲異常,只能終止(只能在自定義的set_terminate()中作最后的處理。)。
拷貝字符串的方法
char dest[sz];
memset(dest,0,sz);
strncpy(dest,source,sz-1);
這樣保證了不會超過緩沖區且結尾為'\0'.
異常處理函數會首先調用所有在try塊中創建了的對象的析構函數,然后執行異常處理函數,然后繼續運行后面的程序。但問題是,如果一個析構函數出現了異常,在析構函數中異常前創建的堆上的所有對象都無法調用其析構函數正常銷毀。方法是使用模板,并自初始化表創建這些模板對象。
set_unexpceted可以截獲沒有被函數異常規格說明包括得異常,還可以簡單的用一個throw;將這個異常作為已知異常再次拋出,如果有相應的catch語句,那么就可以捕獲這個異常。
拋出異常的子類,會被能夠捕獲其父類異常的處理器捕獲。這時會產生切片,即處理器收到的是一個父類,使用引用而不是傳遞值可以避免這個問題。
try
{
throw(except("got it"));
}
catch(except &t)
{
t.what();
}
運行時類形識別對void指針無效。
dynamic_cast<>用于向下映射。
base* b=new derived;
derived* d=dynamic_cast<derived*>b; 如果dynamic_cast失敗的話,將返回NULL,可以以此來試探著判斷指針b的類型。
RTTI還可以使用typeinfo().name()的方法返回對象id。typeinfo()返回typeinfo對象,使用前要包含頭文件typeinfo.h.
class B
class D:public B
B* p=new D;
B& r=*p;
typeid(p)==typeid(B*)
typeid(r)!==typeid(D)
typeid(*p)==typeid(D)
typeid(&r)==typeid(B*)
對引用的動態映射也要制定到一個引用上,如果失敗不是返回NULL,因為應用不許為空,而是產生一個異常,因此對引用的動態映射必須使用異常處理。
對空指針使用typeid()也會產生異常,可以在使用之前檢查指針是否為NULL來避免這個問題。
在對重繼承的情況下,傳統的強制類型轉換可能無法正常工作,但動態映射和typeid工作的很好。
要是動態類型類型轉換,需要基類包含virtual成員函數,并且vc編譯器有/GR選項。經過動態類型轉換,由父類轉換而來的子類指針可以調用子類中新添加而父類中沒有的方法。
static_cast 通常不是必需的,但它會讓類型轉換更加醒目。
const_cast用于將常量和volatile映射給普通指針。
reinterpret_cast是危險并且可移植性很差的轉換,它將對象看作二進制數進行轉換。最好不要使用。
C++中,將結構名直接作為類型名使用,而不需要象c中那樣使用typedef struct 結構名{} 類型名;
WINDOWS核心編程
內核對象:每個內核對象都是一個內存塊,由內核維護,進程在創建了一個內核對象后獲得一個句柄,通常一個進程的句柄對另外一個進程是沒有意義的,但可以通過一定措施在進程間共享內核對象。當進程終止后,它創建的內核對象不一定消失,內核維護每個內核對象的引用計數。
GDI對象不是內核對象,區分內核對象和GDI對象的方法是內核對象的創建函數的參數中有安全屬性,而GDI對象沒有。
內核對象的安全屬性通常在創建服務器程序的時候用到,傳遞一個NULL可以獲得缺省的安全屬性。
當不再使用某個內核對象的時候,可以使用BOOL CloseHandle(HANDLE)關閉句柄,系統會自動將內核對象信息清除出進程的句柄表(此句柄表保存且僅保存該進程使用的所有內核對象信息。),并自動為內核對象的引用計數減一。如果忘記關閉句柄也不要緊,在進程推出后,系統會自動檢查進程的句柄表,清理沒有釋放的句柄,因此忘記關閉句不一定會造成內存泄漏。
程序的進入點WinMain的第一個參數時進程的實例句柄,也是進程映射到虛擬地址空間的起始地址,vc++默認是0x00400000.可以用GetModuleHandle()得到這個值
PTSTR GetCommandLine()獲得命令行
PWSTR CommandLineToArgvW()分解命令行
每個進程都有一個與他相關的環境塊。
VarName1=VarValue1\0
VarName2=VarValue2\0
...............
\0
GetEnvironmentVariable() //獲得環境變量值
ExpandEnvironmentStrings()//展開%包裹的環境變量值
SetEnvironmentVariable() //設定環境變量
進程的親緣性:進程的線程被強迫再CPU的子集上運行。
子進程默認繼承父進程的錯誤標志。
SetErrorMode(UINT) //設定錯誤模式
進程維護當前驅動器和目錄信息
GetCurrentDirectory()
SetCurrentDirectory()
獲得系統版本:
GetVersion()
GetVersionEx()
VeryfyVersionInfo()
GetExitCodeProcess(),對于還在運行的進程,可以得到0x103(STILL_ACTIVE),對于終止的進程,如果還沒有CloseHandle(pi.hProcess),可以得到它的退出碼,否則得到的是亂碼。
windows2000支持作業管理, 通過將進程加入作業,可以對進程的運行權限,使用的資源進行限制。方法如下:
HANDLE hjob=CreateJobObject(NULL,NULL);//創建一個作業對象。
SetInformationJobObject();//設定作業對象的參數,包括對進程的各種限制。
CreateProcess(NULL,"CMD",NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);//創建新進程。
AssignProcessToJobObject(hjob,pi.hProcess); //將進程加入作業。可加入多個。
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
HANDLE h[2];
h[0]=pi.hProcess;
h[1]=hjob;
DWORD dw=WaitForMultipleObject(2,h,false,INFINITE);
switch(dw-WAIT_OBJECT_0)
case 0://the process has terminated..
case 1://all of the job's allotted cpu time was used.
}
CloseHandle(pi.hProcess);
CloseHandle(hjob);
終止作業中所有進程的運行
TerminateJobObject(hjob,UINT uExitCode)
查詢作業統計信息
QueryInformationJobObject();
監視作業的運行:
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;//創建一個I/O完成端口對象
SetInformationJobObject(hjob.JobObjectAssociateCompletionPortInformation,&joacp,sizeof(joacp)
//將作業同完成端口對相關聯。
GetQueuedCompletionStatus() //監控I/O端口。
進程由兩部分組成:進程內核對象, 地址空間。進程是不活潑的,它的執行依賴于線程。
線程由兩部分組成:線程內核對象,線程堆棧。
創建新線程:
DWORD WINAPI FUNC(PVOID pvParam)
int Param;
DWORD dwThreadID;
CreateThread(NULL,0,FUNC,(PVOID)&Param,0,&dwThreadID);
檢查線程是否退出:
BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode);//如果還未終止,得到0x103.
獲得偽句柄:
GetCurrentProcess()
GetCurrentThread()
獲得運行時間:
GetProcessTimes()
GetThreadTimes()
線程或進程的偽句柄轉化為實句柄:
DuplicatgeHandle();//此函數會增加內核對象的引用計數。
偽句柄用于本線程,獲得這個句并不會影響內核對象的計數,而實句柄用于傳遞給子進程。
線程的暫停和運行:
ResumeThread(HANDLE)
SuspendThread(HANDLE) //使用此函數要小心死鎖。
線程休眠:
Sleep(DWORD dwMilliseconds);
自動退出當前時間片:
SwitchtoThread();
可以獲得和修改線程的上下文,使用之前要SuspendThread()
GetThreadContext()
SetThreadContext()
改變進程的優先級://記住進程是不可以調度的,調度的單位是線程。
BOOL SetPriorityClass();
DWORD GetPriorityClass();
設定線程的相對優先級:
int GetThreadPriority(HANDLE hThread);
BOOL SetThreadPriority(Handle hThread,int nPriority);
Microsoft保留了隨時修改調度算法的權利,因此使用相對優先級,可以保證程序在將來的系統上也可以正常運行。
結合進程優先級和線程的相對優先級,就可以得到線程的基本優先級。線程的當前優先級不可以低于基本優先級,
也就是說,系統會提高線程的優先級,并隨著執行時間片的流逝降低優先級,但降到基本優先級后就不再降了。
優先級0-15成為動態優先級范圍,高于15是實時范圍,系統不會調度實時范圍線程的優先級,也不會把動態優先級范圍的
線程提高到15以上。
親緣性是對多處理器系統來說的,為了能利用保留在cpu高速緩存和NUMA(非統一內存訪問)結構計算機本插件板上內存中的數據,系統盡量線程上次運行使用的CPU來運行線程,包括軟親緣性(WIN2000默認)和硬親緣性(用戶可以選擇CPU)
相關的函數有:
BOOL SetProcessAffinityMask(HANDLE hProcess,DWORD_PTR dwProcessAffinityMask);
BOOL GetProcessAffinityMask(Handle hProcess,PDWORD_PTR pdwProcessAffinityMask,PDWORD_PTR pdwSystemAffinityMask);
DWORD_PTR SetThreadAffinityMask(HANDLE hThread,DWORD_PTR dwThreadAffinityMask);
DWORD_PTR SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);
臨界區保證其中的資源(通常是各種共享變量)被原子的訪問,當進入臨界區后,其他訪問這些資源的線程將不會被調度。
線程同步包括用戶方式和內核方式,用戶方式包括原子操作和臨界區,它的特點是速度快,但功能有限。內核方式利用內核對象的通知狀態來同步線程,由于需要由用戶方式切換到內核方式(這種切換很廢時間),且系統要進行很多操作,效率較低,但功能強大(能夠設定超時值等,可以同步多個進程的線程)。
內核方式同步的原理:線程使自己進入休眠狀態,等待內核對象由未通知狀態變為已通知狀態。
可處于未通知狀態變和已通知狀態的內核對象:進程,線程,作業,文件修改通知,時間,可等待定時器,文件,控制臺輸入,信號量,互斥體。
進程和線程在建立時處于未通知狀態,在退出時變為已通知狀態。
等待函數:
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
DWORD WaitForMultipleObject(DWORD dwCount,CONST HANDLE* phObjects,BOOL fWaitALL,DWORD dwMilliseconds);其中,
0<dwCount<WAIT_OBJECTS(windows頭文件中定義為64),如果設定fWaitALl為TRUE,那么函數會知道左右對象變為已通知狀態才會返回,如果傳遞FALSE,那么只要有一個對象變為已通知狀態,函數就會返回。
返回值的含義:
HANDLE h[3];
h[0]=hProcess1;
h[1]=hProcess2;
h[2]=hProcess3;
DWORD dw=WaitForMultipleObject(3,h,FALSE,5000);
switch(dw)
{
case WAIT_FAILED://Bad call to function(invalid handle?)
break;
case WAIT_TIMEOUT://None of the object became signaled within 5000 milliseconds.
break;
case WAIT_OBJECT_0+0:The process identified by h[0] terminated.
break;
case WAIT_OBJECT_0+1:
break;
case WAIT_OBJECT_0+2:
break;
}
//WaitForSingleObject()的返回值只有前三種情況。如果給WaitForMutipleObject()的fWaitAll參數傳遞TRUE,那么其返回值也只有前三種。
事件內核對象:有兩種,人工事件對象:當它得到通知的時候,所有等待的線程都變為可調度線程;自動重置的事件:當事件得到通知的時候,只有一個等待線程變為可調度的線程。創建事件內核對象:
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,BOOL fInitialState,PCTSTR pszName);
將事件改為通知狀態:
BOOL SetEvent(HANDLE hEvent);
將事件改為未通知狀態:
BOOL ResetEvent(HANDLE hEvent);
如果事件是自動重置事件,那么成功等待會產生副作用,即將事件自動置為未通知狀態。如果是人工事件對象,則沒有副作用。
等待定時器內核對象:是在某個時間或按規定的間隔時間發出自己的信號通知的內核對象。
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,PCSTR pszName);
初始總是未通知狀態。
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime,
LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL fResume);
取消定時器:
BOOL CancelWaitableTimer(HANDLE hTimer);
如果僅想改變報時條件,不用調用這個函數暫停報時器,直接調用SetWaitableTimer()就可以了。
信號量內核對象
如果當前資源的數量大于0,發出信號
如果當前資源數量等于0,不發出信號
決不允許資源數量為負值。
創建信號量:
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,
LONG lInitialCount,
LONG lMaximumCount,
PCSTR pszName);
遞增資源:
BOOL ReleaseSemaphore(HANDLE hsem,
LONG lReleaseCount,
PLONG plPreviousCount);
互斥體內核對象:互斥體確保對單個資源的互斥訪問。它包含一個使用數量,一個線程ID,一個遞歸計數器
與臨界區的區別:能夠同步多個進程中的線程,可以設定超時值。
如果ID為0,那么表示沒有線程占用互斥體,互斥體發出信號。
如果ID不為0,表示占用資源的線程ID,不發出信號。
HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
釋放資源:
BOOL ReleaseMutex(HANDLE hMutex);
額外的函數:
DWORD SingalObjectAndWait(
HANDLE hObjectToSignal,
HANDLE hObjectToWaitOn,
DWORD dwMilliseconds,
BOOL fAlertable);
發出一個通知信號并等待另一個通知,效率比分別操作提高很多。
windows2000提供了如下幾種線程池函數用于線程管理:
一、異步調用函數:
BOOL QueueUserWorkItem(
PTHREAD_START_ROUTINE pfnCallback,
PVOID pvContext,
ULONG dwFlags);
該函數將“工作項目”放入線程池并且立即返回。工作項目是指一個用pfnCallback參數標識的函數。它被調用并且傳遞單個參數pvContext.工作項目函數原型如下:
DWORD WINAPI WorkItemFunc(PVOID pvContext);
dwFlags參數:WT_EXECUTEDEFAULT 工作項目放入非I/O組件得線程中
WT_EXECUTEINIOTHREAD 工作項目放入I/O組件的線程中,這樣的線程在I/O請求沒有完成之前不會被終止運行 ,防止因為線程被終止導致I/O請求丟失。
WT_EXECUTEINPERSISTENTTHREAD 放入永久線程池,
WT_EXECUTELONGFUNCTION 工作項目需要長時間的工作,系統會據此安排更多的線程。
線程池不能設置線程個數的上限,否則排隊個數超過線程個數上限的時候,會導致所有的線程都被中斷。
工作項目函數如果訪問了已經被卸載的DLL,會產生違規訪問。
二、按規定的時間間隔調用函數
創建定時器隊列:
HANDLE CreateTimerQueue();
在隊列中創建定時器:
BOOL CreateTimerQueueTimer(
PHANDLE phNewTimer,
HANDLE hTimerQueue,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
DWORD dwDueTime,
DWORD dwPeriod,
ULONG dwFlags);
工作回調函數原型如下:
VOID WINAPI WaitOrTimerCallback(
PVOID pvContext,
BOOL fTimerOrWaitFired);
dwFlags比前面的多了一個標志:WT_EXECUTEINTIMERTHREAD,表示由組件的定時器線程(定時器組件只有一個線程)運行這個
工作函數,此時的工作函數必須是很快返回的,否則定時器組件將無法處理其他的請求。
刪除定時器:
BOOL DeleteTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
HANDLE hCompletionEvent);
在定時器線程中刪除定時器會造成死鎖。設定hCompletionEvent為INVALID_HANDLE_VALUE,那么在定時器的所有排隊工作項目沒有完成之前,DeleteTimerQueueTimer不會返回,也就是說在工作項目中對定時器進行中斷刪除會死鎖??梢越ohCompletionEvent傳遞事件句柄,函數會立即返回,在排隊工作完成之后,會設置該事件。
重新設定定時器://不能修改已經觸發的單步定時器。
BOOL ChangeTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
ULONG dwDueTime,
ULONG dwPeriod;
刪除定時器隊列:
BOOL DeleteTimerQueueEx(
HANDLE hTimerQueue,
HANDLE hCompletionEvent);
三、當單個內核對象變為已通知狀態時調用函數
BOOL RegisterWaitForSIngleObject(
PHANDLE phNewWaitObject,
HANDLE hObject,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
ULONG dwMilliseconds,
ULONG dwFlags);
pfnCallBack原型:
VOID WINAPI WaitOrTimerCallbadkFunc(
PVOID pvContext,
BOOLEAN fTimerorWaitFired);
如果等待超時,fTimerorWaitFired==TRUE,如果是已通知狀態,則為FALSE.
dwFlags可以傳遞參數:WT_EXECUTEINWAITTHREAD,它讓等待組件得線程之一運行工作項目函數。注意項同前。
如果等待的內核對象是自動重置的,那么會導致工作函數被反復調用,傳遞WT_EXECUTEONLYONCE會避免這種情況。
取消等待組件的注冊狀態:
BOOL UnregisterWaitEx(
HANDLE hWaitHandle,
HANDLE hCompletionEvent);
四、當異步I/O請求完成時調用函數
將設備和線程池的非I/O組件關聯
BOOL BindIoCompletionCallback(
HANDLE hDevice,
POVERLAPPED_COMPLETION_ROUTINE pfnCallback,
ULONG dwFlags//始終為0);
工作函數原型:
VOID WINAPI OverlappedCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
,
POVERLAPPED pOverlapped);
Windows的內存結構
從98,2000,到64位的windows,內存管理方式都是不同的,32位的win2000用戶內存是從0x10000到0x7fffffff(64kB-2G),2000 Advanced server可以達到(64kB-3G),其中最高64kB也是禁止進入的。再往上則由系統使用。98則是從0x400000-0x7fffffff(4M-2G),2G-3G是系統用來存放32位共享數據的地方,如很多系統動態連接庫。0-4M是為了兼容16位程序保留的。3G-4G由系統自身使用。98的內核區是不受保護的,2000受保護。
對虛擬地址空間的分配稱作保留,使用虛擬內存分配函數(VirtualAlloc),釋放使用VirtualFree(),目前,所有cpu平臺的分配粒度都是64kB,頁面大小則不同,x86是4kB,Alpha是8kB,系統在保留內存的時候規定要從分配粒度邊界開始,并且是頁面的整數倍,用戶使用VirtualAlloc都遵守這個規定,但系統不是,它是從頁面邊界開始分配的。
將物理存儲器映射到保留的內存區域的過程稱為提交物理存儲器,提交是以頁面為單位進行的,也使用VirtualAlloc函數。
物理存儲器是由內存和(硬盤上的)頁文件組成的,如果訪問的數據是在頁文件中,則稱為頁面失效,cpu會把訪問通知操作系統,操作系統負責將數據調入內存,并指導cpu再次運行上次失效的指令。
當啟動一個程序的時候,系統并不是將整個文件讀入內存或者頁文件,而是將這個文件直接映射到虛擬內存空間,并將需要的數據讀入內存,即將硬盤上的文件本身當作頁文件(雖然不是)。當硬盤上的一個程序的文件映像(這是個exe文件或者dll文件)用作地址空間的物理存儲器,它稱為內存映射文件。當一個.exe或者dll文件被加載時,系統將自動保留一個地址空間的區域,并將該文件映射到該區域中。但系統也提供了一組函數,用于將數據文件映射到一個地址空間的區域中。
物理存儲器的頁面具有不同的保護屬性:
PAGE_NOACESS
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
PAGE_WRITECOPY
PAGE_EXECUTE_WRITECOPY
后兩個屬性是配合共享頁面機制使用的。WINDOWS支持多個進程共享單個內存塊,比如運行notepad的10個實例,可以讓他們共享應用程序的代碼和數據,這樣可以大大提高性能,但要求該內存塊是不可寫的。于是系統在調入.exe或者dll的時候,會計算那些頁面是可以寫入的,為這些頁面分配虛擬內存。然后同其他的頁面一起映射到一塊虛擬內存,但賦PAGE_WRITECOPY或者PAGE_EXECUTE_WRITECOPY屬性(通常包含代碼的塊是PAGE_EXECUTE_READ,包含數據的塊是PAGE_READWRITE)。當一個進程試圖將數據寫入共享內存塊時,系統會進行如下操作:尋找預先分配的一個空閑頁面,將試圖修改的頁面拷貝到這個空閑頁面,賦予PAGE_READWRITE或者PAGE_EXECUTE_READWRITE屬性,然后更新進程的頁面表,使得用戶可以對新的頁面進行寫入。
還有三個特殊的保護屬性:PAGE_NOCACHE PAGE_WRITECOMBINE PAGE_GUARD,前兩個用于驅動程序開發,最后一個可以讓應用程序在頁面被寫入的時候獲得一個異常。
塊的意思是一組相鄰的頁面,它們具有相同的保護屬性,并且受相同類型的物理存儲器支持。
賦予虛擬內存頁面保護屬性的意義是為了提高效率,而且這個屬性總會被物理存儲器的保護屬性取代。
如果數據在內存中沒有對齊,那么cpu要多次訪問才能得到數據,效率很低。
內存管理函數:
獲得系統信息:
VOID GetSystemInfo(LPSYSTEM_INFO psinf);//可以得到頁面大小,分配粒度,最大內存地址,最小內存地址。
獲得內存狀態:
VOID GlobalMemoryStatus(LPMEMORYSTATUS pmst);
獲得內存地址的某些信息:
DWORD VirtualQuery(
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);
DWORD VirtualQuery(
HANDLE hProcess,
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);
內存映射文件的優點:
1 節省頁面文件;
2 加快程序啟動;
3 在多個進程間共享數據。
進程的啟動過程:
系統首先將.exe文件映射到地址空間,缺省基地址是0x400000,然后查詢.exe的輸入表,將其使用的所有.dll也映射到地址空間(基地址在每個.dll文件中,如果不能滿足,需要重定位),然后將執行.exe的啟動代碼。此時.exe文件還在硬盤上。每次代碼跳到一個尚未加載到內存的指令地址,就會出現一個錯誤,系統會發現這個錯誤,并將代碼加再到內存中。
如果再創建這個.exe文件的一個實例。那么直接將原來的地址空間中的內容映射到新的地址空間就可以了。這樣多個實例就可以共享相同的代碼和數據。如果某個實例要改變共享內容,系統就為要更改的頁面申請一個新的頁面,將內容拷貝一份,然后用新的頁面代替地址空間中原來頁面的映射就可以了。98同2000不同,它不待修改便立即為所有的實例分配新的頁面。
使用內存映射文件:
1 創建或打開一個文件內核對象:
HANDLE CreateFile(
PCSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
失敗的返回值是INVALID_HANDLE_VALUE
2 創建一個文件映射內核對象:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
如果給函數的fdwProtect傳遞PAGE_READWRITE標志,那么磁盤上文件的大小會變為同映像文件相同大小。
失敗的返回值是NULL。
3 將文件映射到進程的地址空間:
PVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);
windows2000會根據要求將部分文件映射到地址空間,而win98總是把全部內容映射到地址空間,并且僅能映射到2G-3G空間,此空間為共享空間,所有的進程如果映射相同的文件,那么都會映射到相同的地址,一個進程甚至不必映射就可以訪問這個空間里其他進程的映射文件,win2000多個進程映射同一個文件返回的地址通常是不同的。
4 從進程的地址空間中撤銷文件數句的映像
BOOL UnmapViewOfFile(PVOID pvBaseAddress);
將文件映像寫入磁盤:
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush);
windows保證單個文件映射對象的多個視圖具有相關性。但不保證但個文件的多個映射對象有相關性。
使用MapViewOfFileEx代替MapViewOfFile可以設定文件映射的基地址:
PVOID MapViewOfFileEx(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
PVOID pvBaseAddress);
使用內存映射文件在進程間共享數據
共享機制:RPC ,COM,OLE,DDE,窗口消息(WM_COPYDATA),剪貼板,郵箱,管道,套接字。
在單機上,它們的底層實現方法都是內存映射文件。
可以在頁文件中直接創建文件映射對象,方法是給CreateFileMapping函數的hFile參數傳遞INVALID_HANDLE_VALUE.注意,
CreateFile()函數運行失敗也會返回這個參數,因此一定要檢查CreateFile()的返回值。記住,文件函數運行失敗的可能性太大了。
第三章:多個進程共享對象。
堆棧:優點:可以不考慮分配粒度和頁面邊界之類的問題,集中精力處理手頭的任務,缺點是:分配和釋放內存塊的速度比其他機制慢,并且無法直接控制物理存儲器的提交和回收。
進程的默認堆棧是1MB,可以使用/HEAP鏈接開關調整大小,DLL沒有相關的堆棧。
堆棧的問題在于:很多windows函數要使用臨時內存塊,進程的多個線程要分配內存塊,這些內存都是在默認堆棧上分配的,但規定時間內,每次只能由一個線程能夠分配和釋放默認堆棧的內存塊,其他想要處理內存塊的線程必須等待。這種方法對速度又影響??梢詾檫M程的線程創建輔助堆棧,但windows函數只能使用默認堆棧。
獲取進程默認堆棧句柄:
HANDLE GetProcessHeap();
創建輔助堆棧的理由
1 保護組件:
多個組件的數據混合交叉的存放在一塊內存里,那么一個組件的錯誤操作很容易影響到另外一個組件。而要定位錯誤的來源將十分困難。
2 更有效的內存管理
通過在堆棧中分配同樣大小的對象,可以更加有效的管理內存,減少內存碎片。
3 進行本地訪問:
將同種數據集中到一定的內存塊,可以在操作的時候訪問較少的頁面,這就減少了RAM和硬盤對換的可能.
4 減少線程同步的開銷:
通過告訴系統只有一個線程使用堆棧(創建堆棧時使用HEAP_NO_SERIALIZE標志給fdwOptions),可以避免堆棧函數執行額外的用于保證堆棧安全性的代碼,提高效率,但此時用戶必須自己維護線程的安全性,系統不再對此負責。
5 迅速釋放堆棧。
因為數據單一,因此釋放的時候只要釋放堆棧即可,不必顯示的釋放每個內存塊。
創建輔助堆棧:
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);
從堆棧中分配內存:
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwBytes);注意:當分配超過(1MB)內存塊的時候,最好使用VirtualAlloc();
改變內存塊的大小:
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,
SIZE_T dwBytes);
檢索內存塊的大?。?br>SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPVOID pvMem);
釋放內存塊:
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);
撤銷堆棧:
BOOL HeapDestroy(HANDLE hHeap);
使用輔助堆棧的方法:重載對象的new操作符,在輔助堆上分配內存,并給對象添加一個靜態變量用于保存堆句柄。
其它堆棧函數:
獲取進程中所有堆棧得句柄:
DWORD GetProcessHeaps(DWORD dwNumHeaps,PHANDLE pHeaps);
驗證堆棧完整性:
BOOL HeapValidate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
合并地址中的空閑塊
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags);
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE
遍歷堆棧:
BOOL HeapWalk(
HANDLE hHeap,
PProcess_HEAP_ENTRY pHeapEntry);
各個dll也可以有自己的輸入表。
如何編寫DLL:
在DLL的頭文件中,有如下代碼:
#ifdef MYLIB
#else
#define MYLIB extern "C" __declspec(dllimport)
#endif
在每個輸出變量和輸出函數的聲明前,用MYLIB修飾。
在DLL的實現文件中,有如下代碼:
#i nclude "windows.h"
#define MYLIB extern "C" __declspec(dllexport)
#i nclude "Mylib.h"
其它的同編寫普通C++程序完全相同。 "C" 表示按C方式鏈接和調用函數。C++編譯器缺省按照__stdcall方式編譯和調用,這種方式會改變函數的內部名字。此處如果把"C"都去掉也可以,但C程序將無法調用。另外,使用GetProcAddress函數時也會發生困難,因為
編譯程序已經把函數名字改變了,無法用原來的名字得到函數地址。(核心編程說的不明白,沒想到這本書錯誤這么多)
發行的時候,將頭文件、.lib文件和DLL文件給用戶就可以了。lib文件的作用是說明了頭文件中函數所在的DLL文件,如果沒有lib文件,編譯器將在鏈接過程中提示錯誤:unresolved external symbol 函數名。
事實上,調用DLL有兩種方式,第一種是比較常用,即包含DLL的頭文件,并在鏈接的時候將動態鏈接庫同exe文件像連接,建立輸入表。這個時候需要.lib文件。第二種方法exe文件中沒有輸入表,程序使用LoadLibrary(Ex)和GetProcAddress()顯式的加載DLL文件(卸載用FreeLibrary()),這個時候不需要.lib文件。
HINSTANCE LoadLibrary(PCTSTR pszDLLpathName);
HINSTANCE LoadLibraryEx(PCTSTR pszDLLpathName,NULL,0);
兩次調用LoardLibrary并不會裝載兩次dll文件,只是將dll映射進進程的地址空間。系統會自動為每個進程維護一個dll的計數。FreeLiabray會使計數減一,如果計數為0,系統就會將dll從進程的地址空間卸載。
HINSTANCE GetModuleHandle(PCTSTR pszModuleName);//確定dll是否已經被映射進地址空間。
HINSTANCE hinstDll=GetModuleHandle("MyLib");
if(hinstDll==NULL)
{
hinstDll=LoadLibrary("MyLib");
}
DWORD GetModuleFileName(
HINSTANCE hinstModule,
PTSTR pszPathName,
DWORD cchPath
}
可以獲得某個模塊(.exe或者dll)的全路徑名。
幾個函數的用法:(注意GetProcAddress()函數的用法,如何定義和使用一個函數指針)
typedef int (*MYPROC)(int,int);
int main()
{
HINSTANCE t;
t=LoadLibraryEx(TEXT("tt.dll"),NULL,0);
if(t)
{
cout<<TEXT("load success")<<endl;
}
HINSTANCE hinstDll=GetModuleHandle("tt.dll");
if(hinstDll==NULL)
{
cout<<TEXT("first load failed")<<endl;
hinstDll=LoadLibrary("MyLib");
}
size_t sz=100;
PTCHAR str=new TCHAR[sz];
GetModuleFileName(t,str,sz);
cout<<str<<endl;
delete str;
MYPROC add=NULL;
add=(MYPROC)GetProcAddress(t,"add");
if(NULL!=add)
{
cout<<(*add)(1,2)<<endl;
}
FreeLibrary(t);
return 0;
}
UNICODE
ANSI/UNICODE通用的定義方法(轉換只需要在編譯的時候使用_UNICODE和UNICODE):
TCHAR _TEXT("success") PTSTR PCTSTR _tcscpy(),_tcscat();
使用BYTE PBYTE定義字節,字節指針和數據緩沖。
傳遞給函數的緩存大?。簊izeof(szBuffer)/sizeof(TCHAR)
給字符串分配內存:malloc(nCharacters*sizeof(TCHAR));
其它的字符串函數:
PTSTR CharLower(PTSTR pszString);
PTSTR CharUpper(PTSTR pszString);
轉換單個字符:
TCHAR c=CharLower((PTSTR)szString[0]);
轉換緩存中的字符串(不必以0結尾):
DWORD CharLowerBuff(
PTSTR pszString,
DWORD cchString);
DWORD CharUpperBuff(
PTSTR pszString,
DWORD cchString);
BOOL IsCharAlpha(TCHAR ch);
BOOL IsCharAlpahNumeric(TCHAR ch);
BOOL IsCharLower(TCHAR ch);
BOOL IsCharUpper(TCHAR ch);
線程本地存儲(TLS):為進程的每個線程存儲私有數據。用于那些一次傳遞參數后多次調用的函數(函數會保存上次調用的數據)。
實現方法:進程中有一個位標志樹組(win2000的這個數組大小超過1000)。在每個線程中有一個對應的PVOID數組。通過設定位標志樹組的某個位來分配每個線程中的PVOID數組得相應單元。函數需要每次檢索線程的PVOID數組,獲得該線程的相應數據。
DWORD TlsAlloc(); //為每個線程分配一個空的PVOID數組單元。
BOOL TlsSetValue( //線程設定自己的PVOID數組單元。
DWORD dwTlsIndex,
PVOID pvTlsValue);
PVOID TlsGetValue(
DWORD dwTlsIndex); //檢索PVOID數組。
BOOL TLSFree(
DWORD dwTlsIndex); //釋放PVOID數組單元
靜態TLS:__declspec(thread) DWORD gt_dwStartTime=0;//只能修飾全局或者靜態變量。
DLL掛接(進程注入):讓自己的DLL插入到其他進程的地址空間。
1 使用注冊表插入DLL
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
將你的DLL路徑放入這個關鍵字下面。當User32.dll被映射到進程中的時候,它會加在這個關鍵字下的每個庫。
注意:(1)對win98無效
(2)由于加載事間比較早,你的DLL可能無法調用kernel32以外的dll.
(3) 如果進程沒有使用user32.dll,這個方法無效。
(4) 需要重新啟動。
(5)user32不會檢查每個庫是否加載成功。
2 使用windows鉤子
HOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);
BOOL UnhookWindowsHookEx(HHOOK hhook);
具體過程如下:
(1)進程B的一個線程準備發送消息給一個窗口。
(2)系統察看線程上是否已經安裝了WH_GETMESSAGE鉤子。
(3)系統察看GetMsgProc的DLL是否已經映射到了進程B的地址空間,如果沒有,系統將把DLL映射到B的地址空間,并自動增加引用計數。
(4)調用GetMsgProc函數,返回時,系統會自動為DLL的引用計數減一。
3 使用遠程線程來插入DLL
(1)使用VirtualAllocEx,分配遠程進程的地址空間的內存。
(2)使用WriteProcessMemory,將Dll的路徑名拷貝到第一個步驟中已經分配的內存中。
(3)使用GetProcAddress,獲得LoadLibrary的實際地址。
(4)使用CreateRemoteThread,在遠程進程中創建一個線程。
退出:
(5)使用VirtualFreeEx,釋放內存
(6)使用GetProcAddress,獲得FreeLiabary的地址。
(7)使用CreateRemoteThread,在遠程進程中創建一個線程,調用FreeLiabary函數。
4 使用特洛伊DLL插入
替換dll.
5 將DLL作為調試程序插入
6 win98內存映射文件,creatprocess
結構化異常處理:
結束處理程序:__try{} __finally{}
除非__try執行中進程或者線程結束,否則總會執行__finally,并且__finally中的return會替代__try中的return;好的習慣是將return ,continue,break,goto語句拿到結構化異常處理語句外面,可以節省開銷。將__try中的return 換成__leave,可以節省開銷。在__finally總確定時正常進入還是展開進入:
BOOL AbnormalTermination();//正常進入返回FALSE,局部展開或者全局展開返回TRUE;
異常處理程序:__try{}__exception(異常過濾表達式){}
EXCEPTION_EXECUTE_HANDLE
表示處理異常,處理后轉到exception塊后面的代碼繼續執行。
EXCEPTION_CONTINUE_EXECUTION
EXCEPTION_CONTINUE_SEARCH
可以對異常過濾表達式進行硬編碼,也可以用一個調用一個函數來決定過濾表達式,函數的返回值是LONG.例如,可以進行一定處理,然后返回EXCEPTION_CONTINUE_EXECUTION,再次執行出錯語句。但可能再次出錯,因此這種方法必須小心,防止生成死循環。
DWORD GetExceptionCode() 可以獲得異常種類。它只能在__except后的括號或者異常處理程序中調用。
發生異常后,操作系統會像引起異常的線程的棧里壓入三個結構:EXCEPTION_RECORD CONTEXT EXCEPTION_POINTERS,其中第三個結構包含兩個成員指針,分別指向前兩個結構,使用函數可以獲得第三個結構的指針:
PEXCEPTION_POINTERS GetExceptionInformation();//僅可以在異常過濾器中調用,既__exception后面的小括號。
逗號表達式:從左到右對所有的表達式求值,并返回最有面的表達式的值。
引發軟件異常:
VOID RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST ULONG_PTR *pArguments);
缺省調試器所在注冊表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug Debugger
win98是存放在win.ini里
調試器掛接到被調試進程
BOOL DebugActiveProcess(DWORD dwProcessID);
while(*str++!='\0');
應該注意的是在不滿足條件后,str仍然會自加1。
位操作符>>和<<不會做循環位移,即不會把移出的位放到另一頭。
多態性和動態聯編的實現過程分析
一、基礎:
1、多態性:使用基礎類的指針動態調用其派生類中函數的特性。
2、動態聯編:在運行階段,才將函數的調用與對應的函數體進行連接的方式,又叫運行時聯編或晚捆綁。
二、過程描述:
1、編譯器發現一個類中有虛函數,編譯器會立即為此類生成虛擬函數表 vtable(后面有對vtable的分析)。虛擬函數表的各表項為指向對應虛擬函數的指針。
2、編譯器在此類中隱含插入一個指針vptr(對vc編譯器來說,它插在類的第一個位置上)。
有一個辦法可以讓你感知這個隱含指針的存在,雖然你不能在類中直接看到它,但你可以比較一下含有虛擬函數時的類的尺寸和沒有虛擬函數時的類的尺寸,你能夠發現,這個指針確實存在。
3、在調用此類的構造函數時,在類的構造函數中,編譯器會隱含執行vptr與vtable的關聯代碼,將vptr指向對應的vtable。這就將類與此類的vtable聯系了起來。
4、在調用類的構造函數時,指向基礎類的指針此時已經變成指向具體的類的this指針,這樣依靠此this指針即可得到正確的vtable,從而實現了多態性。在此時才能真正與函數體進行連接,這就是動態聯編。
定義純虛函數方法:virtual returntype function()= 0;
所有的類型都可以用new動態創建,包括類,結構,內置數據類型。
exit()函數包含在"cstdlib"中
泛型編程,使用STL庫
總共有近75個泛型算法
所有容器的共通操作:
== != = empty() size() clear(),begin(),end(),以及insert和erase,不過后者隨容器的的不同而不同
序列式容器:
vector(數組):插入和刪除的效率較低,但存取效率高。
list(雙向鏈表):與前者相反,插入和刪除的效率較高,但存取效率低。每個元素包含三個字段:value back front.
deque(隊列):在前端和末尾操作效率高。
生成序列式容器的五種方法:
1 產生空的容器:
list<string> slist;
vector<int> vtor;
2 產生特定大小的容器,容器中的每個元素都以其默認值為初值(發現VC中的int double等沒有默認值)。
list<int> ilist(1024);
vector<string> svec(24);
3 產生特定大小的容器,并為每個元素指定初值:
list<int ilist(1024,0);
vector<string> svec(24,"default");
4 通過一對迭代器產生容器,這對迭代器用來表示數組作為初值的區間:
int ia[10]={1,2,3,4,5,6,7,8,9,0};
vector<int> iv(ia+2,ia+8);
5 復制某個現有的容器的值:
vector<int> ivec1;
//填充ivec1;
vector<int> ivec2(ivec1);
有6個方法用于操作開始和末尾的元素:push_front() pop_front() push_back() pop_back(),由于pop操作僅刪除元素而不返回元素,因此還需要front() back()方法取開始和末尾的元素,另外,vector不包括push_front() pop_front方法,很顯然,無法實現。
intert的四種變形:
iterator insert(iterator position,elemType value):將value插到position前。返回值指向被插入的元素。
void insert(iterator position,int count,elemType value):在position前插入count個元素,每個元素都是value.
void insert(iterator1 position,iterator2 first,iterator2 last):將first,last之間的元素插到position前.
iterator insert( iterator position):在position前插入元素,初值為所屬類型的默認值。
erase的兩種變形:
1 iterator erase(iterator posit):刪除posit指向的元素。
list<string>::iterator it=find(slist.begin(),slist,end(),str);
slist.erase(it);
2 iterator erase(iterator first,iterator last):刪除first,last間的元素。
list不支持iterator的偏移運算
對于常值容器,使用常迭代器:
const vector<string> cs_vec;
vector<string::const_iterator iter_cs_vec.begin();
iterator可以當作指針用,可以用*取內容,也可以用->調用對象的成員。
使用泛型算法
#i nclude <algorithm>
find():線性搜索無序集合
binary_search():二分搜索有序集合。
count():返回元素個數。
search():搜索序列,如果存在返回的iterator指向序列首部,否則指向容器末尾。
max_element(begin,end):返回區間內的最大值。
copy(begin,end,begin):元素復制。
sort(begin,end):排序。
function objects:#i nclude <functional>
算術運算:
plus<type> minus<type> negate<type> multiplies<type> divides<type> modules<type>
關系運算:
less<type> less equal<type> greater<type greater equal<type> equal_to<type> not_equal_to<type>
邏輯運算:
logical_and<type> logical_or<type> logical_not<type>
adapter:適配器。
bind1st:將數值綁定到function object的第一個參數。
bind2nd:將數值綁定到function object的第二個參數。
使用map:
#i nclude <map>
#i nclude <string>
map<string,int> words;
words["vermeer"]=1;
map<string,int>::iterator it=words.begin();
for(;it!=words.end();++it)
cout<<"key:"<<it->first<<"value:"<<it->second<<endl;
查找map元素的方法:
words.find("vermeer");//返回iterator,指向找到的元素,找不到返回end();
還可以:
if(words.count(search_word))
count=words[search_word];
使用set:
#i nclude <set>
#i nclude <string>
set<string> word_exclusion;
//判斷是否存在某個元素
if(word_exclusion.count(tword))
//默認情況下,所有元素按less-than運算排列
//加入元素
iset.insert(ival);
iset.insert(vec.begin(),vec.end());
與set相關的算法
set_intersection() set_union() set_difference() set_symmetric_difference()
使用insertion adapters:
#i nclude <iterator>
back_inserter()
inserter()
front_inserter()
使用STL通常會有很多警告,為了避免在調試模式(debug mode)出現惱人的警告,使用下面的編譯器命令:
#pragma warning(disable: 4786)
strncpy(dest,source,count) if(count〈=strlen(source)),那么null結尾不會被加在dest的尾部,如果count>strlen(source),那么不足的部分會用null填充。
windows內存是由高地址向底地址分配的,但變量的存儲是從底地址到高地址的,如INT類型的四個字節,數組的每個元素。
內存復制的時候不能用字符串拷貝函數,因為即使使用strncpy指定了復制的長度,拷貝函數也會遇到'\0'自動終止,要使用MEMSET。
由于對齊的關系,下面兩個結構使用sizeof,前者是12,后者是16。
struct DNSAnswer
{
unsigned short name;
unsigned short type;
unsigned short cla;
unsigned short length;
unsigned int ttl;
};
struct DNSAnswer
{
unsigned short name;
unsigned short type;
unsigned short cla;
unsigned int ttl;
unsigned short length;
};
子類可以使用父類的保護成員,而友元比子類的權限還大,可以使用類的私有和保護成員。
在內存分配失敗的情況下,系統只有在出錯處理函數為空的情況下,才會拋出異常:std::bad_alloc(),否則會反復調用處理函數并再次嘗試分配內存。
如果重載了NEW,那么在繼承的時候要小心,如果子類沒有覆蓋NEW,那么它會去使用父類的NEW ,因此應該在new,delete中做檢查
if (size != sizeof(base)) // 如果數量“錯誤”,讓標準operator new,base為類名
return ::operator new(size); // 去處理這個請求
if (size != sizeof(base)) { // 如果size"錯誤",
::operator delete(rawmemory); // 讓標準operator來處理請求
return;
}
c++標準規定,要支持0內存請求(分配一個字節),并且可以刪除NULL指針(直接返回)。
在創建線程的時候,傳遞的變量一定要是全局或者靜態的變量,因為傳遞的是變量的地址,如果是局部變量地址很快就會失效。
主線程退出后,其子線程自動結束。
智能指針:它可以避免內存泄露,因為智能指針是在棧上創建的;還可以避免堆上內存的重復釋放錯誤,因為它保證只有一個指針擁有這塊內存的所有權。