Вы находитесь на странице: 1из 3

Jon Jenkinson’s C++Builder Page

Streaming
This month we’re taking a look at streaming, something try
{
which plays a key part in the VCL, and of course C++. In the Buffy = new char[TempSize + 1];
VCL TStream provides the base for all streaming. sStream->Read(Buffy, TempSize);
Buffy[TempSize] = ‘\0’; //add null
This allows us to write truly re-usable functions in our //terminator
applications and especially our own classes. This is best shown FMyString = Buffy;
}
by an example, so here we have a simple data class with a __finally
couple of properties which we can then stream in and out. {
if(Buffy)
class JonsClass : public TObject delete [] Buffy;
{ }
private: }
AnsiString FMyString; else
TDateTime FMyDateTime; FMyString = “”;
public: }
__fastcall JonsClass();
__fastcall ~JonsClass(); The LoadFromStream is a little more complex because of
//streaming the way we have to handle the AnsiString (*see hot tip in the
void __fastcall LoadFromStream(TStream
sidebar). The read of the TDateTime is simple enough, as is
*sStream);
void __fastcall SaveToStream(TStream the size of the data to read next, but we have to have a
*sStream); temporary buffer to read data into the AnsiString itself.
//properties
__property String MyString =
Again we write using pointers directly into memory locations
{read=FMyString, write=FMyString}; so ensure that memory allocations have been done correctly.
__property TDateTime MyDateTime = Final note: on using the sizeof function, pass in the actual
{read=FMyDateTime,write=FMyDateTime}; variable name and not a data type - this way, if you change
}; the variable’s data type, you don’t have to filter it down.

