1. 程式人生 > >CORBA 常用基本方法解析

CORBA 常用基本方法解析

CORBA Programming with TAO - 4.Basic Functions(常用基本方法解析) 摘要: 簡要介紹CORBA規範定義的幾個常用基本方法的功能及應用中需要注意的問題。 正文: idl編譯器會為每個在idl中宣告的interface生成一個對應的代理基類: class InterfaceName; 以及兩個物件引用型別: InterfaceName_ptr 和 InterfaceName_var 前者是一個指標型別,其定義往往是: typedef InterfaceName* InterfaceName; 因此,不作過多討論。 InterfaceName_var則是一個智慧指標類,通過使用智慧指標類,免去了我們手工維護指標引用計數的工作,大大簡化了應用程式的編寫。 下面重點對代理基類和_var智慧指標類中的幾個基本方法進行簡單說明。 一、代理基類中定義的幾個基本方法
每個代理基類(以及CORBA::Object)都提供如下幾個靜態方法: ·_duplicate 返回一個引數指標的拷貝(為了免去物件深拷貝所引起的消耗,實際是通過調整引用計數的方法實現的) ·_nil 返回相應介面型別的空引用 ·_narrow 嘗試將引數指標轉型為目標指標,若轉換失敗,則返回目標型別的空引用。 除了以上靜態方法,每個代理類還包括兩個方法: ·_is_a 該方法接收一個型別id資訊const char *type_id,並返回一個CORBA::Boolean,用於判斷某個引用是否是type_id所指示的型別。 ·_this 該方法返回當前物件的拷貝的引用。 ·_add_ref/_remove_ref
用於增加引用計數和減小引用計數,在我們編寫介面實現時可能會用到(客戶程式中無法使用這兩個方法,因為只有Skeleton程式碼中會生成這兩個方法,客戶程式程式碼也沒有必要使用這兩個方法來維護引用計數)。 此外,在CORBA名稱空間中還定義了: ·CORBA::is_nil 判斷某個ptr是否為空 ·CORBA::release 釋放參數物件。 而為了比較兩個引用是否相同,CORBA::Object中定義了方法: ·is_equivalent 按照CORBA規範,不使用以上基本方法對CORBA物件或物件指標進行比較、型別轉換、非空測試等所產生的行為都是未定義的。 這幾個方法本身比較簡單,並且後續的文章中將看到上述各基本方法的使用,這裡就先不舉例了。 二、_var智慧指標類中的基本方法
按照CORBA規範,每個_var智慧指標類都包括如下幾個方法: ·in ·out ·inout ·_retn ·ptr 掌握這幾個方法的最簡單的方法是學習CORBA::String_var類的實現。CORBA::String_var是CORBA:: String類的智慧指標類,其中封裝了一個char*指標。我們來看看TAO是如何實現這幾個方法的(見%TAO_ROOT% /tao/CORBA_String.inl): ACE_INLINE const char * CORBA::String_var::in (void) const {  return this->ptr_; } ACE_INLINE char *& CORBA::String_var::inout (void) {  return this->ptr_; } ACE_INLINE char *& CORBA::String_var::out (void) {  CORBA::string_free (this->ptr_);  this->ptr_ = 0;  return this->ptr_; } ACE_INLINE char * CORBA::String_var::_retn (void) {  char *temp = this->ptr_;  this->ptr_ = 0;  return temp; } /// TAO extension. ACE_INLINE char * CORBA::String_var::ptr (void) {  return this->ptr_; } 表面看來似乎沒有什麼值得注意的,只是返回了幾個不同型別的指標:in方法返回一個指標(因僅作為傳入引數),inout方法返回一個指標的引 用(因不僅要作我傳入引數,還要通過函式呼叫修改其內容),out方法同樣返回一個指標的引用(因需要通過函式呼叫修改其內容),inout方法返回一個 指標(因僅作為返回引數,不能修改),ptr也返回一個普通指標,同樣也不能修改內容。 但仔細看一看_retn的實現,你會發現,該方法首先儲存了指標的內容,然後將指標清0(這樣當String_var被釋放時就不會free原 來的地址空間),最後將該指標返回。也就是說,通過該方法呼叫,String_var物件所指向的地址空間的控制權被交給了呼叫該方法的一方,同樣,釋放 地址空間的工作也應該由呼叫該方法的一方來完成。 而對於out方法,為了保證原指標被安全釋放,該方法先釋放原來指向的地址空間,並將指標清0,最後返回該指標的引用。因此,被呼叫的方法內部應負責為該物件分配空間,而釋放該空間的工作則是由呼叫方完成的。 其他物件上述方法的實現與String_var在原理上是基本一致的,只是IDL編譯器tao_idl在生成stub和skeleton程式碼時 會分別為定長結構體和變長結構體應用不同的類模板,從而生成不同型別的_var類。對於定長結構體,應用的類模板為TAO_Fixed_Var_T,而對 於變長結構體,應用的類模板為TAO_Var_Var_T。二者的區別在於對於定長結構體而言,_retn和out方法與inout方法的實現是一樣的, 沒有清0的過程,以下是TAO_Fixed_Var_T模板類中的相關程式碼: // Mapping for fixed size. template<typename T> ACE_INLINE T & TAO_Fixed_Var_T<T>::out (void) {  return *this->ptr_; } template<typename T> ACE_INLINE T TAO_Fixed_Var_T<T>::_retn (void) {  return *this->ptr_; } 關於_var類的更多資訊,可參考%TAO_ROOT%/tao/VarOut_T.inl,或: 三、典型問題解析 上面簡單介紹了CORBA程式設計中常用的幾個基本方法,下面在此基礎上對記憶體管理相關的幾個問題進行簡要分析。 ·包含變長成員變數的結構體的記憶體釋放問題 以下面的idl為例: struct DemoStruct {       string name_; }; 對於如下的程式碼: int main() {       //...       DemoStruct_var demo = new DemoStruct;       demo.name_ = CORBA::string_dup("Hello");       //... } 由於我們使用了_var智慧指標類,通過new動態分配的空間會在demo物件被銷燬時被釋放,但是其中通過CORBA::string_dup為成員變數name_所分配的空間是否會洩漏呢? 答案是:不會。這是因為idl檔案中結構體的string成員變數經過tao_idl編譯後,被對映為TAO_String_Manager, 這種型別與String_var基本是一樣的,只是String_var不帶任何引數的建構函式會將指標ptr_初始化為0,而 TAO_String_Manager不帶任何引數的建構函式則會將ptr_初始化成一個空字串,具體可見%TAO_ROOT% /tao/Managed_Types.i及%TAO_ROOT%/tao/CORBA_String.inl。 因此,當結構體被釋放時,我們就無需為其中的string的釋放問題擔心。對於其它包含變長型別的結構體而言,情況是一樣的。 ·_var類使用的誤區 在使用String_var時有一個問題需要注意,String_var提供了三個建構函式(見%TAO_ROOT%/tao/CORBA_String.h): CORBA::String_var::String_var (char *p)  : ptr_ (p) { } ACE_INLINE CORBA::String_var::String_var (const char *p)  : ptr_ (CORBA::string_dup (p)) { } CORBA::String_var::String_var (const CORBA::String_var& r) {  this->ptr_ = CORBA::string_dup (r.ptr_); } 第一個建構函式僅對指標p進行淺拷貝,儲存到內部的ptr_中,而後面兩個則通過深拷貝來構造String_var物件。 兩種不同的構造方式區別雖然比較小,但可能引起一些十分隱蔽的問題。如下面的程式碼就存在問題: String_var str("Hello"); 因為上述程式碼會使用第一個而不是第二個建構函式來構造str,從而使得str獲得靜態地址空間"Hello"的控制權,並在str被析構時嘗試 釋放該空間,這顯然是錯誤的。要避免該錯誤,我們應該總是強制使用第二個建構函式,或在構造Stirng_var物件前主動複製String的內容,如: String_var str1((const char*)"Hello"); String_var str2(CORBA::string_dup("Hello")); 除非你清楚地知道第一個建構函式是你需要的。 對於其它_var型別而言,不存在與上面第二種構造方式等價的建構函式,我們總是使用第一種形式的建構函式,即新構造的_var物件會獲得指標的控制權。 與上面第三種構造方式類似,如果你傳入的是一個_var引用,則使用的是如下的建構函式: template<typename T> TAO_Var_Base_T<T>::TAO_Var_Base_T (const TAO_Var_Base_T<T> & p)  : ptr_ (p.ptr_ ? new T (*p.ptr_) : 0) { } 該建構函式會對傳入的_var引用進行深拷貝。 ·對遠端方法呼叫記憶體管理問題的解釋 CORBA記憶體分配/釋放的原則很簡單:各自負責自己分配空間的釋放,C/S兩端記憶體的分配與釋放(以及更新)不會自動通知另一方。 對於Client程式碼而言,由Client程式碼負責釋放的空間還包括ORB在unmarshalling期間建立的Server的指標的映象,即Server端指標的本地拷貝,這些空間可能是out/inout引數或者作為介面方法的返回值通過方法呼叫獲得的。 這在理解上應該沒有什麼問題。但是,其中對於Server方的記憶體管理,我們沒有考慮。以如下程式碼為例: DemoStruct* DemoIntf::foo() {       DemoStruct_var var = new DemoStruct;       //...       return var._retn(); } 當函式返回時,Client方ORB會通過unmarshalling建立一個該指標所指記憶體區域的映象,然後訪問該指標,最後由Client 方負責該區域的釋放;但是我們說過,C/S雙方分配和釋放記憶體並不會自動通知另一方,對於Server方而言,由於_retn方法會釋放原_var物件對 其ptr_指標所指地址空間的控制權,原來由_var物件所管理的空間似乎變得失去了控制,那麼這一部分記憶體是否會洩漏呢? 同樣,答案是不會,這是因為ORB在將該指標進行marshalling並傳遞給Client後,會負責指標所指空間的釋放,同樣對於in、out、inout引數的管理也是類似的。 結合上面對Server方記憶體管理方式的討論,我們來看看下面的例子。 對於如下的idl檔案: struct DemoStruct {       string name_; }; interface DemoIntf {       DemoStruct get(); }; 有類似下面的實現: class DemoIntf : ... { private:       DemoStruct_var demo_; public:       DemoStruct* get() {             return demo_;       } }; 上述實現初看起來可以正常工作,但是根據上面的討論,ORB會釋放返回值指標的地址空間,這將導致demo_被意外釋放,最終導致錯誤的發生。因此,我們應該將實現程式碼改為: DemoStruct* get() {       ::Device::DeviceID_var dev_id = new ::Device::DeviceID;       dev_id->device_name = CORBA::string_dup(id_->device_name);       return dev_id._retn(); } 而客戶方程式碼的程式碼是這樣的: ::Device::DeviceID_var device_id = this->device_id(); 參考: 1.      Michi Henning, Steve Vinoski. Advanced CORBA Programming with C++. Addison-Wesley, 1999.