This is a description of the various programming conventions we use. It is based on conventions from the Binder, but is applicable to C++ programming in general. It covers both C++ language features and styles as well as conventions specific to the Binder itself.
These conventions are intended to be very minimal the focus is almost entirely on the public API that is exposed to developers, and not on detailing where every brace and tab goes in the actual implementation.
Code implemented on top of the Binder should follow these conventions. Other parts of the system may or may not follow the various conventions described here, as desired by those authors; however, if your code will be seen by third party developers it is highly encouraged that you adopt these conventions to ensure a consistent developer experience.
At the same time the Address Book may have a com.palmsource.apps.Address.Supermenu component, which it uses to show a super-menu for opening an address entry. This component is part of the Adddress Book's private implementation and should not be used by others, so is in the com.palmsource.* namespace.
Note that something being in the org.openbinder.* namespace is probably not completely a guarantee of compatibility. For example, there are various private system interfaces (such as org.openbinder.app.IApplicationManager) that are not part of the SDK and developers should not use; this is enforced by controlling which IDL files are included in the SDK. In general (and especially for component names), however, these rules should be followed.
All of our OS APIs are placed into the platform-level namespace as described above, and then into a second-level namespace within that for the kit to which it belongs. For example:
namespace palmos { namespace support { class SConditionVariable { ... }; } } // namespace palmos::support
A namespace can have public "using" statements with it, if that namespace's API physically depends on another:
namespace palmos { namespace render { using namespace support; class SRect { // Need the Support Kit's SValue class for this. SRect(const SValue& value, status_t* outError); ... }; } } // namespace palmos::render
Note that given the above code (saying palmos::render
builds on top of palmos::support
), it would be invalid within the palmos::support
namespace to write "<tt>using render</tt>". That is, there should be no circular dependencies between kits/namespaces.
It is never okay to have a "<tt>using</tt>" statement in the global namespace in a public header. You must only place them within other namespaces. (This is because it is impossible to automatically disambiguate symbols once they are in the global namespace.) However, you can do whatever you want as part of your implementation. Thus most .cpp
files do something like this:
#include <render/Rect.h> using namespace palmos::render; SRect::SRect() { } ...
The main thing to watch out for is that we ultimately need to support binary compatibility with our APIs, and this is very problematic with templates because by their nature they expose their implementation. Thus you can never export template functions from a library, because you will end up with multiple (potentially different) implementations of the template compiled in to the library and its clients. (Not to mention the fact as our shared library tools currently exist you will be unable to link to the library due to duplicate symbol errors.)
There are two basic approaches we take to templates. The first is to just make the template very simple, so that the implementation is trivial (and thus won't change) and can be safely compiled in to every client. A good example of this is the sptr<> and wptr<> templates in support/Atom.h.
For more complicated templates, we take an approach that is a variation on the above. The template class is divided into two parts, a base class containing the interesting implementation and a set of pure virtual methods, and then a template class deriving from the base class that implements the pure virtuals based on the type it is templatized over. In this way the template class is again a very simple implementation that doesn't need to be exported from the library. The SAbstractVector and SVector classes in support/Vector.h are a good example of this approach.
Note there are currently many places in the source tree that don't follow these conventions. For example, you will often see classes with a "P" prefix, which is from an old iteration of the conventions. People will be cleaning up the code as they get a chance.
void SetProperty(int32_t property); // setter function int32_t Property() const; // getter function
Never directly expose member variables in the public/protected sections. If performance is an issue, simply make inline getter/setter methods (and clearly comment the method variable that it must stay the same for binary compatibility).
A hook function that subclasses override should use the "On" prefix. Note that Binder interfaces will rarely if ever use "On", because they define an external protocol into the class. "On" here is used for an internal protocol of the class making calls on itself for more derived classes to override.
IView GenerateCell(SValue name, size_t row, SValue cellData, [inout]float itemHeight); virtual SString GenerateTitle() = 0; virtual SString GenerateTitleIcon(); //!< defaults to none
We have started using a convention in the activity classes for the word "Generate" at the front of virtual functions that create some piece of data to return to the caller. This is used in both interfaces (for example IColumn::GenerateCell() creates a new IView object for a cell in the column) and call-backs in classes (for example BInteractiveActivity::GenerateTitle() creates a string for the window's title).
A method that converts a given object into another type should use the "As" prefix. If the conversion can fail you should include an optional output parameter of an error result, though it should still generate a well-defined value in case of error, such as an empty string or undefined SValue.
status_t StatusCheck() const;
Classes that can have an error state such as due to memory or other error during constructor, being constructed without being initialized, becoming invalid while being used should have a StatusCheck() method that returns the current status of the object.
lock_status_t Lock() const; void Unlock() const; virtual status_t SetSizeLocked(off_t size);
In some rare occasions (most significantly the generic Data Model class implementations), we must expose locking in the public API. This should be done by including Lock() and Unlock() methods, where the Lock() method returns a standard lock_status_t type. Any other methods that are called with the lock held must have the "Locked" suffix. This is very useful to clearly document the locking policies of the class.
Note that you should very rarely use this approach in public APIs, because it almost always makes the API more complicated to use and rigid.
void private_method(); void do_something_l();
Private methods should be written in lower_case(). This way the default settings of MakeSLD will not export these symbols.
A private method that must be called with a lock held should use a "_l" suffix. For example, do_something_l(). This is very useful to document internal locking policies in the implementation.
int32_t m_someInt;
All member variables should have a "m_" prefix and then used mixedCase starting with a lower-case letter.
class SUrl { public: ... SUrl(const SValue &value, status_t* out_err=NULL); SValue AsValue(int32_t form = B_FLATTEN_FORM_ACTIVE) const; inline operator SValue() const { return AsValue(); }
If you are writing a stack class that is to be marshalled by pidgen (so it can be used in Binder interfaces), you must provide the above constructor and methods.
Note that if AsValue() fails, it should return a status SValue using SValue::Status(). (If it fails due to running out of memory or other exception cases, use SetError().)
To support custom marshalling, you need in addition to implement the following functions:
class SPoint { public: ... static ssize_t MarshalParcel(SParcel& dest, const void* var); static status_t UnmarshalParcel(SParcel& src, void* var); static status_t MarshalValue(SValue* dest, const void* var); static status_t UnmarshalValue(const SValue& src, void* var); };
Reference counted classes derive from SAtom, SLightAtom or SLimAtom. All I- and B-prefix classes are reference counted. Instances of these classes are always created with new, and pointed to with sptr<> or wptr<>; the reference counting mechanism will take care of calling delete for you at the appropriate time. These classes are always passed by reference.
The "atom" reference counting classes are described in SupportFAQ.
Stack-based classes generally correspond to types, for example SRect, SValue, SLayoutConstraints, etc. You should never use 'new' to instantiate them, instead constructing them on the stack or in a container such as SVector<>. These classes are always passed by value (an out or in/out parameter might seem like an exception, but conceptually you should think of this as copying the value back and forth).
Stack-based classes should be efficient to copy, so that they can always be passed by value. This is important in a multithreaded system where returning a copy of an object can make thread safety much easier to deal with. If your class can't be efficiently copied, you can use copy-on-write semantics. SString, SValue, and SGlyphMap are all examples of this; they all use the convenient SSharedBuffer class for their implementation.
Note that SSharedBuffer and implementations using it are an exception to our rule about not manually managing memory... but that's okay, because SSharedBuffer is there to help us implement other classes in the normal API that hide their memory management.
Sometimes you will have a set of related class that make sense to go together in one header file. In this case the header name will be representative of the kinds of classes it contains. For example, "support/Atom.h" includes SAtom, SLightAtom, SLimAtom, as well as sptr and wptr.
You are encouraged to include multiple classes in a header, when it helps developers to understand the header files: it is basically a balancing act between a few headers with so many classes in them you can find the class, vs each header having one class but there being so many headers you are overwhelmed by them. Use your judgement.
Every kit should have a header file in it called "kit/KitDefs.h" that includes global constants and other definitions for that kit. For example see view/ViewDefs.h.
UPPER_CASE
. If the constant is not in the namespace of a specific class/interface, then it must have a B_ prefix. Usually for constants outside of a class the first word in the constant will be a common specification of the category of constants it belongs to (i.e., B_VIEW_*
)._MASK
. Some examples:
enum {
B_JUSTIFY_MASK = 0x0000000F,
B_JUSTIFY_LEFT = 0x00000001,
B_JUSTIFY_RIGHT = 0x00000002,
B_JUSTIFY_FULL = 0x00000003,
B_JUSTIFY_CENTER = 0x00000004,
B_VALIGN_MASK = 0x000000F0,
B_VALIGN_BASELINE = 0x00000010,
B_VALIGN_TOP = 0x00000020,
B_VALIGN_BOTTOM = 0x00000030,
B_VALIGN_CENTER = 0x00000040
};
interface IDatum { enum { READ_ONLY = 0x0000, WRITE_ONLY = 0x0001, READ_WRITE = 0x0002, READ_WRITE_MASK = 0x0003, ERASE_DATUM = 0x0200, OPEN_AT_END = 0x0400 }; methods: ... }
enum { B_NO_MEMORY = sysErrNoFreeRAM, B_BAD_VALUE = sysErrParamErr, B_NOT_ALLOWED = sysErrNotAllowed, B_TIMED_OUT = sysErrTimeout };
B_CONST_STRING_VALUE_LARGE (BV_VIEW_BYTES, "bytes",); B_CONST_STRING_VALUE_SMALL (BV_VIEW_KEY, "key",); B_CONST_STRING_VALUE_LARGE (BV_VIEW_MODIFIERS, "modifiers",); B_CONST_STRING_VALUE_LARGE (BV_VIEW_REPEAT, "repeat",);
There are other macros for building SValues of other types: B_CONST_INT32_VALUE() and B_CONST_FLOAT_VALUE().
status_t StringSplit(const SString& srcStr, const SString& splitOn, SVector<SString>* strList); status_t StringSplit(const SString& srcStr, const char* splitOn, SVector<SString>* strList); status_t ExecuteEffect( const sptr<IInterface>& target, const SValue &in, const SValue &inBindings, const SValue &outBindings, SValue *out, const effect_action_def* actions, size_t num_actions, uint32_t flags = 0); template<class TYPE> void Construct(TYPE* base, size_t count = 1); template<class TYPE> void MoveAfter(TYPE* to, TYPE* from, size_t count = 1); template<class TYPE> void Assign(TYPE* to, const TYPE* from, size_t count = 1); template<class TYPE> void Swap(TYPE& v1, TYPE& v2);
In general header files should have brief comments for all APIs, enough that someone browsing the file can get a basic grasp of how the API works without having so much detail that it is hard to get the overall view of what is there. The BView header file (view/View.h) serves as a good example, such as:
! @name Utilities Convenience functions to retrieve general information about the view and perform interactions with the view hierarchy. //@{ ! Return the Graphic plane that was given in SetGraphicPlane() SGraphicPlane GraphicPlane() const; //! A not-necessarily-unique identifier for your view, supplied by the parent. virtual SString ViewName() const; //! Called by the parent to supply a name. virtual void SetViewName(const SString& value); //! Returns the parent or NULL if the view doesn't have a parent. ! This function is potentially slow, use carefully. It is not valid to call this during Draw(); this is for transaction and current state only. virtual sptr<IViewParent> Parent() const; //! Invalidate the view. ! Use this functions preferrably over IViewParent::InvalidateChild(). If the view doesn't have a parent, the function returns without doing anything. void Invalidate(); //! Invalidate() a specific rectangle in the view. void Invalidate(const SRect& rect); //! Invalidate() a specific shape in the view. void Invalidate(const SRegion& shape); //! For implementation, don't use. virtual void Invalidate(BUpdate* update); //! Calls @c MarkTraversalPath() with @e constrain and @e layout flags. ! This will eventually trigger an update as MarkTraversalPath() walks up in the hierarchy and encounters a @c ViewLayoutRoot object. virtual status_t InvalidateConstraints(uint32_t execFlags = 0); //! Request that a transaction start, if one hasn't already. virtual void RequestTransaction(uint32_t execFlags = 0); //@}
For the most part, a more general interface is a more powerful interface: the more general it is, the more places it can be used, and thus all code written to implement or use that interface is itself more reusable.
Before writing a new interface, you should first make sure there isn't an existing interface that will serve your needs. Even if there is an existing interface that is close but doesn't quite do what you want, it may make sense to modify the existing interface so it is more general instead of designing a new one.
namespace palmos { namespace support { interface INode { ... } } } // namespace palmos::support
This creates an interface called "org.openbinder.support.INode".
Property names should be mixedCase, with their first letter being lower-case.
By following this convention, pidgen will automatically generate for you C++ methods following the correct convention:
sptr<INode> Attributes() const; SString MimeType() const; void SetMimeType(const SString& value);
status_t Register(SValue key, IBinder binder); status_t Walk([inout]SString path, uint32_t flags, [out]SValue node); status_t InvalidateChild(IView child, [inout]BUpdate update); status_t RemoveView(IView view, [optional]uint32_t execFlags); status_t GetStuff([out]SValue stuff, [optional out]SString name) const;
Methods should be MixedCase, with their first letter being upper-case.
Output parameters can be used anywhere, though they should generally appear after input parameters. Optional parameters must be the last in the method.
A factory method is one that generates a new instance of something each time it is called. Such methods should begin with the "New" prefix.
Events should use the same naming convention as methods, MixedCase starting with an upper-case letter. They typically use a past-tense verb, since they are indicating that some action has happened.
enum
{
CREATE_DATUM = 0x0100,
CREATE_CATALOG = 0x0200,
CREATE_MASK = 0x0300
};
You can use enumerations to create integral constants. (Note that in our IDL syntax, enumerations must appear before any "properties:", "methods:", or "events:" sections.) Like constants in C classes, they should be UPPER_CASE with words separated by underscores, and not us a prefix (the interface name is the prefix).
//! Interface to a node in the Binder namespace ! Nodes allow you to walk paths through the namespace and access meta-data. interface INode { enum { ! This flag can be supplied to various catalog functions, to indicate you would like them to return the contents of an IDatum instead of the object itself, if possible. REQUEST_DATA = 0x1000 }; properties: //! Retrieve the meta-data catalog associated with this node, or NULL if it doesn't exist. [readonly]INode attributes; methods: //! Walk through the namespace based on the given path. ! This function decodes the first name of the path, and recursively calls Walk() on the entry it finds for that. status_t Walk([inout]SString path, uint32_t flags, [out]SValue node); events: //! This event is sent when a new entry appears in the catalog. ! @param who the parent catalog in which this change occured. @param name the name of the entry that changed. @param entry the entry itself, either an INode or IDatum. void EntryCreated(INode who, SString name, IBinder entry); }