I'm not sure if this is possible but here it goes.
Usually I send strings like this...
Connection.IOHandler.WriteLn('alpha');
Connection.IOHandler.WriteLn('bravo');
Connection.IOHandler.WriteLn('charley');
//and so on..
But what if I want to send it in just one go, just send it all at once.
Maybe I could put it on an array of strings then send the array.
someStr : array[1..3] of string = ('alpha','bravo','charley');//this could be more
...
StrListMem := TMemoryStream.Create;
try
StrListMem.WriteBuffer(someStr[0], Length(someStr));
StrListMem.Position:=0;
Connection.IOHandler.Write(StrListMem, 0, True);
finally
StrListMem.Free;
end;
I just have no idea how to this right, maybe somebody can give an example? and how the receiver(client) will read it.
EDIT:
I also having problem in how to read the stream, not sure what's wrong with this code.
Client:
msRecInfo: TMemoryStream;
arrOf: Array of Char;
...
msRecInfo := TMemoryStream.Create;
try
AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
SetLength(arrOf, msRecInfo.Size div SizeOf(Char));
msRecInfo.Position := 0;
msRecInfo.ReadBuffer(arrOf[0], Length(arrOf) * SizeOf(Char));
finally
MessageBox(0,pChar(arrOf[0]),0,0);//memo1.Lines.Add(arrOf[0]);
msRecInfo.Free;
end;
You can write the whole array to stream in one swoop. You actually already wrote close to a correct code
StrListMem := TMemoryStream.Create;
try
for I := 0 to Length(someStr) - 1 do
begin
StrListMem.WriteBuffer(someStr[I][1], Length(someStr[I]) * SizeOf(Char));
StrListMem.WriteBuffer(sLineBreak[1], Length(sLineBreak) * SizeOf(Char));
end;
StrListMem.Position:=0;
Connection.IOHandler.Write(StrListMem, 0, True);
finally
StrListMem.Free;
end;
Alternatively if you want to only work with data and abstract the transport layer away (read, easier coding) you can check out my IMC. It abstracts some things for you making it easier.
EDIT:
I added the newline character after each string as it seems to be consistent with original OP code. I don't have Delphi at hand so do not blindly copy the code please.
EDIT:
After reading the question again I realized that his goal is only to send it in one go and it is not mandatory to use a string array. So there is a better way to do it.
StrListMem := TStringList.Create;
try
StrListMem.Add('alpha');
StrListMem.Add('bravo');
StrListMem.Add('charley');
Connection.IOHandler.WriteBufferOpen;
try
Connection.IOHandler.Write(StrListMem);
finally
Connection.IOHandler.WriteBufferClose;
end;
finally
StrListMem.Free;
end;
To read it
StrListMem := TStringList.Create;
try
IdTCPClient1.IOHandler.ReadStrings(StrListMem);
finally
StrListMem.Free;
end;
Easier and cleaner :)
Related
I would like to use SaveToStream to save a ClientDataSet ALONG WITH OTHER MATERIAL. Here is a short sample:
filename := ChangeFileExt(Application.ExeName, '.dat');
FS := TFileStream.Create(filename, fmCreate);
CDS.SaveToStream(FS);
ShowMessage('After save, position is ' + IntToStr(FS.Position));
{now write a longint}
L := 1234;
siz := SizeOf(L);
Write(L, siz);
FS.Free;
But when I try to load this back in using LoadFromStream, and I again display the position after the ClientDataSet has been loaded, I see that the position is now 4 bytes AFTER the clientdataset was originally saved. It seems that CDS.LoadFromStream just plows ahead and consumes whatever follows it. As a result, when I then try to read the longint, I get an end of file error.
It is not sufficient to just use the CDS.SaveToStream at the end of creating a file, because what I'd really like to do is to save TWO clientdatasets to the file, one after the other, plus other material.
Ideas? Thanks.
[NB, this solution is essentially doubling up the work that (TLama's suggestion) "ReadDataPacket/WriteDataPacket" already does internally. I would use TLama's approach i.e. sub-class TClientDataSet to expose the above protected methods, and use the WriteSize parameter.]
Save the datasets to a temporary stream and then copy that to your destination stream with size information:
procedure InternalSaveToStream(AStream: TStream);
var
ATempStream: TMemoryStream;
ASize: Int64;
begin
ATempStream := TMemoryStream.Create;
// Save first dataset:
DataSet1.SaveToStream(ATempStream, dfBinary);
ASize := ATempStream.Size;
AStream.WriteData(ASize);
ATempStream.Position := 0;
AStream.CopyFrom(ATempStream, ALength);
ATempStream.Clear;
// Save second dataset:
DataSet2.SaveToStream(ATempStream, dfBinary);
ASize := ATempStream.Size;
AStream.WriteData(ASize);
ATempStream.Position := 0;
AStream.CopyFrom(ATempStream, ALength);
ATempStream.Clear;
FreeAndNil(ATempStream);
end;
To read back, first read the size and then copy that section of your source to a temporary stream again and load your dataset from that:
procedure InternalLoadFromStream(AStream: TStream);
var
ATempStream: TMemoryStream;
ASize: Int64;
begin
ATempStream := TMemoryStream.Create;
// Load first datset:
AStream.Read(ASize,SizeOf(ASize));
ASize := ATempStream.Size;
ATempStream.CopyFrom(AStream,ASize);
ATempStream.Position := 0;
DataSet1.LoadFromStream(ATempStream);
//...etc.
end;
I'm following this tutorial to the letter for saving and loading my VirtualStringTree: http://wiki.freepascal.org/VirtualTreeview_Example_for_Lazarus
My problem now is that when I have saved and loaded, my data is corrupted. Chars are changed and added. So does anyone know what the problem is?
I found this Link and changed the awnser to fit my code.
Data.Symbol[1] //Simply points to a char. So I changed it to 'char'
Read:
Stream.Read(Len, SizeOf(Len));
SetLength(Data.Column0, Len);
Stream.Read(PChar(Data.Column0)^, Len * SizeOf(Char));
//Copy/Paste this code for all Columns
Write:
Len := Length(Data.Column0);
Stream.Write(Len, SizeOf(Len));
Stream.Write(PChar(Data.Column0)^, Length(Data.Column0) * SizeOf(Char));
//Copy/Paste this code for all Columns
Second answer by TLama:
Read:
var
TreeData: PTreeData;
BinaryReader: TBinaryReader;
begin
TreeData := Sender.GetNodeData(Node);
BinaryReader := TBinaryReader.Create(Stream);
try
TreeData.Column0 := BinaryReader.ReadString;
TreeData.Column1 := BinaryReader.ReadString;
TreeData.Column2 := BinaryReader.ReadString;
finally
BinaryReader.Free;
end;
end;
Write:
var
TreeData: PTreeData;
BinaryWriter: TBinaryWriter;
begin
TreeData := Sender.GetNodeData(Node);
BinaryWriter := TBinaryWriter.Create(Stream);
try
BinaryWriter.Write(TreeData.Column0);
BinaryWriter.Write(TreeData.Column1);
BinaryWriter.Write(TreeData.Column2);
finally
BinaryWriter.Free;
end;
end;
I implemented TLama's answer because I really can't be bothered with all the pointers. This code looks much better, is a lot easier to read, takes up less space and does the same.
So to conclude. When you search the web for saving your VST data, your gonna get a lot of junk code. The first answer will fix that code for you. The second will make you smile :)
I have an application which receives the print command and decodes it.
I save the print command in a text file. And then read it in a byte array.
The decoded string also contains image part which are displayed as Junk characters.
When I try to send the byte array to the printer using WritePrinter function, it returns False.
I tried to check the error code returned which was 1784 but couldn’t find anything about this error code and why it may be happening.
Please find below the code snippet :
AssignFile (decodedfile, st_path + '\Sample.txt');
reset(decodedfile);
SetLength(buffer, FileSize(decodedfile));
For i := 1 to FileSize(decodedfile) do
Read(decodedfile, buffer[i - 1]);
CloseFile(decodedfile);
DocInfo.pDocName := pChar('Direct to Printer');
DocInfo.pOutput := Nil;
DocInfo.pDatatype := pChar('RAW');
PrinterName := cmbPrinters.Text;;
if not WinSpool.OpenPrinter(pChar(PrinterName), hPrinter, nil) then begin
raise exception.create('Printer not found');
end;
If StartDocPrinter(hPrinter, 1, #DocInfo) = 0 then
Abort;
try
If not StartPagePrinter(hPrinter) then
Abort;
try
If not WritePrinter(hPrinter, #buffer, Length(buffer), BytesWritten) then begin
dError := GetLastError;
ShowMessage(InttoStr(dError));
Abort;
end;
finally
EndPagePrinter(hPrinter);
end;
finally
EndDocPrinter(hPrinter);
end;
WinSpool.ClosePrinter(hPrinter);
If anyone has faced any issue similar to this, kindly let me know if I have missed anything.
Note:
I have verified that there is no error in decoding the input print command.
I use Delphi 4.
It looks like buffer is a dynamic array. It would have been very helpful if you had included the declarations of your variables with the rest of the code. However, I guess with reasonable confidence that its type is
buffer: array of Byte;
But you pass #buffer to WritePrinter. That's the address of the pointer to the first byte of the array.
You need to pass simply the pointer to the first byte. Like this:
WritePrinter(..., Pointer(buffer), ...);
As an aside the way you load the file is a little archaic. It would probably be simpler to create a TMemoryStream and call LoadFromFile on it.
stream := TMemoryStream.Create;
try
stream.LoadFromFile(filename);
....
if not WritePrinter(..., stream.Memory, stream.Size, ...) then
....
finally
stream.Free;
end;
I suppose that after the HTTPRio component receives the data, it parses the data. I'm saying this because after the program leaves the AfterExecute procedure it takes ages before continuing to the next line of code.
Want I want is to clear the data that has arrived on the AfterExecute procedure. Is this possible?
procedure TEventHandlers.thhoptAfterExecute(const MethodName: string;
SOAPResponse: TStream);
var
fs : TFileStream;
begin
fs := TFileStream.Create('F:\Lixosam\LixoSMS'+
IntToStr(ThCampanha),
fmCreate, fmShareDenyNone);
SOAPResponse.Position := 0;
fs.CopyFrom(SOAPResponse, SOAPResponse.Size);
fs.Free;
end;
How can I clear the data so the component doesn't have to do any parsing?
You can write whatever you want into the SOAPResponse. You can clear it, edit it, whatever. If a completely empty response causes trouble with deserialization (complaints about "document must have top-level element, at line 0"), you could stick in a "skeleton" response that provides minimal structure. If you edit the response, you should set the size accordingly.
Here is some code that I have:
var
sl : TStringList;
begin
sl := TStringList.Create;
try
SOAPResponse.Position := 0;
sl.LoadFromStream(SOAPResponse); // Load the response into a stringlist so we can work on it.
// some manipulation to the lines in sl occur here, such as stringreplaces and such.
// Now write out edits back out to the stream.
SOAPResponse.Position := 0; // Now overwrite the crappy response with our good one.
SOAPResponse.size := length(sl.Text); // Important - set new length before saving. Otherwise, the old
sl.SaveToStream(SOAPResponse); // leftover crud is still there, at the end, and the XML will blow up on it.
finally
FreeAndNil(sl);
end;
end;
my application opens files does transformations and saves the data out to another file..or possible the same file.. the file size changes but i dont know how big or small its gona be untill i see the data inside the first file..
At the moment i load the file into a dynamic array do all that i need to do in there then save it back... this was looking good untill i got to my testing stage where i found transforming multi gigabyte files on a system with 128mb ram caused some issues...LOL
here is my code..
procedure openfile(fname:string);
var
myfile: file;
filesizevalue:integer;
begin
AssignFile(myfile,fname);
filesizevalue := GetFileSize(fname);
Reset(myFile, 1);
SetLength(dataarray, filesizevalue);
BlockRead(myFile, dataarray[0], filesizevalue);
CloseFile(myfile);
end;
what i need is direct file access to minimise ram usage.. thats what i think i need/
is this what i need can it be done in delphi
I'd look at using a TFileStream, perhaps one with buffering, but you need to show what you are doing with the data really because it is hard to determine the best strategy. As gabr says, one option is memory mapped files, the code of which is in his link but since it is my code, I'll add it here too!
procedure TMyReader.InitialiseMapping(szFilename : string);
var
// nError : DWORD;
bGood : boolean;
begin
bGood := False;
m_hFile := CreateFile(PChar(szFilename), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
if m_hFile <> INVALID_HANDLE_VALUE then
begin
m_hMap := CreateFileMapping(m_hFile, nil, PAGE_READONLY, 0, 0, nil);
if m_hMap <> 0 then
begin
m_pMemory := MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, 0);
if m_pMemory <> nil then
begin
htlArray := Pointer(Integer(m_pMemory) + m_dwDataPosition);
bGood := True;
end
else
begin
// nError := GetLastError;
end;
end;
end;
if not bGood then
raise Exception.Create('Unable to map token file into memory');
end;
You can also map parts of file directly into memory. That's definitely the most direct way. See What is the fastest way to Parse a line in Delphi for an example.
If problem permits, you can use BlockRead and BlockWrite to read chunk of input file, process it and then write that chunk to output file. Something like this:
AssignFile(inFile,inFname);
AssignFile(outFile,outFname);
repeat
BlockRead(inFile, buff, SizeOf(buff), bytesRead);
ProcessBuffer(buff);
BlockWrite(outFile, buff, bytesRead, bytesWritten);
until (bytesRead = 0) or (bytesWritten <> bytesRead);
Code presumes that you won't change size of buffer while processing it. If size of file changes, then you should change last two lines of example code.
I prefer to use tFileStream for this kind of processing. In this example I am assuming there is a constant ArraySize which is set to the size of a single array element. For example, if your "array" is an array of integer then it would be set to:
ArraySize := SizeOf( Integer );
which would set the ArraySize to 4.
Function LoadPos(inFIlename:string;ArrayPos:Int64;var ArrayBuff) : boolean;
var
fs : tFileStream;
begin
result := false;
fs := tFileStream.Create(inFilename,fmOpenRead);
try
// seek to the array position
fs.Seek( ArrayPos * ArraySize, soFromBeginning);
// load the element
result := fs.Read( ArrayBuff, ArraySize ) = ArraySize;
finally
fs.free;
end;
end;
The only problem with this approach is it only works for fixed size structures, variable length strings require a different approach.
I don't think you'll get a "more direct" file access. Do you use all the data in the file? Otherwise you could perhaps use a stream and load only the data needed into memory. But if you use all the data, there's only one solution IMHO: read the file in chunks. But that highly depends on the kind of transformation you want to apply. If the transformation is not local (so that combined data elements are all in the same chunk), you gonna have problems.