|
||
Some property operations require a reference to the property to be
established first. This is done using the
RProperty::Attach()
member function. After a call to this
function, the RProperty
object acts like a standard handle
to a kernel resource. When this handle is no longer required, it can be
released in the standard way by calling the inherited
RHandleBase::Close()
member function.
Note that releasing the handle does not cause the property to disappear. This only happens if the property is deleted.
Note also that it is quite legitimate to attach to a property that has not been defined, and in this case no error will be returned either. This enables the lazy definition of properties as used in some of the usage patterns.
// attach to the ‘counter’ property
RProperty counter;
TInt r=counter.Attach(KMyPropertyCat,EMyPropertyName,EOwnerThread);
User::LeaveIfError(r);
// use the counter object...
// when finished, release the handle
counter.Close();
A property is defined using the
RProperty::Define()
function, specifying the attributes of
that property.
A property does not need to be defined before it can be accessed. This supports programming patterns where both publishers and subscribers may define the property. Note, however that for security reasons, there are restrictions on the category that can be used when defining a property; see security issues for more information.
Once defined, a property persists in the kernel until the system
reboots, or the property is explicitly deleted. Its lifetime is not tied to
that of the thread or process that originally defined it. This means that, when
defining a property, it is important to check the return code from the call
RProperty::Define()
to deal with the possibility that the property
has previously been defined, but not deleted.
The following code shows the definition of two properties:
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
static _LIT_SECURITY_POLICY_PASS(KAllowAllPolicy);
static _LIT_SECURITY_POLICY_C1(KPowerMgmtPolicy,ECapabilityPowerMgmt);
// define first property to be integer type
TInt r=RProperty::Define(EMyPropertyCounter,RProperty::EInt,KAllowAllPolicy,KPowerMgmtPolicy);
if (r!=KErrAlreadyExists)
{
User::LeaveIfError(r);
}
// define second property to be a byte array, allocating 100 bytes
r=RProperty::Define(EMyPropertyName,PProperty::EByteArray,KAllowAllPolicy,KPowerMgmtPolicy,100);
if (r!=KErrAlreadyExists)
{
User::LeaveIfError(r);
}
. . .
Once defined, a property value can change, but the property type cannot. Byte-array type properties can also change length provided the length does not exceed the maximum value of 512 bytes. The limit on the size of a property ensures some limit on RAM usage.
The API allows byte-array and Unicode text type properties to be pre-allocated when they are defined. This means that the time taken to set the values is bounded. However, if the length of these property types subsequently increases, then memory allocation may take place, and no guarantees can then be made on the time taken to set them.
There are further security issues to be
considered when defining a property. You need to provide two security policies
- one to govern which processes can publish the property value, and the other
to govern which processes can retrieve the property value. Security policies
are instances of TSecurityPolicy
objects, although for
efficiency reasons, you will almost always use the
_LIT_SECURITY_POLICY_...
macros to generate constant objects that
behave like TSecurityPolicy
objects. The API reference for
TSecurityPolicy
provides far more detail on this.
In this example, all processes will be allowed to retrieve the property
value, but only those processes having the power management system capability
(ECapabilityPowerMgmt
) will be allowed to publish the
property value.
Note that a process that defines a property does not have automatic rights of access to that property, other than to delete it. If the defining process also wishes to publish and/or subscribe to that property, then it must ensure that it satisfies the security policies that it itself has put in place when defining the property.
A defined property is deleted by calling
RProperty::Delete()
. Any outstanding subscriptions for
this property will complete with KErrNotFound
. Only the
process with the correct secure ID is allowed to delete it.
For example, extending the code fragment introduced above:
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
// define first property to be integer type
TInt r=RProperty::Define(EMyPropertyCounter,RProperty::EInt);
if (r!=KErrAlreadyExists)
{
User::LeaveIfError(r);
}
// define second property to be a byte array, allocating 100 bytes
r=RProperty::Define(EMyPropertyName,PProperty::EByteArray,100);
if (r!=KErrAlreadyExists)
{
User::LeaveIfError(r);
}
. . .
// much later on
. . .
// delete the ‘name’ property
r=RProperty::Delete(EMyPropertyName);
if (r!=KErrNotFound)
{
User::LeaveIfError(r);
}
A property is published using the RProperty::Set()
family
of functions. Properties can be published:
using a previously attached RProperty
handle,
or
by specifying the property category and key with the new value.
The former mode is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks, except when publishing a byte-array property that requires the allocation of a larger space for the new value.
The latter mode offers no real-time guarantees.
Property values are written atomically. This means that it is not possible for threads reading a property to get a garbled value.
All outstanding subscriptions for a property are completed when the value is published, even if it is exactly the same as the existing value. This means that a property can be used as a simple broadcast notification service.
Publishing a property that is not defined is not necessarily a
programming error. The Set()
functions just return an error. If
this is not expected for any particular usage, then the error must be checked
and processed by the caller.
See the code fragment in the section Retrieving a property value
The current value of a property is read using the
RProperty::Get()
family of functions. Properties can be retrieved:
using a previously attached RProperty
handle,
or
by specifying the property category and key with the new value.
The former mode is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks.
The latter mode offers no real-time guarantees.
Property values are read atomically. This means that it is not possible for threads reading a property to get a garbled value.
Retrieving a property that is not defined is not necessarily a
programming error. The Get()
functions just return an error. If
this is not expected for any particular usage, then the error must be checked
and processed by the caller.
Integer properties must be accessed using the overloads that take an integer or integer reference value, whereas byte-array properties can be accessed using the overloads that take a descriptor reference.
The following code fragment shows publication and retrieval of a property. Note that it uses the idea of attaching to a property. Note also that it contains a race condition, especially if another thread is executing the same sequence to increment the ‘counter’ value.
const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
// attach to the ‘counter’ property
RProperty counter;
TInt r=counter.Attach(KMyPropertyCat,EMyPropertyCounter,EOwnerThread);
User::LeaveIfError(r);
// publish a new name value
TFileName n;
RProcess().Filename(n);
r=RProperty::Set(KMyPropertyCat,EMyPropertyName,n);
User::LeaveIfError(r);
// retrieve the first 10 characters of the name value
TBuf<10> name;
r=RProperty::Get(KMyPropertyCat,EMyPropertyName,name);
if (r!=KErrOverflow)
{
User::LeaveIfError(r);
}
// retrieve and publish a new value using the attached ‘counter’ property
TInt count;
r=counter.Get(count);
if (r==KErrNone)
{
r=counter.Set(++count);
}
User::LeaveIfError(r);
// when finised, release the handle
counter.Close();
Subscribing to a property is the act of making an asynchronous request to be notified of a change to that property.
A thread makes a request for notification of a change to a property by
calling the RProperty::Subscribe()
member function on an
already attached property object. Only one subscription request can be
outstanding at any one time for a RProperty
instance.
An outstanding subscription request can be cancelled by calling the
RProperty::Cancel()
member function. This is unsubscribing
from the property.
Subscribing to a property is a single request to be notified when the property is next updated, it does not generate an ongoing sequence of notifications for every change to that property's value. Neither does it provide the caller with the new value. In essence, the act of notification should be interpreted as “Property X has changed” rather than “Property X has changed to Y”. This means that the new value must be explicitly retrieved, if required. As a result, multiple updates may be collapsed into one notification, and subscribers may not have visibility of all intermediate values.
This might appear to introduce a window of opportunity for a subscriber to be out of synchronisation with the property value – in particular, if the property is updated again before the subscriber thread has had the chance to process the original notification. However, a simple programming pattern, outlined in the second example below ensures this does not happen. The principle is that, before dealing with a subscription completion event, an active object should re-issue the subscription request.
Note that if the property has not been defined, then a subscription
request does not complete until the property is subsequently defined and
published. Note that the request will complete with
KErrPermissionDenied
if the subscribing process does not
have sufficient capability as defined by the
TSecurityPolicy
object supplied by the process defining
the property.
If the property is already defined, then the request completes
immediately with KErrPermissionDenied
if the subscribing
process does not have sufficient capability.
const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
// attach to the ‘counter’ property
RProperty counter;
TInt r=counter.Attach(KMyPropertyCat,EMyPropertyCounter,EOwnerThread);
User::LeaveIfError(r);
// wait for the previously attached ‘counter’ property to be updated
TRequestStatus s;
counter.Subscribe(s);
User::WaitForRequest(s);
// Notification complete, retrieve the counter value.
TInt count;
counter.Get(count);
. . .
const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
// Active object that tracks changes to the ‘name’ property
class CPropertyWatch : public CActive
{
enum {EPriority=0};
public:
static CPropertyWatch* NewL();
private:
CPropertyWatch();
void ConstructL();
~CPropertyWatch();
void RunL();
void DoCancel();
private:
RProperty iProperty;
};
CPropertyWatch* CPropertyWatch::NewL()
{
CPropertyWatch* me=new(ELeave) CPropertyWatch;
CleanupStack::PushL(me);
me->ConstructL();
CleanupStack::Pop(me);
return me;
}
CPropertyWatch::CPropertyWatch()
:CActive(EPriority)
{}
void CPropertyWatch::ConstructL()
{
User::LeaveIfError(iProperty.Attach(KMyPropertyCat,KMyPropertyName));
CActiveScheduler::Add(this);
// initial subscription and process current property value
RunL();
}
CPropertyWatch::~CPropertyWatch()
{
Cancel();
iProperty.Close();
}
void CPropertyWatch::DoCancel()
{
iProperty.Cancel();
}
void CPropertyWatch::RunL()
{
// resubscribe before processing new value to prevent missing updates
iProperty.Subscribe(iStatus);
SetActive();
// property updated, get new value
TFileName n;
if (iProperty.Get(n)==KErrNotFound)
{
// property deleted, do necessary actions here...
NameDeleted();
}
else
{
// use new value ...
NameChanged(n);
}
}
There are three usage patterns that can easily be identified, labelled as: standard state, pure event distribution, and speculative publishing.
This pattern is used for events and state that are known to be used widely in the system. Examples of this might be battery level and signal strength, which are important in every phone.
The publisher calls RProperty::Define()
to
create the appropriate property. For byte array or text properties, a size
sufficient for all possible values should be reserved. An error of
KErrAlreadyExists
should be ignored. The publisher then
publishes the property values as, and when, appropriate. If the
RProperty::Set()
call fails, this should be treated as a serious
error, since it indicates that important system state is not getting through.
Appropriate action might include panicking or rebooting the system. Subscribers
will use RProperty::Subscribe()
to request notification,
and RProperty::Get()
to retrieve the property value on
notification.
The memory to store the property value will be permanently allocated, even if it turns out that no-one in the system needs that value. This does ensure that the value can always be published, even if the system is in an out of memory situation. For this reason, this approach should be limited to widely used and important state. The Speculative publishing pattern offers an approach for dealing with less important state.
This pattern is used when events need to be distributed, not values.
The publisher of the event simply uses an integer property, and calls RProperty::Set() with any value. Even if the value of the property is not changed by this operation, all subscribers will be notified that a Set() has occurred, and by implication that the related event has occurred.
Subscribers will be able to detect that an event has occurred, but will get no other information. The minimum possible memory is wasted on storage for the dummy value.
This pattern is used when it is not known whether a value will be of
interest to others or not. Unlike the
standard state pattern, the publisher of the event does not call
RProperty::Define()
to create the property. Instead, it
simply calls RProperty::Set()
as appropriate, and ignores any
KErrNotFound
error.
When other code in the system, i.e. a potential subscriber, is
interested in the state, it calls RProperty::Define()
to
create the property and allocate the memory for the value. An error of
KErrAlreadyExists
should be ignored, as this only
indicates that some other code in the system is also interested in the value
and has already created the property.
The subscriber then calls RProperty::Subscribe()
and RProperty::Get()
as usual to interact with the property. On
the first Get()
, the subscriber may retrieve the property default
value (zero, or a zero length descriptor). This must be substituted with a
sensible default value for the property in question.
Using this pattern, no memory is wasted on properties that have no subscribers, while the publisher code is simpler as there is no need for configuration as to which properties to publish.
The publisher, however, wastes some time attempting to publish unneeded values, but this should not be an issue unless the value is very frequently updated.
Where events are published very infrequently, the subscriber could have a dummy value for a long time, until the next publish event updates the value. Often this is not a problem as a default value can be substituted. For example a full/empty indicator for a battery level, none for signal strength etc. This pattern is unlikely to be useful if there is no suitable default value.
While the Publish and Subscribe API, as represented by
RProperty
is designed to be efficient, there are certain
usage patterns that can improve performance.
If you intend to call Set()
or Get()
repeatedly, it is preferable to use the attached forms of the calls. The
attached forms are the ones that do not take a UID/Key parameter, and that can
only be called after calling RProperty::Attach()
. The
attached variants are constant time operations, and execute much faster than
the corresponding unattached versions.
For byte-array and text properties, it is possible to pre-allocate
space for the data. Doing this results in Set()
operations that do
not exceed the preallocated space, avoiding the need to do a memory allocation.
However, if the data is shorter than the reserved space, the excess is wasted.
Since Set()
automatically extends the data area if needed, then
the only reason to pre-allocate space is if the Set()
operation
has to be real-time, i.e. has to have known execution time.
Even in situations where the Standard State pattern may seem appropriate, speculative publishing may be a better choice, specially for low-level components that know little about how they are used or what the wider system configuration may be. The onus is then on the UI/Policy layer to ensure that the appropriate properties are defined early on in device boot according to policy rules it can define. This ensures that the policy layers in the system maintain control and can implement a wide variety of policies.
Standard state is only relevant for properties that are essential to every Symbian device. Battery level probably falls into this category, signal strength may well not.
When a property is changed, all subscribers are notified. This leads to their threads running to service the notification. If a property changes value frequently, it would be wasteful for subscribers to perform substantial processing for each notification.
Take a property representing signal strength as an example. Potentially, this could be updated several times a second. If a change in value were only used to update the UI signal bar, it would not be harmful. However, if it were used by many entities for serious processing (e.g. polling for email, sending unsent SMSes, re-connecting to the internet), then such frequent updates would have a severe effect on battery life.
Nevertheless, it is obviously desirable for many parts of a phone OS to know about the state of network coverage, and to take appropriate action. In cases like this, it may be worth the publisher defining multiple properties with associated update characteristics. For example, raw signal strength (updated > 1 time/sec), periodic signal strength (updated once every 10s) and network coverage (updated only when moving between some signal and none). Each subscriber can then monitor the appropriate notification and so reduce the number of threads that run when the underlying value changes.