wxFFile, why can't I get the correct result?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

wxFFile, why can't I get the correct result?

Post by dqf88 »

when I show the CustName's value (cout<<"TextCtrl2:"<<Cust->CustName<<endl;), why can't I get the correct result?

Code: Select all

#include <wx/list.h>
#include <wx/ffile.h>
#include <wx/listimpl.cpp>
#include <iostream>
using namespace std;

class Customer
{
public:
    int CustID=1;
    wxString CustName=wxString("temple string");
    wxPoint CustPoint=wxPoint(0,0);
    double Value=1.1;
    bool SaveObjects( wxFFile &fFile )
    {
        fFile.Write( &CustID, sizeof( CustID ) );
        fFile.Write( &CustName, sizeof( wxString ) );
        fFile.Write( &CustPoint, sizeof( wxPoint ) );
        fFile.Write( &Value, sizeof( Value ) );
    }
    bool LoadObjects( wxFFile &fFile )
    {
        fFile.Read( &CustID, sizeof( CustID ) );
        fFile.Read( &CustName, sizeof( wxString ) );
        fFile.Read( &CustPoint, sizeof( wxPoint ) );
        fFile.Read( &Value, sizeof( Value ) );
    }
};

void DDDialog::OnAbout(wxCommandEvent& event)
{
    wxFFile m_fFile;
    m_fFile.Open( wxT("data.txt"), "a+b" );
    Customer* Cust = new Customer;
    Cust->CustID=wxAtoi(TextCtrl1->GetValue());
    Cust->CustName=TextCtrl2->GetValue();
    Cust->CustPoint=wxPoint(wxAtoi(TextCtrl3->GetValue()),0);
    Cust->Value=wxAtof(TextCtrl4->GetValue());
    Cust->SaveObjects(m_fFile);
    delete Cust;
    m_fFile.Close();
}

void DDDialog::OnButton3Click(wxCommandEvent& event)
{
    system("cls");
    wxFFile m_fFile;
    m_fFile.Open( wxT("data.txt"), "rb" );
    Customer* Cust = new Customer;
    while(!m_fFile.Eof())
    {
        Cust->LoadObjects(m_fFile);
        cout<<"TextCtrl1:"<<Cust->CustID<<endl;
        cout<<"TextCtrl2:"<<Cust->CustName<<endl;
        cout<<"TextCtrl3:"<<Cust->CustPoint.x<<endl;
        cout<<"TextCtrl4:"<<Cust->Value<<endl;
    }
    cout<<endl;
    delete Cust;
    m_fFile.Close();
}

User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxFFile, why can't I get the correct result?

Post by doublemax »

Code: Select all

fFile.Write( &CustName, sizeof( wxString ) );
You can't save a wxString this way. You just save the datastructure itself, but not the allocated text which lies in a different memory location.

I would suggest to use wxFileConfig for this.
Use the source, Luke!
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

Re: wxFFile, why can't I get the correct result?

Post by dqf88 »

doublemax wrote:

Code: Select all

fFile.Write( &CustName, sizeof( wxString ) );
You can't save a wxString this way. You just save the datastructure itself, but not the allocated text which lies in a different memory location.

I would suggest to use wxFileConfig for this.
I need save items with this method. How can I do it this way? thanks
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxFFile, why can't I get the correct result?

Post by PB »

dqf88 wrote:I need save items with this method. How can I do it this way? thanks
I suggest checking wxFFile documentation. If you do that, you will learn wxFFile actually has a method specifically designed for writing wxStrings., i.e.

Code: Select all

bool wxFFile::Write (const wxString&	str, const wxMBConv& conv = wxConvAuto())
Reading a string previously written like that on the other hand...

If you really must use wxFFile, I suggest just writing and reading it the "good" old way. I.e.
1. Write the number of bytes the wxString contains in UTF-8 mode.
2. Write the actual binary representation of wxString UTF-8 encoded characters.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxFFile, why can't I get the correct result?

Post by doublemax »

Code: Select all

// reading
// appname and vendorname are ignored when using wxFileConfig

// the directory for the .INI file must exist and you must have write permission.
// The file itself can be missing, then it will be created
wxFileConfig *conf = new wxFileConfig(wxT("appname"), wxT("vendorname"), wxT("c:\\mydata\\myconfig.ini"));
if(conf)
{
  wxConfigBase::Set(conf);
  wxString username = conf->Read( wxT("/username"), "");
  long value = conf->Read( wxT("/someinteger"), 0);
}

