不懂就要學,windows編程

來源:互聯網
上載者:User
. 遠端程序呼叫RPC
基本過程如下:
(1) 調用者調用本地stub中的一個過程
(2) 這個stub過程把有關的參數組裝成一個訊息包或一組訊息包, 形成一條訊息. 運行此執行過程的遠程場點的IP地址和執行該過程的進程ID號也包含在這條訊息中.
(3) 將這條訊息發送給對應的RPC runtime(RPC運行庫)子程式, 由這個子程式將訊息發送到Server.
(4) 在接收到這條訊息後, server端的RPC runtime子程式引用與被調用者對應的stub中的一個子程式, 並讓它來處理訊息.
(5) 與被調用者對應的stub中的這個子程式解壓訊息, 解析出相關參數, 並用本地調用方式執行所指定的過程.
(6) 返回調用結果, 調用者對應的stub子程式執行return語句返回到使用者, 整個RPC過程結束.

. 實現安全執行緒的dll
線程局部儲存TLS
Windows作業系統提供了Process/Thread的程式模型,其中Process是資源的指派至
,掌握了程式所擁有的資源,而Thread則代表了程式的運行,是作業系統調度的對象。需
要注意,作業系統中,這兩種東西都是一種KERNEL32對象。分別由Process DataBase和Th
read DataBase來表示。具體可以參考Matt Petrik的Windows 95 Programing Secret。

    Thread Local Storage是一個實現Thread的全域資料的機制,並且這些資料僅僅在這
個Thread中可見,因為這些資料儲存在該Thread的Thread DataBase中:在每一個Thread
DataBase中都定義了一個64元的DWORD數組用來儲存這些資料。同時作業系統也提供了相應
的函數來完成對這些資料的操作,如:TlsAlloc,TlsFree,TlsSetValue,TlsGetValue。

    在MFC中,也提供了TLS功能,為此MFC設計了一系列的類和程式來完成這個任務。具體
的程式在afxtls.cpp和afxtls_.h中。
涉及到的主要的類有:
class CTypedSimpleList : public CSimpleList
struct CThreadData : public CNoTrackObject
struct CSlotData
class CThreadSlotData
class CThreadLocal : public CThreadLocalObject
    其中CThreadSlotData是封裝TLS的最重要的類,CTypedSimpleList,CSlotData,CTh
readDAta都是為了封裝TLS而設計的只具有協助工具功能的類。CThreadLocal是更高層的封裝。

    首先讓我們來對其資料封裝方式進行分析,重要的類的定義及其分析如下所示:(為簡
單起見,只列出資料成員而不再列出函數成員)
定義:
class CThreadSlotData
{
public:
DWORD m_tlsIndex;
int m_nAlloc;   
int m_nRover; 
int m_nMax;   
CSlotData* m_pSlotData;
CTypedSimpleList<CThreadData*> m_list;
CRITICAL_SECTION m_sect;
};
分析:
    在afxtls.cpp中定義了一個CThreadSlotData類的全域變數:_afxThreadData。在CTh
readLocal的成員函數中大量使用了這個全域變數來訪問TLS功能。
DWORD m_tlsIndex
用來儲存TLS資料的索引,也就是在Thread DataBase中64元數組中的位移量,這個資料在
CThreadSlotData類的建構函式中初始化。
int m_nAlloc
int m_nRover
int m_nMax
這三個變數用來分配slot和記錄相關狀態,比如m_nAlloc用來儲存當前已經分配的slot的
個數。線程為每一個TLS資料分配一個slot。
CSlotData* m_pSlotData;
用來記錄已經分配的每一個slot的狀態:已經使用或是尚未使用。
CTypedSimpleList<CThreadData*> m_list;
CThreadSlotData為每一個Thread實現一個並且只實現一個CThreadData對象,並且用鏈表
類對象m_list來管理它們。實際上,真正被儲存到Thread DataBase中去的是這個CThread
Data對象的指標,而程式員要儲存的TLS資料被儲存到這個CThreadData對象的pData成員指
向的動態數組中。所有Thread的CThreadData對象通過CThreadData對象的pNext成員連成鏈
表,並由CTypedSimpleList<CThreadData*> m_list管理。
CRITICAL_SECTION m_sect;
由於所有Thread的TLS操作都要靠訪問_afxThreadData來實現,這樣就產成了多線程同步的
問題,m_sect就是用來進行線程同步的變數。保證每次只有一個Thread在訪問_afxThread
Data中的成員變數。

