how to retrieve inner objects of an object properly using TMemoryStream and TStringStream

109 Views Asked by At

I have 2 classes named Woman and Man. They have been registered for streaming system. Woman class has some attributes and most importantly an instance of class Man in it. Using TMemoryStream and TStringStream classes I was able to retrieve all attributes of Woman but Man*, by WriteComponent and ReadComponent methods of TmemoryStream class. Actually the compiler throws an exception and the reason is that Man* is NULL and is not loaded properly. In my program I need to load all attributes including simple data types and instances of other written classes. Please give me advice how to load Woman object properly so that Man* is not NULL any more. here is my code snippet.

#include <vcl.h>
#pragma hdrstop

#include <tchar.h>
#include <memory>
#include <iostream>
#include <conio.h>
#include <string>

#pragma argsused

using namespace std;

class Man : public TComponent
{
    private:
    double fMoney;
    public:
    __fastcall Man(TComponent* _Owner,double InMoney)
        : TComponent(_Owner)
        {
            fMoney = InMoney;
        }
    __published:
    __property double Money = {read=fMoney, write=fMoney};
};

class Woman : public TComponent
{
    private:
    int fAge;
    UnicodeString fMyName;
    Man* fManInClass;
    public:
    __fastcall Woman(TComponent* _Owner, int InAge, UnicodeString InName)
        : TComponent(_Owner)
    {
        fAge = InAge;
        fMyName = InName;
        fManInClass = new Man(this, 0);
    }
    __published:
    __property int Age = {read=fAge, write=fAge};
    __property UnicodeString MyName = {read=fMyName, write=fMyName};
    __property Man* ManInClass = {read = fManInClass, write = fManInClass};
};


void RegisterClassesWithStreamingSystem(void)
{

  #pragma startup RegisterClassesWithStreamingSystem
  Classes::RegisterClass(__classid(Man));
  Classes::RegisterClass(__classid(Woman));
}


int _tmain(int argc, _TCHAR* argv[])
{
    Woman* FirstWoman = new Woman(NULL, 25, "Anjelina");
    FirstWoman->ManInClass->Money = 2000;
    UnicodeString as;
    auto_ptr<TMemoryStream> MStr(new TMemoryStream);
    auto_ptr<TStringStream> SStr(new TStringStream(as));

    MStr->WriteComponent(FirstWoman);
    MStr->Seek(0, soFromBeginning);
    ObjectBinaryToText(MStr.get(), SStr.get());
    SStr->Seek(0, soFromBeginning);
    as = SStr->DataString;

    auto_ptr<TMemoryStream> pms(new TMemoryStream);
    auto_ptr<TStringStream> pss(new TStringStream(as));
    TComponent *pc;

    ObjectTextToBinary(pss.get(), pms.get());
    pms->Seek(0, soFromBeginning);

    pc = pms->ReadComponent(NULL);


    Woman* AWoman = dynamic_cast<Woman*>(pc);

    cout << AWoman->Age << endl;
    cout << AWoman->MyName.c_str() << endl;
    cout << AWoman->ManInClass->Money << endl; // AWoman->ManInClass is NULL -> Exception

    delete FirstWoman;
    pc->Free();
    getch();
    return 0;
}
1

There are 1 best solutions below

1
Remy Lebeau On

This is related to a similar problem I described in my answer to your earlier question - namely, that your Woman class is defining a constructor that has custom parameters, and as such the DFM streaming system cannot call that constructor when reading a Woman from a DFM stream. That is why your Man* pointer is NULL - your constructor is not being called to initialize that pointer.

There is only one constructor signature that the DFM can call:

__fastcall <classname>(TComponent* Owner)

You cannot change that signature. You can define additional overloaded constructors with extra parameters as needed, but the above constructor is required for DFM streaming.

Try this instead:

#include <vcl.h>
#pragma hdrstop

#include <tchar.h>
#include <memory>
#include <iostream>
#include <conio.h>
#include <string>

#pragma argsused

using namespace std;

class Man : public TComponent
{
private:
    double fMoney;

public:
    __fastcall Man(TComponent* Owner)
        : TComponent(Owner)
    {
    }

    __fastcall Man(TComponent* Owner, double InMoney)
        : TComponent(Owner)
    {
        fMoney = InMoney;
    }

__published:
    __property double Money = {read=fMoney, write=fMoney};
};

class Woman : public TComponent
{
private:
    int fAge;
    UnicodeString fMyName;
    Man* fManInClass;

    void __fastcall SetManInClass(Man *Value)
    {
        fManInClass->Money = Value->Money;
    }

public:
    __fastcall Woman(TComponent* Owner)
        : TComponent(Owner)
    {
        fManInClass = new Man(this);
        fManInClass->SetSubComponent(true);
    }

    __fastcall Woman(TComponent* Owner, int InAge, UnicodeString InName)
        : TComponent(Owner)
    {
        fAge = InAge;
        fMyName = InName;
        fManInClass = new Man(this);
        fManInClass->SetSubComponent(true);
    }

__published:
    __property int Age = {read=fAge, write=fAge};
    __property UnicodeString MyName = {read=fMyName, write=fMyName};
    __property Man* ManInClass = {read = fManInClass, write = SetManInClass};
};

void RegisterClassesWithStreamingSystem(void)
{
  Classes::RegisterClass(__classid(Man));
  Classes::RegisterClass(__classid(Woman));
}
#pragma startup RegisterClassesWithStreamingSystem

int _tmain(int argc, _TCHAR* argv[])
{
    auto_ptr<Woman> FirstWoman(new Woman(NULL, 25, L"Anjelina"));
    FirstWoman->ManInClass->Money = 2000;

    auto_ptr<TMemoryStream> MStr(new TMemoryStream);
    auto_ptr<TStringStream> SStr(new TStringStream(L""));

    MStr->WriteComponent(FirstWoman);
    MStr->Position = 0;
    ObjectBinaryToText(MStr.get(), SStr.get());
    SStr->Position = 0;
    UnicodeString as = SStr->DataString;

    auto_ptr<TMemoryStream> pms(new TMemoryStream);
    auto_ptr<TStringStream> pss(new TStringStream(as));

    ObjectTextToBinary(pss.get(), pms.get());
    pms->Position = 0;

    auto_ptr<TComponent> pc(pms->ReadComponent(NULL));

    Woman* AWoman = static_cast<Woman*>(pc.get());    

    cout << AWoman->Age << endl;
    cout << AWoman->MyName.c_str() << endl;
    cout << AWoman->ManInClass->Money << endl;

    pc.reset();
    FirstWoman.reset();

    getch();
    return 0;
}

Also notice the extra calls to fManInClass->SetSubComponent(). That is needed if you ever want to use your Woman component in the Form Designer at design-time and set its properties and sub-properties in the Object Inspector. The DFM streaming system also makes use of the internal csSubComponent flag that SetSubComponent() sets/clears. This is a signal to the VCL system that the fManInClass object is owned by the Woman object and has special handling.

Also notice the setter method added for the ManInClass property. Since Woman owns the Man object, you don't want to allow an outside caller to change the value of the fManInClass variable, thus causing a memory leak and ownership conflicts. A published property needs to be read/write in order to be DFM-streamable (unless you override the virtual TComponent::DefineProperties() method to provide custom streaming), so it needs both a getter and a setter, but you can use class methods to protect access to that variable.

I strongly suggest you get yourself a good book on how to write VCL components.