// writing
wxConfigBase *conf = wxConfigBase::Get(false);
if(conf)
{
  conf->Write( wxT("/username"), some_text_control->GetValue() );
  conf->Write( wxT("/someinteger"), 17 );
}
Use the source, Luke!
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

Re: wxFFile, why can't I get the correct result?

Post by dqf88 »

Why the "LoadWxString" doesn't work? Thanks again

Code: Select all

bool SaveWxString( wxFFile &fFile, wxString &str )
{
	long lHelp;
	if ( ! fFile.IsOpened() )
		return false;
	lHelp = (long)str.Len() + 1L;
	fFile.Write( &lHelp, sizeof( lHelp ) );
	fFile.Write( str.c_str(), lHelp );
	return true;
}
wxString LoadWxString( wxFFile &fFile )
{
	long lHelp;
	wxString strRet;
	strRet.Empty();
	if ( ! fFile.IsOpened() )
		return strRet;
	if ( sizeof( lHelp ) == fFile.Read( &lHelp, sizeof( lHelp ) ) )
	{
		if ( lHelp > 0L )
		{
			wxChar *cp;
			cp = new wxChar[ lHelp ];
			if ( cp )
			{
				fFile.Read( cp, lHelp );
				*(cp + lHelp - 1) = 0x00;
				strRet = cp;
				delete [] cp;
			}
		}
	}
	return strRet;
}
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxFFile, why can't I get the correct result?

Post by PB »

When you run into a problem, it's best to debug it by yourself first, e.g. step into the code and watch what happens there, including the content of the variables. Such process is extremely simple and easy with the current IDEs.

When you ask people for advice and you get it, heed the advice instead of ignoring it and blindly stomping the wrong way.

When you ask for a length of wxString, you get the count of characters it has, not the number of bytes the characters take. In Unicode build on MSW, wxChar is 16-bit, AFAIK on Linux it is 32-bit (see wchar_t). I.e., the number of bytes needed to store the content of wxString is sizeof(wxChar) * wxString::size(). Of course, depending on wxChar size makes your program/data unportable. That's why I recommended to always store the wxString in portable way using UTF-8.

Additionally, when storing data such as customer recrods, it is best to use the tool designed for it - a database such as (wx)SQLite.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxFFile, why can't I get the correct result?

Post by PB »

If you really need to, I guess you could do that as I demonstrate in the simple app below:

Code: Select all

#include <wx/wx.h>
#include <wx/ffile.h>

bool WritewxStringToFFile(wxFFile& file, const wxString& str)
{
    wxCharBuffer buf(str.utf8_str());
    const wxUint32 bufLength = buf.length();    

    if ( file.Write(&bufLength, sizeof(bufLength)) != sizeof(bufLength) )
        return false;
    
    if ( bufLength > 0 )
    {
        if ( file.Write(buf.data(), bufLength) != bufLength )
            return false;
    }
    
    wxLogMessage("Wrote \"%s\" (%zu characters, %zu bytes).", str, str.size(), bufLength);
    return true;
}

bool ReadwxStringFromFFile(wxFFile& file, wxString& str)
{    
    wxUint32 bufLength = 0;    

    if ( file.Read(&bufLength, sizeof(bufLength)) != sizeof(bufLength) )
        return false;
    
    if ( bufLength > 0 )
    {
        wxCharBuffer buf(bufLength);

        if ( file.Read(buf.data(), bufLength) == bufLength )        
            str = wxString::FromUTF8(buf, bufLength);
        else
            return false;              
    } else
        str = wxS("");
     
    wxLogMessage("Read \"%s\" (%zu characters, %zu bytes).", str, str.size(), bufLength);
    return true;
}

bool WriteStringsToFFile(const wxString& path, const wxArrayString& strings)
{
    wxFFile file(path, "w+b"); // create an empty file for binary writing

    if ( !file.IsOpened() )
        return false;

    for ( size_t i = 0; i < strings.size(); i++ )
    {
        if ( !WritewxStringToFFile(file, strings[i]) )
        {
            wxLogMessage("Failed to write \"%s\".", strings[i]);
            return false;
        }
    }
    
    return true;
}