定義:
struct CThreadData : public CNoTrackObject
{
CThreadData* pNext; // required to be member of CSimpleList
int nCount;         // current size of pData
LPVOID* pData;      // actual thread local data (indexed by nSlot)
};
分析:
    CThreadData用來輔助CThreadSlotData來完成TLS功能。每一個Thread的TLS資料都要
靠一個CThreadData對象來管理和儲存。
CThreadData* pNext
在CThreadSlotData中,CThreadData由一個鏈表來管理,pNext用來把各個Thread的CThre
adData對象連成鏈表。
int nCount
指出用於儲存TLS資料指標的動態數組的長度。
LPVOID* pData
在CThreadData儲存的實際上是各個TLS資料的指標,為此定義了一個指標數組,nCount用
來指示數組長度,pData用來指出數組的基地址。

定義:
struct CSlotData
{
DWORD dwFlags;      // slot flags (allocated/not allocated)
HINSTANCE hInst;    // module which owns this slot
};
分析:
    CSlotData用來輔助CThreadSlotData來完成TLS功能。每一個Thread的TLS資料都要靠
一個CThreadData對象來儲存,具體實現是把TLS資料的指標儲存在CThreadData對象的動態
指標數組中(基地址由pData指出)。而這個數組中每一個成員的使用狀況則由一個與之長度
相同的CSlotData數組來表示,具體由DWORD dwFlags來表明。

    從上面的分析不難發現,MFC中TLS功能的封裝是這樣的,所有Thread的TLS資料指標都
儲存在一個動態指標數組中,而該數組的基地址由一個CThreadData對象的 pData指出。
同時,儲存在Thread DataBase中的是這個CThreadData對象的指標,而不是TLS資料的指標
,並且其索引值均相同,都為CThreadSlotData類中的m_tlsIndex成員。而且,在CThread
SlotData中提供了一個鏈表來管理所有Thread的CThreadData對象。這樣CThreadSlotData
類就能訪問所有的Thread的TLS資料。見圖tls.bmp。(為了方便,我把圖放到了簽名檔中了
,就在下面)

    下面來進一步說明如何使用TLS功能。
    為了方便TLS的使用,MFC設計了CThreadLocal類。它是一個模板類,具體的定義如下

template<class TYPE>
class CThreadLocal : public CThreadLocalObject
{
// Attributes
public:
AFX_INLINE TYPE* GetData()
{
TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);
ASSERT(pData != NULL);
return pData;
}
AFX_INLINE TYPE* GetDataNA()
{
TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA();
return pData;
}
AFX_INLINE operator TYPE*()
{ return GetData(); }
AFX_INLINE TYPE* operator->()
{ return GetData(); }

// Implementation
public:
static CNoTrackObject* AFXAPI CreateObject()
{ return new TYPE; }
};
    在使用CThreadLocal時,只要用CThreadLocal<ClassType> name;即可構造一個類型為
ClassType的TLS資料,注意ClassType必須以CNoTrackObject為基類。實際上上述聲明定義
了一個名稱為name的CThreadLocal對象,但是通過這個CThreadLocal對象,即可產生並訪
問類型為ClassType的TLS資料。

dll的多進程多安全執行緒的幾種策略

1、動態庫只有一個匯出函數。

這種情況非常少,也是最容易處理的情況。這種情況下編寫函數時,只需要考慮不要有衝突的全域資料就可以了。這裡的全域資料包括了在堆中分配的資料區塊和靜態全域變數等。如果存在這樣的全域資料,那麼進程中的不同線程訪問這個函數就會造成衝突。

