[S60]ARM平台專屬問題 Writable Static Data in DLLs
原文地址:
http://www.cppblog.com/gtwdaizi/articles/51908.html
[S60] ARM平台專屬問題 Writable Static Data in DLLs2007-07-08 16:59
在編譯arm平台程式的時候,出現如下錯誤提示: ERROR: Dll 'AppName[UID].APP' has initialised data. 或者: ERROR: Dll 'AppName[UID].APP' has uninitialised data. (副檔名APP的應用程式其實也是一個DLL。)而在為模擬器編譯的時候,這個問題不會出現。這曾經導致我在完成完整的設計,編碼和調試後, 被迫放棄原有設計。 從這條錯誤資訊的字面意思是什麼也看不出來的。initialised 和 uninitialised都一樣有問題。 其實真正的含義是Dll裡存在可寫的全域變數。 大家知道在程式啟動並執行時候,DLL只會被裝載一次。在Windows平台,每個進程都有自己獨立的DLL空間。也就是說,不同進程裝載同一個DLL,互相之間是獨立的。只有在一個進程內,才是共用的。但是S60平台的設計是所有進程都共用同一個DLL空間。這樣的設計顯然是出於節約記憶體的目的,是很有必要的。但是這樣就帶來一個問題,那就是DLL裡不可以有可寫的全域變數,否則就要造成混亂。A進程對變數的改寫會直接影響到B進程,這是程式設計者所不願意看到的。所以,S60平台的編譯器就禁止了在DLL內申明可寫全域變數。但是全域變數還是可以用的,只要加上const申明即可。 一般來說,在做DLL設計的時候,的確不鼓勵使用可寫全域變數。即使是windows平台,DLL的可寫全域變數也會在不同模組之間帶來問題。當遇到這個編譯器錯誤的時候,應該設法修改設計,迴避使用全域變數。 但是因為APP實際上也是DLL,這就導致連S60的主程式也不能使用可寫的全域變數,這個在某些時候就成了問題,全域變數畢竟是一個重要的實現手段。對此,S60提供了線程局部儲存(thread local storage)來解決問題。 TLS的關鍵是兩個函數: void Dll::SetTls(void*)和void* Dll::Tls() SetTls用於將任意類型的指標儲存到線程局部儲存中,而Tls()則取出該指標。 指標指向在堆上分配的一塊記憶體。一個線程只能有一個局部儲存變數。所以,如果你有很多全域變數,就要定義一個結構,把所有的全域變數封裝在其中。這是挺彆扭的,不過S60 3rd據說就支援dll的可寫全域變數了。 tls範例代碼: 設定 GlobalData* p = new GlobalData(); if ( p ) { Dll::SetTls( p ); } 使用 GlobalData* p = (GlobalData*) Dll::Tls(); |
在Symbian上如何定義全域變數
方法1(推薦)把這個變數定義成AppUi類的私人成員,在建立view時將這個變數傳引用(或傳指標)到view中,這樣view就能隨時訪問它了。
方法2.把這個變數定義成AppUi類的私人成員,並為它寫公用的訪問函數
// CMyAppUi
public: // new methods
TInt Share(); // return iShare
private:
JAVA手機網[www.cnjm.net] TInt iShare;
在View裡通過下面的方式訪問這個變數:
// 如果View繼承自CAknView
CMyAppUi* appUi = static_cast<CMyAppUi*>(AppUi());
appUi->Share(); //
// 如果View是其它類型
CMyAppUi* appUi = static_cast<CMyAppUi*>(CCoeEnv::Static()->AppUi());
appUi->Share(); //
方法3.使用單態類,參考諾基亞論壇上的文檔:
Tip Of The Month: How To Implement A Singleton Class In Symbian OS
How to implement a singleton class in Symbian OS ID: TTS000222
Version 1.1
Published at www.forum.nokia.com on October 19, 2006.
Overview
The singleton pattern is one of the best-known patterns in software engineering. Essentially, a singleton is a class which only allows a single instance of itself to be created, and usually gives simple access to that instance.
How to use thread local storage (TLS) to implement a singleton class
In Symbian OS, each DLL that is loaded in each thread has a machine word of thread-specific memory that can be written to and read — but no other static memory, which is why you can't have static class member variables. Since static class member variables are usually used to implement the singleton pattern, in Symbian OS we have to get around this, for instance by using TLS.
The following code demonstrates a singleton object whose NewL function uses TLS to test whether an object of its own type has been created. If it has, it simply returns the pointer stored in TLS, converted to its own type. If not, it instantiates an object of its own type, stores it in TLS, and then returns it.
Note that this assumes that no other class in the DLL that includes this class uses TLS. If this is not the case, you must write a singleton manager class, which uses TLS to store a pointer to a structure of pointers to all the singleton classes that the program needs.
Example 1: Singleton implementation based on TLS
==============
CMySingleton.h
==============
class CMySingleton : public CBase
{
public: // constructor and destructor
static CMySingleton* NewL();
virtual ~CMySingleton();
private: // constructors
CMySingleton(); // private because of the singleton pattern; it is
// guaranteed that only NewL will call it
void ConstructL();
public: // other functions
...
private: // other functions
...
private: // data
...
}
================
CMySingleton.cpp
================
CMySingleton::CMySingleton* NewL()
{
CMySingleton* singleton;
// Check thread local storage:
if ( Dll::Tls() == NULL )
{
// TLS is still null, which means that no CMySingleton has
// been instantiated yet. Do so now, and return that
// instance:
singleton = new ( ELeave ) CMySingleton();
CleanupStack::PushL( singleton );
singleton->ConstructL();
CleanupStack::Pop( singleton );
// Store a pointer to the new instance in thread local storage:
TInt err = Dll::SetTls( static_cast<TAny*>( singleton ) );
if ( err == KErrNone )
{
return singleton;
}
else
{
delete instance;
User::Leave( err );
return NULL;
}
}
else
{
// CMySingleton has been instantiated once already, so return
// that instance:
singleton = static_cast<CMySingleton*>( Dll::Tls() );
return singleton;
}
}
TLS on S60 3rd Edition
Since applications from S60 3rd Edition onwards are implemented as EXE programs, Dll::Tls() is not available anymore. Instead, TLS functionality is implemented in UserSvr class (e32svr.h):
static TInt DllSetTls(TInt aHandle, TAny *aPtr);
static TAny *DllTls(TInt aHandle);
static void DllFreeTls(TInt aHandle);
Note that EXE programs can contain writeable static data, but this is not recommended to be used except as a last resort.
Using class CCoeStatic to implement a singleton class
A simpler way of implementing singletons than using TLS is possible for those classes which use the CCoeEnv class. Since CCoeEnv is a part of the UI control framework, this concerns only applications, not application engines.
This applies also to S60 3rd Edition and later editions.
Example 2: Singleton implementation based on CCoeStatic
==============
CMySingleton.h
==============
/**
* Example implementation of a singleton class by means of inheriting
* from CCoeStatic.
*/
class CMySingleton : public CCoeStatic
{
public: // constructors and destructor
/**
* Returns an instance of this class. When called for the first
* time, a new instance is created and returned. After that,
* calling InstanceL returns the same instance that was created
* earlier.
*
* @return A pointer to a CMySingleton object
*/
static CMySingleton* InstanceL();
private: // constructor
/**
* Default constructor is private because we are using the
* singleton design pattern.
*/
CMySingleton();
...
}
================
CMySingleton.cpp
================
// -------------------------------------------------------------------------
// CMySingleton::CMySingleton
// C++ default constructor. It is private because we are using the
// singleton design pattern.
// -------------------------------------------------------------------------
CMySingleton::CMySingleton()
: CCoeStatic( KUidMySingleton )
{
}
// -------------------------------------------------------------------------
// CMySingleton::InstanceL
// Returns an instance of this class. When called for the first time,
// a new instance is created and returned. After that, calling
// InstanceL returns the same instance that was created earlier.
// Note that the UID passed to CCoeEnv::Static needs to be unique.
// -------------------------------------------------------------------------
CMySingleton* CMySingleton::InstanceL()
{
CMySingleton* instance = static_cast<CMySingleton*>
( CCoeEnv::Static( KUidMySingleton ) );
if ( !instance )
{
instance = new ( ELeave ) CMySingleton;
CleanupStack::PushL( instance );
instance->ConstructL();
CleanupStack::Pop();
}
return instance;
}
===========================================================
===========================================================
本人補充:
symbian系統編譯的dll檔案不能使用可寫的靜態變數是針對"v8.1a"及以前版本的"Symbian OS"的,之後的版本(如"v9")無此限制(因此可以用EPOCALLOWDLLDATA),但不推薦使用。
在C語言工程中最好不要有可寫的全域靜態變數(Global Writeable Static Data),在有些手機版本中是不支援全域變數的,但可以有全域靜態常量,對於全域指標常量注意要這樣申明,例如:static const char* const,即要有兩個“const”。如果有出現全域靜態變數或靜態常量,則在MMP檔案中要加入“EPOCALLOWDLLDATA”標識,否則會出現編譯錯誤。對於可寫的全域靜態變數有種處理方法是把所有全域變數放到一個STRUCT結構中,然後這個結構的指標用Dll::SetTls()和Dll::Tls()來存取,存在的問題是用C編譯的代碼中無法調用C++的這種方法,還有一種最笨的處理方法是把指標的值存入一個檔案中;