bool ReadStringsFromFFile(const wxString& path, wxArrayString& strings)
{    
    wxFFile file(path, "rb");

    strings.clear();

    if ( !file.IsOpened() )
        return false;

    while ( !file.Error() && file.Tell() < file.Length() )
    {
        wxString str;
        if ( !ReadwxStringFromFFile(file, str) )
        {
            wxLogMessage("Failed to read the string.");
            return false;
        }
        strings.push_back(str);
    } 
    
   return true;
}


class MyFrame : public wxFrame
{
public:
    MyFrame()
        : wxFrame(NULL, wxID_ANY, "Test", wxDefaultPosition, wxSize(500,500))
    {                
        wxTextCtrl* textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
        wxLog::DisableTimestamp();
        wxLog::SetActiveTarget(new wxLogTextCtrl(textCtrl));

        // ensure the file is in writable location
        wxString fileName = "C:\\FFile_test.bin";
        TestWrite(fileName);
        TestRead(fileName);
    }

    void TestWrite(const wxString& fileName)
    {       
        wxArrayString as;
        
        as.push_back("John Smith");                
        as.push_back(""); // empty string
        // and now actually test the unicode
        wxString str = wxString::FromUTF8("\xE4\xB8\xAD\xE5\x9B\xBD\xE8\xAA\x9E");
        as.push_back(str);

        WriteStringsToFFile(fileName, as);        
    }

    void TestRead(const wxString& fileName)
    {        
        wxArrayString as;
                
        ReadStringsFromFFile(fileName, as);
    }
};

class MyApp : public wxApp
{
public:
    virtual bool OnInit()
    {
        (new MyFrame)->Show();
        return true;
    }
};
wxIMPLEMENT_APP(MyApp);
The output after running the program
ffile.png
ffile.png (16.39 KiB) Viewed 3976 times
I still advise against doing that, and in the real application it won't be that easy as you probably won't be storing only strings there (you would use e.g. wxTextFile if this were the case]. You must create your own file format and track the records accordingly. And that's just one of the many reasons why it is much better to use a database.
Last edited by PB on Wed Aug 03, 2016 5:13 pm, edited 1 time in total.
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

Re: wxFFile, why can't I get the correct result?

Post by dqf88 »

PB wrote:If you really need to, I guess you could do that as I demonstrate in the simple app below:

Code: Select all

#include <wx/wx.h>
#include <wx/ffile.h>

bool WritewxStringToFFile(wxFFile& file, const wxString& str)
{
    wxCharBuffer buf(str.utf8_str());
    const size_t bufLength = buf.length();    

    if ( file.Write(&bufLength, sizeof(bufLength)) != sizeof(bufLength) )
        return false;
    
    if ( bufLength > 0 )
    {
        if ( file.Write(buf.data(), bufLength) != bufLength )
            return false;
    }
    
    wxLogMessage("Wrote \"%s\" (%zu characters, %zu bytes).", str, str.size(), bufLength);
    return true;
}

bool ReadwxStringFromFFile(wxFFile& file, wxString& str)
{    
    size_t bufLength = 0;    

    if ( file.Read(&bufLength, sizeof(bufLength)) != sizeof(bufLength) )
        return false;
    
    if ( bufLength > 0 )
    {
        wxCharBuffer buf(bufLength);

        if( file.Read(buf.data(), bufLength) == bufLength )        
            str = wxString::FromUTF8(buf);
        else
            return false;              
    } else
        str = wxS("");
     
    wxLogMessage("Read \"%s\" (%zu characters, %zu bytes).", str, str.size(), bufLength);
    return true;
}

bool WriteStringsToFFile(const wxString& path, const wxArrayString& strings)
{
    wxFFile file(path, "w+b"); // create an empty file for binary writing

    if ( !file.IsOpened() )
        return false;

    for ( size_t i = 0; i < strings.size(); i++ )
    {
        if ( !WritewxStringToFFile(file, strings[i]) )
        {
            wxLogMessage("Failed to write \"%s\".", strings[i]);
            return false;
        }
    }
    
    return true;
}

bool ReadStringsFromFFile(const wxString& path, wxArrayString& strings)
{    
    wxFFile file(path, "rb");

    strings.clear();

    if ( !file.IsOpened() )
        return false;

    while ( !file.Error() && file.Tell() < file.Length() )
    {
        wxString str;
        if ( !ReadwxStringFromFFile(file, str) )
        {
            wxLogMessage("Failed to read the string.");
            return false;
        }
        strings.push_back(str);
    } 
    
   return true;
}