解決辦法也很簡單,就是盡量用堆棧(stack)來解決問題。由於堆棧的所有人是線程,所以它必然是安全執行緒的。當然也要注意避免堆疊溢位。

我們都知道,如果要在函數再次調用時保留前一次調用的狀態,可以使用靜態變數。但如果你要保持函數的安全執行緒,那麼靜態變數是不能用的,因為靜態變數是全域的,是屬於進程的,也就是屬於進程內線程共用的。所以如果確實需要在同一線程中保持函數的狀態,相當於在不同次調用間傳遞參數,可以考慮使用靜態全域線程局部變數,即:
 __declspec( thread ) int tls_i = 1;
 該變數定義就使編譯器保證了tls_i是對應於每個線程的,即每個線程都一個tls_i的副本(copy),這樣必然就是安全執行緒的。

2、動態庫匯出了多個函數,而且多個函數間存在資料傳遞。

就像前面說的,一般DLL都匯出多個函數,一個初始化,一個資源釋放,其他為核心功能函數。這些函數間極有可能發生資料傳遞。如果一個初始化函數是線上程A中調用的,而核心功能函數是線上程B中調用的,那麼線程A初始化函數的資源就無法對應線程B中的核心功能,此外還有核心功能函數間的資料傳遞,這樣的DLL就不是安全執行緒的,必然導致錯誤。

解決辦法是由使用者(即使用DLL的人)保證這些匯出函數是在一個線程中調用。但這樣會很大程度上限制介面的設計和使用者的使用自由度。所以最好的方法是函數只管自己的安全執行緒,不同函數傳遞資料用動態TLS,線程局部儲存。

比如:
我在全域定義了一個變數,用於儲存當前線程局部儲存的index ID。
__declspec( thread ) int tls_i = 1;
當調用分配資源的函數時,調用動態TLS函數TlsAlloc,分配一個ID,將其記錄在全域的安全執行緒的tls_i變數,並通過TlsSetValue函數將資料儲存線上程安全的地區;當調用擷取資源的函數時,通過TlsGetValue擷取資源,處理完成後,調用Tlsfree對TLS index釋放,以便新線程佔有。

這樣,只要DLL中每個函數保證其局部是安全執行緒的,函數間傳遞資料通過TLS(靜態和動態),就可以實現整個DLL的安全執行緒。

3、限制訪問DLL中某一函數的線程數目。

有時候,對於DLL中的某一個函數的訪問線程數目是有限制的,超過了限制其他線程就得等一定的時間,一定的時間過後如果還不能得到執行機會,那就返回逾時。這樣的設計對使用者來說是友好的,而且很實用,有的商業程式確實是按照允許使用者訪問的通道數目來計價的。

對DLL中的函數做這樣的一個封裝,一般是簡單的待用Semaphore訊號量,來解決。DLL初始化時調用CreateSemaphore函數對訊號量進行初始化,其原型如下:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
                       // pointer to security attributes
  LONG lInitialCount,  // initial count
  LONG lMaximumCount,  // maximum count
  LPCTSTR lpName       // pointer to semaphore-object name
);
對於訊號量,它每WaitForSingleObject一次(當然是要進入),其狀態值(一個整數)就減1,使用完ReleaseSemaphore其狀態值就加1,當其狀態值為0時訊號量就由有訊號變為無訊號。利用訊號量的這一特性,我們在初始化時將訊號量的初始值(第2個參數)設定為限制的線程訪問數目。在要限制訪問線程數目的函數內部,通過調用WaitForSingleOject擷取控制權,並指定一個等待時間(這個由設定檔指定),根據情況逾時返回,使用完ReleaseSemaphore釋放對佔用,讓其他線程可以調用這個函數。

4、多進程情況下的多安全執行緒DLL。

前面3講了有時候需要對某一函數的訪問線程進行限制,而我們知道,DLL是可以被多個進行載入並調用的。那就是說如果我們只對一個進程進行了限制,那麼在多進程調用的情況下,這樣的限制被輕易攻破。

