Specifically, I want to store the user's camera settings without requiring them to load a process icon. The Settings API will let me do this, but it doesn't have a convenient way for me to persist my data structures. As a lazy Ruby developer I am spoiled :-P - so yes, I would like to serialize my Array<> to a char* that I can store using the settings API. XML would be wasteful since it is just an internal mechanism. I don't really care if it is human readable. That's what the XPSM files are for ;-)
Actually, that isn't difficult to do with PCL using the Settings class and a few container classes.
These static member functions of the pcl::Settings class allow you to do what you want:
bool Settings::Read( const IsoString& key, ByteArray& b )
void Settings::Write( const IsoString& key, const ByteArray& b )A ByteArray object is a dynamic array of bytes implemented as the Array<uint8> template instantiation in PCL. ByteArray allows you to store basically anything, so it is ideal to store the contents of simple data structures.
Let's say that your camera settings are being represented with a CameraSettings class. Then all you need is adding two member functions that convert its data to/from raw binary representations:
class CameraSettings
{
public:
...
void AddToRawData( ByteArray& ) const;
ByteArray::const_iterator GetFromRawData( ByteArray::const_iterator );
};Now you need to implement the actual export/import mechanisms. First, let's introduce a few utility routines implemented as template functions:
/*
* Adds an object t to a ByteArray stream b.
*/
template <class T>
void AddToRawData( ByteArray& b, const T& t )
{
const uint8* p = reinterpret_cast<const uint8*>( &t );
b.Add( p, p+sizeof( t ) );
}
/*
* Retrieves an object t from a ByteArray stream at the specified location i.
* Returns an iterator located at the next position in the ByteArray stream.
*/
template <class T>
ByteArray::const_iterator GetFromRawData( T& t, ByteArray::const_iterator i )
{
t = *reinterpret_cast<const T*>( i );
return i + sizeof( T );
}
/*
* Adds the contents of a string s to a ByteArray stream b.
*/
template <class S>
void AddStringToRawData( ByteArray& b, const S& s )
{
AddToRawData( b, uint32( s.Length() ) );
if ( !s.IsEmpty() )
b.Add( reinterpret_cast<const uint8*>( s.Begin() ), reinterpret_cast<const uint8*>( s.End() ) );
}
/*
* Loads a string's character contents from the specified location i on a ByteArray.
* Returns an iterator located at the next position in the ByteArray stream.
*/
template <class S>
ByteArray::const_iterator GetStringFromRawData( S& s, ByteArray::const_iterator i )
{
uint32 n;
i = GetFromRawData( n, i );
if ( n > 0 )
{
s.Assign( reinterpret_cast<const S::char_type*>( i ), 0, n );
i += n * sizeof( S::char_type );
}
else
s.Clear();
return i;
}
/*
* Template instantiations for the String type.
*/
void AddToRawData( ByteArray& b, const String& s )
{
AddStringToRawData( b, s );
}
ByteArray::const_iterator GetFromRawData( String& s, ByteArray::const_iterator i )
{
return GetStringFromRawData( s, i );
}
/*
* Template instantiations for the IsoString type.
*/
void AddToRawData( ByteArray& b, const IsoString& s )
{
AddStringToRawData( b, s );
}
ByteArray::const_iterator GetFromRawData( IsoString& s, ByteArray::const_iterator i )
{
return GetStringFromRawData( s, i );
}
With these simple routines we have a rough implementation of ByteArray-based data streams. The implementation is indeed rough, but efficient and sufficient to our purposes. I have in mind adding more capable and elegant data stream classes to PCL but for now these simple routines should do the trick quite well.
In the above code, note that strings require specific template instantiations and specializations, as these are aggregate objects. Basically, we need to store the length in characters (not bytes!) and, if the string is not empty, the sequence of characters as a set of raw bytes. Other simple types, such as int and float, double, etc., can be stored directly.
As an example, let's suppose that your CameraSettings class only has a few data members similar to these:
class CameraSettings
{
private:
String cameraName;
int imageWidth; // in pixels
int imageHeight; // ...
float readoutNoise; // in e-
int shutterSpeed; // in ms
};Then your conversion routines would look like these:
void CameraSettings::AddToRawData( ByteArray& b ) const
{
AddToRawData( b, cameraName );
AddToRawData( b, imageWidth );
AddToRawData( b, imageHeight );
AddToRawData( b, readoutNoise );
AddToRawData( b, shutterSpeed );
}
ByteArray::const_iterator CameraSettings::GetFromRawData( ByteArray::const_iterator i )
{
return GetFromRawData( shutterSpeed,
GetFromRawData( readoutNoise,
GetFromRawData( imageHeight,
GetFromRawData( imageWidth,
GetFromRawData( cameraName, i ) ) ) ) );
}Now all you need is storing and managing your private data streams with the Settings class. For example:
typedef Array<CameraSettings> camera_settings_container; // represents your set of supported cameras
void SaveCameras( const camera_settings_container& cameras )
{
ByteArray data;
for ( camera_settings_container::const_iterator i = cameras.Begin(); i != cameras.End(); ++i )
i->AddToRawData( data );
Settings::Write( "CameraData", data );
}
camera_settings_container LoadCameras()
{
camera_settings_container cameras;
ByteArray data;
if ( Settings::Read( "CameraData", data ) )
{
CameraSettings camera;
for ( ByteArray::const_iterator i = data.Begin(); i < data.End(); i = camera.GetFromRawData( i ) )
cameras.Add( camera );
}
return cameras;
}Disclaimer: code not tested, so it can contain some errors.
Hope this helps.