Error in printing a base64 encoded print command using Thermal Printer - delphi

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;

Related

How to read Data from a TBlobField using ADO in Delphi?

I was trying to read data from a TBlobField using TADOBlobStream. I wrote the following function
function DecompressBlobFieldCustom(AField:TBlobField):String;
var
BLOBStream:TADOBlobStream;
Size:Integer;
begin
BLOBStream:= TADOBlobStream.Create(AField,bmRead);
Size:= BLOBStream.Size;
BLOBStream.Read(Result,Size);
end;
and i use the function as follows
Data := DecompressBlobFieldCustom(FldImage);
But when i try to do if Length(Data) > 0 then i am getting Access violation at address XXXX error. I couldn't figure out the problem. Please Help.
Result is a string type, but a blob stream operates on byte arrays. It is usually a mistake to try to treat a byte array as though it were a string. Furthermore, you did not allocate a buffer into which to read, which is the actual cause of the error.
Read into a byte array like this:
function ReadBlobField(Field: TBlobField): TBytes;
var
Stream: TStream;
begin
Stream := TADOBlobStream.Create(Field, bmRead);
try
SetLength(Result, Stream.Size);
if Stream.Size>0 then
Stream.ReadBuffer(Result[0], Stream.Size);
finally
Stream.Free;
end;
end;
It is preferable to use the CreateBlobStream method of TDataSet to create blob streams, as discussed in the documentation. So the code would be better like this:
function ReadBlobField(DataSet: TDataSet; Field: TField): TBytes;
var
Stream: TStream;
begin
Stream := DataSet.CreateBlobStream(Field, bmRead);
try
SetLength(Result, Stream.Size);
if Stream.Size>0 then
Stream.ReadBuffer(Result[0], Stream.Size);
finally
Stream.Free;
end;
end;
I've assumed that the data really is a byte array and is not holding text. The fact that it is held in a blob suggests that, as does the mention of decompression and images.
Some other comments:
You must destroy the stream when you are finished with it.
It is generally preferable to use ReadBuffer rather than Read because ReadBuffer raises an exception if the requested number of bytes are not read.

Copy a file to clipboard in Delphi

I am trying to copy a file to the clipboard. All examples in Internet are the same. I am using one from, http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212186.html but it does not work.
I use Rad Studio XE and I pass the complete path. In mode debug, I get some warnings like:
Debug Output:
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
I am not sure is my environment is related: Windows 8.1 64 bits, Rad Studio XE.
When I try to paste the clipboard, nothing happens. Also, seeing the clipboard with a monitor tool, this tool shows me error.
The code is:
procedure TfrmDoc2.CopyFilesToClipboard(FileList: string);
var
DropFiles: PDropFiles;
hGlobal: THandle;
iLen: Integer;
begin
iLen := Length(FileList) + 2;
FileList := FileList + #0#0;
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen);
if (hGlobal = 0) then raise Exception.Create('Could not allocate memory.');
begin
DropFiles := GlobalLock(hGlobal);
DropFiles^.pFiles := SizeOf(TDropFiles);
Move(FileList[1], (PChar(DropFiles) + SizeOf(TDropFiles))^, iLen);
GlobalUnlock(hGlobal);
Clipboard.SetAsHandle(CF_HDROP, hGlobal);
end;
end;
UPDATE:
I am sorry, I feel stupid. I used the code that did not work, the original question that somebody asked, in my project, while I used the Remy's code, the correct solution, here in Stackoverflow. I thought that I used the Remy's code in my project. So, now, using the Remy's code, everything works great. Sorry for the mistake.
The forum post you link to contains the code in your question and asks why it doesn't work. Not surprisingly the code doesn't work for you any more than it did for the asker.
The answer that Remy gives is that there is a mismatch between ANSI and Unicode. The code is for ANSI but the compiler is Unicode.
So click on Remy's reply and do what it says: http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212187.html
Essentially you need to adapt the code to account for characters being 2 bytes wide in Unicode Delphi, but I see no real purpose repeating Remy's code here.
However, I'd say that you can do better than this code. The problem with this code is that it mixes every aspect all into one big function that does it all. What's more, the function is a method of a form in your GUI which is really the wrong place for it. There are aspects of the code that you might be able to re-use, but not factored like that.
I'd start with a function that puts an known block of memory into the clipboard.
procedure ClipboardError;
begin
raise Exception.Create('Could not complete clipboard operation.');
// substitute something more specific that Exception in your code
end;
procedure CheckClipboardHandle(Handle: HGLOBAL);
begin
if Handle=0 then begin
ClipboardError;
end;
end;
procedure CheckClipboardPtr(Ptr: Pointer);
begin
if not Assigned(Ptr) then begin
ClipboardError;
end;
end;
procedure PutInClipboard(ClipboardFormat: UINT; Buffer: Pointer; Count: Integer);
var
Handle: HGLOBAL;
Ptr: Pointer;
begin
Clipboard.Open;
Try
Handle := GlobalAlloc(GMEM_MOVEABLE, Count);
Try
CheckClipboardHandle(Handle);
Ptr := GlobalLock(Handle);
CheckClipboardPtr(Ptr);
Move(Buffer^, Ptr^, Count);
GlobalUnlock(Handle);
Clipboard.SetAsHandle(ClipboardFormat, Handle);
Except
GlobalFree(Handle);
raise;
End;
Finally
Clipboard.Close;
End;
end;
We're also going to need to be able to make double-null terminated lists of strings. Like this:
function DoubleNullTerminatedString(const Values: array of string): string;
var
Value: string;
begin
Result := '';
for Value in Values do
Result := Result + Value + #0;
Result := Result + #0;
end;
Perhaps you might add an overload that accepted a TStrings instance.
Now that we have all this we can concentrate on making the structure needed for the CF_HDROP format.
procedure CopyFileNamesToClipboard(const FileNames: array of string);
var
Size: Integer;
FileList: string;
DropFiles: PDropFiles;
begin
FileList := DoubleNullTerminatedString(FileNames);
Size := SizeOf(TDropFiles) + ByteLength(FileList);
DropFiles := AllocMem(Size);
try
DropFiles.pFiles := SizeOf(TDropFiles);
DropFiles.fWide := True;
Move(Pointer(FileList)^, (PByte(DropFiles) + SizeOf(TDropFiles))^,
ByteLength(FileList));
PutInClipboard(CF_HDROP, DropFiles, Size);
finally
FreeMem(DropFiles);
end;
end;
Since you use Delphi XE, strings are Unicode, but you are not taking the size of character into count when you allocate and move memory.
Change the line allocating memory to
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen * SizeOf(Char));
and the line copying memory, to
Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^, iLen * SizeOf(Char));
Note the inclusion of *SizeOf(Char) in both lines and change of PChar to PByte on second line.
Then, also set the fWide member of DropFiles to True
DropFiles^.fWide := True;
All of these changes are already in the code from Remy, referred to by David.