class MyFrame : public wxFrame
{
public:
    MyFrame()
        : wxFrame(NULL, wxID_ANY, "Test", wxDefaultPosition, wxSize(500,500))
    {                
        wxTextCtrl* textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
        wxLog::SetActiveTarget(new wxLogTextCtrl(textCtrl));

        wxBoxSizer* sizer;
        sizer = new wxBoxSizer(wxVERTICAL);        
        sizer->Add(textCtrl, 1, wxALL | wxEXPAND, 5);
        SetSizer(sizer);

        // ensure the file is in writable location
        wxString fileName = "C:\\FFile_test.bin";
        TestWrite(fileName);
        TestRead(fileName);
    }

    void TestWrite(const wxString& fileName)
    {       
        wxArrayString as;
        
        as.push_back("John Smith");                
        as.push_back(""); // empty string
        // and now actually test the unicode
        wxString str = wxString::FromUTF8("\xE4\xB8\xAD\xE5\x9B\xBD\xE8\xAA\x9E");
        as.push_back(str);

        WriteStringsToFFile(fileName, as);        
    }

    void TestRead(const wxString& fileName)
    {        
        wxArrayString as;
                
        ReadStringsFromFFile(fileName, as);
    }
};

class MyApp : public wxApp
{
public:
    virtual bool OnInit()
    {
        (new MyFrame)->Show();
        return true;
    }
};
wxIMPLEMENT_APP(MyApp);
The output after running the program
ffile.png
I still advise against doing that, and in the real application it won't be that easy as you probably won't be storing only strings there (you would use e.g. wxTextFile if this were the case]. You must create your own file format and track the records accordingly. And that's just one of the many reasons why it is much better to use a database.
Thank you very much, the code is really what I want to write :D :D
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxFFile, why can't I get the correct result?

Post by PB »

dqf88 wrote:Thank you very much, the code is really what I want to write :D :D
OK, just be aware the code I posted was written in a hurry as an example, so there is even less guarantees than usual that it is bug-free.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxFFile, why can't I get the correct result?

Post by PB »

PB wrote:OK, just be aware the code I posted was written in a hurry as an example, so there is even less guarantees than usual that it is bug-free.
And now I realized there is at least one bug in the code. One cannot write/read size_t variables (bufLength in the code above) as binary, this is going to break as the sizeof(size_t) is different between 32- and 64-bit programs. A fixed size type has to be used instead, such as wxUint32 (assuming string byte size fits into a 32-bit value). I have updated the code example now.
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

Re: wxFFile, why can't I get the correct result?

Post by dqf88 »

PB wrote:
PB wrote:OK, just be aware the code I posted was written in a hurry as an example, so there is even less guarantees than usual that it is bug-free.
And now I realized there is at least one bug in the code. One cannot write/read size_t variables (bufLength in the code above) as binary, this is going to break as the sizeof(size_t) is different between 32- and 64-bit programs. A fixed size type has to be used instead, such as wxUint32 (assuming string byte size fits into a 32-bit value). I have updated the code example now.
Thanks for your patience, :)
dqf88
Experienced Solver
Experienced Solver
Posts: 55
Joined: Fri Aug 10, 2012 9:59 am

Re: wxFFile, why can't I get the correct result?

Post by dqf88 »

doublemax wrote:

Code: Select all

// reading
// appname and vendorname are ignored when using wxFileConfig

// the directory for the .INI file must exist and you must have write permission.
// The file itself can be missing, then it will be created
wxFileConfig *conf = new wxFileConfig(wxT("appname"), wxT("vendorname"), wxT("c:\\mydata\\myconfig.ini"));
if(conf)
{
  wxConfigBase::Set(conf);
  wxString username = conf->Read( wxT("/username"), "");
  long value = conf->Read( wxT("/someinteger"), 0);
}

// writing
wxConfigBase *conf = wxConfigBase::Get(false);
if(conf)
{
  conf->Write( wxT("/username"), some_text_control->GetValue() );
  conf->Write( wxT("/someinteger"), 17 );
}
Thanks, you helped me a lot. :)
Post Reply