PDA

View Full Version : QSettings, custom format and class methods as read/write functions.



leppa
31st March 2010, 11:40
Hello everybody,

I've run into one problem, but first of all I'll describe what I tried to achieve. I'm writing a multiuser client app with server-side authentication. This app has to store some user's settings on his computer. This settings need to be encrypted with a key, which is stored on the server-side and is sent after user's authentication. Every user has its own unique key.

I came up with the following solution: I've created a wrapper class that contains a QSettings object, an encryption key, setValue(), value(), sync(), writeFile() and readFile() methods. The methods setValue(), value() and sync() are calling the corresponding QSettings's methods. writeFile() and readFile() are declared according to QSettings::WriteFunc and QSettings::ReadFunc and do writing/reading settings to/from the file. They also encrypt/decrypt the file if the encryption key is not NULL.

The class is inside a namespace as all other app stuff.

Now the stripped down header file:


namespace CLIENT {

class Settings: public QObject
{
Q_OBJECT

public:
Settings(const QString &user, const Key &k, QObject *parent = 0);
void setValue(const QString &key, const QVariant &value);
void sync();
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
~Settings();

private:
QSettings::Format f;
QSettings *s;
Key key;

bool readFile(QIODevice &device, QSettings::SettingsMap &map);
bool writeFile(QIODevice &device, const QSettings::SettingsMap &map);
};

}

And source file:


namespace CLIENT {

Settings::Settings(const QString &user, const Key &k, QObject *parent)
: QObject(parent), key(k)
{
f = QSettings::registerFormat("settings", (QSettings::ReadFunc)&Settings::readFile, (QSettings::WriteFunc)&Settings::writeFile);

QString appname = QCoreApplication::applicationName();
if (!user.isEmpty())
appname.append(".").append(user);

s = new QSettings(f, QSettings::UserScope, QCoreApplication::organizationName(), appname, parent);
}

void Settings::setValue(const QString &key, const QVariant &value)
{
s->setValue(key, value);
}

void Settings::sync()
{
s->sync();
}

QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
return s->value(key, defaultValue);
}

Settings::~Settings()
{
delete s;
}

bool Settings::readFile(QIODevice &device, QSettings::SettingsMap &map)
{
// Reads data from device, decrypts it with the key and saves to map
}

bool Settings::writeFile(QIODevice &device, const QSettings::SettingsMap &map)
{
#ifdef DEBUG
qDebug() << Q_FUNC_INFO << endl << "Writing map:" << map;
#endif // DEBUG
// Saves map to QByteArray, encrypts it with the key and writes to device
}

}


Now about the problem:
I get a segfault at line 350 in qmap.h:

inline const_iterator constBegin() const { return const_iterator(e->forward[0]); }
which traces back to the line 43 of my code (the first access to map).

The debugger tells me that the map is <not in scope>, the device seems to be <not in scope> too.

Am I doing something wrong?
Is there any way I can get my class working with QSettings or do I have to write it from scratch?

I'm using Qt 4.6.2 (minGW).

leppa
3rd April 2010, 14:24
Forgot to mention. If I move writeFile() and readFile() out of the class, i.e., make them regular functions like this:


bool readFile(QIODevice &device, QSettings::SettingsMap &map);
bool writeFile(QIODevice &device, const QSettings::SettingsMap &map);

and change line 6 of source file to

f = QSettings::registerFormat("settings", readFile, writeFile);
everything starts working as expected. But then I'm unable to use per-instance encryption key as there is no way to determine an instance of QSettings that calls writeFile() and readFile() in this case.

PS: It would be much easier if QSettings class provided oveloadable reading and writing methods.

mstegehu
6th December 2010, 16:22
Settings::Settings(const QString &user, const Key &k, QObject *parent)
: QObject(parent), key(k)
{
f = QSettings::registerFormat("settings", (QSettings::ReadFunc)&Settings::readFile, (QSettings::WriteFunc)&Settings::writeFile);

QString appname = QCoreApplication::applicationName();
if (!user.isEmpty())
appname.append(".").append(user);

s = new QSettings(f, QSettings::UserScope, QCoreApplication::organizationName(), appname, parent);
}

I can image that you will have problems with object not being created if you use it in the constructor.

I think this should work:

f = QSettings::registerFormat("settings", readFile, writeFile);


But than you can not call the QSetting constructor unless your Settings object is created:

s = new QSettings(f, QSettings::UserScope, QCoreApplication::organizationName(), appname, parent);

Thus try putting the last code segment in an initialize function instead of the constructor. readFile and writeFile might not exists.

high_flyer
6th December 2010, 16:46
f = QSettings::registerFormat("settings", (QSettings::ReadFunc)&Settings::readFile, (QSettings::WriteFunc)&Settings::writeFile);
Why are you using this at all like that? (function pointers??)
Why not just define and implement your methods normally?

But if you do, you probably mean to do:


typedef void (QSettings::*pReadFunc)(QIODevice &, QSettings::SettingsMap &);
pReadFunc = &Settings::writeFile;
//Same for pWriteFunc;
f = QSettings::registerFormat("settings",pReadFunc,pWriteFunc);


I didn't test my code here for compilation, it should be regarded as pseudo code.
The idea is that you didn't fully described your function signature.
EDIT:
Actually much more is missing - object initialization, and then assigning the function pointer from that object.

Again, this is complicating things WAAAAY more than needed.

Timoteo
6th December 2010, 17:02
You don't need a typedef or variable assignment to pass a function pointer. His prototype is already available to the compiler from his class header. Also, how else do you use a callback in c++ without passing a functor?

high_flyer
7th December 2010, 10:03
You don't need a typedef or variable assignment to pass a function pointer.
No one said you did.
Its just more readable this way, when you don't put the whole definition as parameter - that was not the point I was making.


His prototype is already available to the compiler from his class header.
true.
Was to quick to write, before thought through.


Also, how else do you use a callback in c++ without passing a functor?
I was on the wrong path.
I thought he was trying to do something else than what I see now he does, and thought the whole approach was complicating things.
But I think I understand the problem now.
Will have to think to see if I can see what can be wrong.

Timoteo
7th December 2010, 12:32
No one said you did.
Its just more readable this way, when you don't put the whole definition as parameter - that was not the point I was making.
You don't have to define anything, you are simply passing the address of the function. The "definition" aka prototype is already supplied for his function in his class header, and the receiver has a specification for it in the parameter of the function he is calling. Do you seriously think that


passFuncPointer(someFunc);
must look like

/*seriously?*/
passFuncPointer(someFunc(int,int,float));
without a typedef?

Here's some code to show you what I mean.

#include <QDebug>
void someFunc(int p)
{
qDebug() << p;
}
void call_ftor(void(*func)(int))
{
func(13);
}
int main(int argc, char* argv[])
{
/*passing the function pointer*/
call_ftor(someFunc);
}