stuck with streaming file to string

okay, so I (VERY) recently started playing with lazaruz/free pascal, and I'm a little stuck with reading files with TMemoryStream and it's streaming kin.
I'm trying to write a simple base64 encoder, that can encode strings of text, or files (like images and WAVs) to then be used in html and javascript.
The following code compiles great but I get EReadError Illegal stream image when trying to load a file. I'll include the working string only procedure for reference:
procedure TForm1.TextStringChange(Sender: TObject);
begin
Memo1.Lines.Text := EncodeStringBase64(TextString.Text);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.Text := '';
Form1.BorderIcons := [biSystemMenu,biMinimize];
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
filename := OpenDialog1.Filename;
stream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
stream.LoadFromFile(filename);
stream.Seek(0, soFromBeginning);
ObjectBinaryToText(stream, StrStream);
StrStream.Seek(0, soFromBeginning);
Memo1.Lines.Text := EncodeStringBase64(StrStream.DataString);
finally
StrStream.Free;
end;
finally
stream.Free;
end;
end;
end;
Can anyone help me out?
You get the "illegal stream image" exception because the file you're loading probably isn't a binary DFM file. That's what ObjectBinaryToText is meant to process. It's not for arbitrary data. So get rid of that command.
You can skip the TMemoryStream, too. TStringStream already has a LoadFromFile method, so you can call it directly instead of involving another buffer.
StrStream.LoadFromFile(filename);
But a string isn't really the right data structure to store your file in prior to base64-encoding it. The input to base64 encoding is binary data; the output is text. Using a text data structure as an intermediate format means you may introduce errors into your data because of difficulties in encoding certain data as valid characters. The right interface for your encoding function is this:
function Base64Encode(Data: TStream): string;
You don't need to load the entire file into memory prior to encoding it. Just open the file with a TFileStream and pass it to your encoding function. Read a few bytes from it at a time with the stream's Read method, encode them as base64, and append them to the result string. (If you find that you need them, you can use an intermediate TStringBuilder for collecting the result, and you can add different buffering around the file reads. Don't worry about those right away, though; get your program working correctly first.)
Use it something like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
filename: string;
stream: TStream;
begin
if OpenDialog1.Execute then begin
filename := OpenDialog1.Filename;
stream := TFileStream.Create(filename, fmOpenRead);
try
Memo1.Lines.Text := Base64Encode(stream);
finally
stream.Free;
end;
end;
end;
I never heard before about ObjectBinaryToText(), but looks like funky one. Also, what is EncodeStringBase64() function?
At first place, you shouldn't convert binary stream to text to encode it, instead you should directly B64 encode binary data. B64 algorithm is intended to work on array of bytes.
Since Delphi 6, there is EncdDecd.pas unit, which implements B64 encoding methods. I'm not sure if Lazarus/FPC have this, but if they do, your code to B64 encode file should look like this (add EncdDecd to uses list):
procedure TForm1.Button1Click(Sender: TObject);
var
instream : TFileStream;
outstream: TStringStream;
begin
if OpenDialog1.Execute then
begin
instream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead or fmShareDenyNone);
try
outstream := TStringStream.Create;
try
EncodeStream(instream, outstream);
Memo1.Lines.Text := outstream.DataString;
finally
outstream.Free;
end;
finally
instream.Free;
end;
end;
end;

Send array of string using tcp

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 :)

Direct file access in delphi

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.

Resources