Right, we’ve got a simple class with the normal things, the Now we have a couple of very powerful functions. As you’ll
only addition being the LoadFrom... and SaveTo...Stream see in the demo code accompanying this article, we can save
functions. These two simple functions are defined as our data to a file simply with the following,
follows, void __fastcall JonsClass::SaveToFile(String
FFileName)
void __fastcall JonsClass::SaveToStream(TStream {
*sStream) TFileStream *MyFile = NULL;
{//write data
int TempSize; try
//write DateTime {
sStream->Write(&FMyDateTime, MyFile = new TFileStream(FFileName,
sizeof(FMyDateTime)); fmCreate|fmShareDenyWrite);
//write String SaveToStream(MyFile);
}
TempSize = FMyString.Length();
__finally
sStream->Write(&TempSize, sizeof(TempSize));
{
sStream->Write(FMyString.c_str(), TempSize); if(MyFile)
} delete MyFile;
}
This function defines how our data will appear on the stream. }
With the TDateTime object, or other simple class objects
and data types, we simply write out the data to the stream. Similarly with loading from file,
void __fastcall JonsClass::LoadFromFile(String
However, the string and any other variable length data needs FFileName)
writing with a size parameter, which we write first, followed {
by the data itself. Note the use of pointers to pass data onto TFileStream *MyFile = NULL;
the stream. Basically, we copy the memory from the pointer try
for as many bytes as we need. {
MyFile = new TFileStream(FFileName,
void __fastcall fmOpenRead|fmShareDenyNone);
JonsClass::LoadFromStream(TStream *sStream)
{ LoadFromStream(MyFile);
char *Buffy = NULL; }
int TempSize; __finally
{
//read datetime off stream. if(MyFile)
sStream->Read(&FMyDateTime, sizeof(TDateTime)); delete MyFile;
//read string off stream }
sStream->Read(&TempSize, sizeof(TempSize)); }
if(TempSize > 0)
{
The same principle is then used with loading and saving to
any of the supported stream types: try
{
MyFile = new TFileStream(FFileName,
• TStringStream (for manipulating in-memory strings)
fmCreate|fmShareDenyWrite);
SaveToStream(MyFile);
• TMemoryStream (for working with a memory buffer) }
__finally
• TBlobStream (for working with BLOB fields) {
if(MyFile)
• TWinSocketStream (for reading and writing over a delete MyFile;
socket connection) }
}
• TOleStream (for using a COM interface to read and
write) void __fastcall jClassList::LoadFromFile(String
FFileName)
You simply create the stream and load and save by passing {
TFileStream *MyFile = NULL;
the stream through our functions.
try
More Power {
MyFile = new TFileStream(FFileName,
The real power of this type of streaming comes once you
start building up arrays of your class, in something like a fmOpenRead|fmShareDenyWrite);
list. You create simple Load and Save streaming functions LoadFromStream(MyFile);
which call the classes own functions for each instance, and }
__finally
then in the same way you use one of the available streaming {
classes as above. if(MyFile)
delete MyFile;
In the demo code we have a basic TObjectList to hold }
multiple instances of our dataclass. Here are the lists load }
and save from stream functions,
Now, if you haven’t spotted it, here is the real use of
void __fastcall
jClassList::LoadFromStream(TStream *sStream) streaming, we can go polymorphic. Once we have a
{ LoadFrom... and SaveTo... stream methods for a given class,
JonsClass *sparejClass; we simply create generic LoadFrom and SaveTo for anything
that can stream.
sStream->Seek(0, soFromBeginning);
So, for instance, we can now create polymorphic functions
while(sStream->Position < sStream->Size)
{
called JonsLoadFromFile and JonsSaveToFile, which simply
sparejClass = new JonsClass; take a method pointer to the streaming functions, declared
sparejClass->LoadFromStream(sStream); as,
Add(sparejClass);
} typedef void __fastcall (__closure *MyFunc
} )(TStream *);

void __fastcall void __fastcall jLoadFromFile(String sFileName,


jClassList::SaveToStream(TStream *sStream) MyFunc sFunc);
{ void __fastcall jSaveToFile(String sFileName,
int Counter = Count; MyFunc sFunc);
JonsClass *sparejClass;
defined as,
sStream->Size = 0; //we’re overwriting
for(int x = 0; x < Counter; x++) void __fastcall jLoadFromFile(String sFileName,
{ MyFunc sFunc)
sparejClass = dynamic_cast <JonsClass *> {
(Items[x]); TFileStream *MyFile = NULL;
sparejClass->SaveToStream(sStream);
} try
} {
MyFile = new TFileStream(sFileName,
As you can see they’re simple, using the classes own fmOpenRead|fmShareDenyWrite);
functions to do the actual work. The main thing to note is sFunc(MyFile);
that these functions position the stream to the start each }
time. __finally
{
The actual use of the list stream functions is as simple as if(MyFile)
those in the dataclass. Indeed the load and save to and from delete MyFile;
}
file functions are identical, showing the simplicity once }
you’ve defined the original class function.
void __fastcall jSaveToFile(String sFileName,
void __fastcall jClassList::SaveToFile(String
MyFunc sFunc)
FFileName)
{
{
TFileStream *MyFile = NULL;
TFileStream *MyFile = NULL;
However this is one of the common errors when dealing
try with AnsiStrings. Whilst the c_str() function does provide
{ a pointer to the start of a c style array of chars, it is a
MyFile = new TFileStream(sFileName,
fmCreate|fmShareDenyWrite);
temporary pointer, not a constant one.
sFunc(MyFile); In Delphi you have direct access to the underlying array, so
} you can write AnsiString[1] to get at the base pointer. In
__finally
{
BCB, the c_str() pointer only provides a temporary snapshot
if(MyFile) to the memory location of the array at any one time, thus it
delete MyFile; should only be used for reading. As soon as you write to
} the pointer you’re no longer writing to the AnsiString.
}
Also be warned, if you assign a new value to the AnsiString
and then used as follows, (assuming jClass is an instance of itself, the c_str() pointer you may already have will no longer
JonsClass and jClassList an instance of JonsClassList), be valid. If you need to take a c style string from the
AnsiString, then strcpy it out into a fresh buffer, trying to
jSaveToFile(“test.jsk”, jClass->SaveToStream); use the pointer returned by c_str for more than one call
jSaveToFile(“list.jsk”, jClassList- may look as if it works, but your application will suddenly
>SaveToStream);
come a cropper for no apparent reason.
jLoadFromFile(“test.jsk”, jClass- This is especially true in multi-threaded BCB apps, where
>LoadFromStream); you may find the c_str pointer changing from one call to
jLoadFromFile(“list.jsk”, jClassList-
>LoadFromStream);
the next as other threads work on the value. Thus if you use
c_str in a multi-threaded application ensure that you protect
You can then create a complete set of polymorphic functions the value with a TCriticalSection until you’ve finished with it
for each type of stream available that you may want to use, (and, by default, all writes to the underlying AnsiString).
safe in the knowledge that you can simply define your classes
Jon is our BUG Technical Leader for
with LoadFrom... and SaveTo...stream methods, the rest of
Scotland and the North of England, our
the work is done for you. webmaster, and compiles the
In the code for the article you’ll find the full definition of Developers’ Information Library CD.
JonsClass and JonsClass list as well as the polymorphic As if that wasn’t enough, Jon is a prime
functions. Obviously if you use the polymorphic method mover behind “The Bits...” C++Builder
you don’t need to define individual LoadFromFile and information and tutorial website - see
SaveToFile methods for each class. I’ve also included a www.richplum.co.uk/cbuilder.
couple of buttons (BlobSave and BlobLoad) which show You can contact him on jon.jenkinson@btconnect.com or
loading the class into and out of a TBlobField. 01670 368859.
We’ve just scratched the surface of streaming this time.
Next time we’ll look at TComponent streaming and also
create a class which uses streaming to provide a simple Angus’
database complete with index. Our class wont care whether
the stream it works with is a file, or in memory, we’ll simply
put bits of data here and there and then retrieve them.
Nifty
(The code to accompany this article is available from the Tips
downloads section of the BUG website, www.ukbug.co.uk)
Have you ever wanted to add a cool
SideBar gradient fill for your Delphi forms
Hot Tip : As an aside on AnsiStrings. If you’re coming from but didn’t want to buy a component to do so? Just
Delphi you can easily read AnsiStrings from a stream in the follow these simple steps:
following method, 1. Create a Windows Metafile (.wmf), using
var say PaintShopPro
TempInt :Integer;
begin 2. Create a new image - any size (make it
sStream.Read(TempInt, sizeof(Integer)); small, it’ll be doctored up later)
SetLength(FMyString, TempInt);
3. Use the paint bucket fill tool and select a
sStream.Read(FMyString[1], TempInt);
end; type of fill gradient.
4. Save it as a (.wmf).
This appears to imply to some BCB programmers that the
following would work, 5. (In Delphi) Place an image component on
a form, set the following properties: Align:
int TempInt; alClient Picture: Load the metafile you
created. Stretch: True.
sStream.Read(&TempInt, sizeof(TempInt));
FMyString.SetLength(TempInt); 6. A free gradient fill.
sStream.Read(FMyString.c_str(), TempInt);

Вам также может понравиться