我們都知道,Semaphore訊號量屬於核心對象,也就是說其可以被多進程共用訪問,也就說,如果我們給一個Semaphore指定了一個名字,在另一個進程中,我們只要調用OpenSemaphore函數用同一名字開啟訊號量就可以訪問了。這樣問題就解決了?

現實情況是,多進程情況下,一般不是簡單的多進程共用一個Semaphore就可以了。多進程間需要互連很多資訊。一般的解決辦法是,採用共用資料區段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS")
通過pragam編譯器指令產生了一個名叫share的共用資料區段,這樣對於變數share_data就可以多進程共用的了。如果要多進程間交換資料,只要在data_seg中添加資料定義即可。

.C++中的深拷貝與淺拷貝,拷貝建構函式
http://blog.chinaunix.net/u/25952/showart_274326.html
深拷貝即進行了類的成員變數賦值操作,還進行了引用對象的拷貝操作,而淺拷貝只進行類成員變數的賦值操作。還可以理解為如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生複製過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源但複製過程並未複製資源的情況視為淺拷貝。
對於C++的類來說,拷貝建構函式不是總要寫的,系統會提供預設的拷貝建構函式,就像預設建構函式一樣。但是如果在拷貝建構函式裡面發生了資源分派(比如堆空間申請)情況,就需要寫自己的拷貝建構函式來。如:
string::string(const string& str)
{
   if (this.data == str.data)
      return;
   if (str.data == null)
   {
      this.data = new char[1];
      this.data[0] = 0;
   }
   else
   {
      this.data = new char[strlen(str.data) + 1];
      strcpy(this.data, str.data);
   }
}
關於什麼是拷貝建構函式,按照標準有如下規則:
a) X& -> X::X(X&) -> X::X(X&, int i=0)
b) const X& ->  X::X(const X&)
c) volatile X& -> X::X(volatile X&)
d) const volatile X& -> X::X(const volatile X&)
且沒有其他參數或其他參數都有預設值,那麼這個函數是拷貝建構函式.
根據以上規則,我們可以知道一個類可以擁有多個拷貝建構函式。編譯器提供的預設拷貝建構函式執行的是淺拷貝操作。

.應用程式的最大可用記憶體(32位平台)
我們都知道對於32位的作業系統平台最大的實體記憶體是4G(2的32次方),而windows核心需要分配2G的實體記憶體定址空間,所以應用程式可用的最大記憶體為2G,那麼如何提高記憶體的最大分配數值呢?1.切換作業系統的位元,從32位升級為64位;2.在啟動項中設定/3G,這樣應用程式的實體記憶體定址空間就可以到3G。

.Core dump 檔案的分析
當我們的程式崩潰時,核心有可能把該程式當前記憶體映射到core檔案裡,方便程式員找到程式出現問題的地方。
http://blog.chinaunix.net/u/6593/showart.php?id=150292

.MFC 非模態對話方塊 模態對話方塊
模態對話方塊訊息迴圈是封閉的,即只在本視窗內進行,出發收到WM_DESTROY 或是 WM_CLOSE。而非模態對話方塊的訊息處理是基於父表單的。如何讓非模態對話方塊轉變成模態對話方塊 http://blog.csdn.net/cathyeagle/archive/2004/09/15/106004.aspx。同模態對話方塊建立非模態對話方塊可以通過Create函數。

.同處理序伺服程式與進程外伺服器的區別
同處理序伺服程式是以dll方式載入到應用程式進程中的,因此執行速度比進程外伺服器快,同代理也可以實現RPC服務。進程外伺服器是以exe或是服務的實現出現,進程外伺服器可以分布在本機也可以分布在遠程機器,但是進程外伺服器跟進程通訊需要通過處理序間通訊的方式進行,如共用記憶體,管道等,這樣速度上比同處理序伺服程式要慢,不過進程外伺服器crash掉不會影響應用程式,所以健壯性比較好。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.