Sending printer specific commands - delphi

I have an issue here, which I am trying to encode magnetic stripe data to an Fargo DTC400 printer, in the specifications it says I need to send the following string commands from example notepad, wordpad etc etc :
~1%TRACK NUMBER ONE?
~2;123456789?
~3;123456789?
this example encodes the string in track one, and the numbers 123456789 in both track 2 and 3.. this works from Notepad.exe.
EDIT:
Current delphi code I use works on another printer:
procedure SendQuote(MyCommand : AnsiString);
var
PTBlock : TPassThrough;
begin
PTBlock.nLen := Length(MyCommand);
StrPCopy(#PTBlock.SData, MyCommand);
Escape(printer.handle, PASSTHROUGH, 0, #PTBlock, nil);
end;
when I am trying to encode this string from my own application I get trouble, it seems the printer is totally ignoring my commands, when I choose print to file, I can read the binary data and see my string in the printed file, when I try to print to file from example notepad.exe I get just rubish binary data and cannot find my strings at all...
so I wonder what does notepad do to send this string command which I dont ?
hope someone can shed light on this because I have been eager to implement fargo support in my application for a longer period of time .
thanks
Update.
the following code is ancient but it does the job, however is there another way I can use this with the Passthrough code above?
var
POutput: TextFile;
k: Integer;
begin
with TPrintDialog.Create(self) do
try
if Execute then
begin
AssignPrn(POutput);
Rewrite(POutput);
Writeln(POutput,'~1%TESTENCODER?');
Writeln(POutput,'~2;123456789?');
Writeln(POutput,'~2;987654321?');
CloseFile(POutput);
end;
finally
free;
end
end;

TPassThrough should be declared like this :
type
TPassThrough = packed record
nLen : SmallInt;
SData : Array[0..255] of AnsiChar;
end;
You might be using a modern Delphi (2009 or newer) or forgotten the packed directive.
See also this SO question for a correct-way-to-send-commands-directly-to-printer.
At Torry's there is an example snippet (written by Fatih Ölçer):
Remark : Modified for use with Unicode Delphi versions as well.
{
By using the Windows API Escape() function,
your application can pass data directly to the printer.
If the printer driver supports the PASSTHROUGH printer escape,
you can use the Escape() function and the PASSTHROUGH printer escape
to send native printer language codes to the printer driver.
If the printer driver does not support the PASSTHROUGH printer escape,
you must use the DeviceCapabilities() and ExtDevMode() functions instead.
Mit der Windows API Funktion Escape() kann man Daten direkt zum Drucker schicken.
Wenn der Drucker Treiber dies nicht unterstützt, müssen die DeviceCapabilities()
und ExtDevMode() Funktionen verwendet werden.
}
// DOS like printing using Passthrough command
// you should use "printer.begindoc" and "printer.enddoc"
type
TPrnBuffRec = packed record
bufflength: Word;
Buff_1: array[0..255] of AnsiChar;
end;
function DirectToPrinter(S: AnsiString; NextLine: Boolean): Boolean;
var
Buff: TPrnBuffRec;
TestInt: Integer;
begin
TestInt := PassThrough;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT), #testint, nil) > 0 then
begin
if NextLine then S := S + #13 + #10;
StrPCopy(Buff.Buff_1, S);
Buff.bufflength := StrLen(Buff.Buff_1);
Escape(Printer.Canvas.Handle, Passthrough, 0, #buff, nil);
Result := True;
end
else
Result := False;
end;
// this code works if the printer supports escape commands
// you can get special esc codes from printer's manual
// example:
printer.BeginDoc;
try
DirectToPrinter('This text ');
finally
printer.EndDoc;
end;

Related

TStringlist not loading Google Contacts file

I'm trying to use a Stringlist to load a CSV file generated by Google Contacts. When i open this file in an text editor like Sublime Text, i can see the contents properly, with 75 lines. This is a sample from the Google Contacts file :
Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Group Membership,Phone 1 - Type,Phone 1 - Value,Phone 2 - Type,Phone 2 - Value,Phone 3 - Type,Phone 3 - Value
H,H,,,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,,,,,,
H - ?,H,-,?,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-863-64393,,,,
H - ?,H,-,?,,,,,,,,,,,,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-986-364393,,,,
BUT when i try to load this same file using Stringlist, this is what i see in the Stringlist.text property :
'ÿþN'#$D#$A
Here is my code :
procedure Tform1.loadfile;
var sl : tstringlist;
begin
sl := tstringlist.create;
sl.loadfromfile('c:\google.csv');
showmessage('lines : '+inttostr(sl.count)+' / text : '+ sl.text);
end;
This is the result i get :
'1 / 'ÿþN'#$D#$A'
What is happening here ?
Thanks
According to the hex dump you provided, the BOM indicates that your file is encoded using UTF-16LE. You a few options in front of you, as I see it:
Switch to Unicode and use the TnT Unicode controls to work with this file.
Read the file as an array of bytes. Convert to ANSI and then continue using ANSI encoded text. Obviously you'll lose information for any characters than cannot be encoded by your ANSI code page. A cheap way to do this would be to read the file as a byte array. Copy the content after the first two bytes, the BOM, into a WideString. Then assign that WideString to an ANSI string.
Port your program to a Unicode version of Delphi (anything later than Delphi 2007) and work natively with Unicode.
I rather suspect that you are not very familiar with text encodings. If you were then I think you would have been able to answer the question yourself. That's just fine but I urge you to take the time to learn about this issue properly. If you rush into coding now, before having a sound grounding, you are sure to make a mess of it. And we've seen so many people make that same mistake. Please don't add to the list of text encoding casualties.
Thanks to the information of David, i could achieve the task by using the function below ; because Delphi 2007 does not have unicode support, it needs third-party function to do it.
procedure loadUnicodeFile( const filename: String; strings: TStringList);
Procedure SwapWideChars( p: PWideChar );
Begin
While p^ <> #0000 Do Begin
// p^ := Swap( p^ ); //<<< D3
p^ := WideChar( Swap( Word(p^)));
Inc( p );
End; { While }
End; { SwapWideChars }
Var
ms: TMemoryStream;
wc: WideChar;
pWc: PWideChar;
Begin
ms:= TMemoryStream.Create;
try
ms.LoadFromFile( filename );
ms.Seek( 0, soFromend );
wc := #0000;
ms.Write( wc, sizeof(wc));
pWC := ms.Memory;
If pWc^ = #$FEFF Then // normal byte order mark
Inc(pWc)
Else If pWc^ = #$FFFE Then Begin // byte order is big-endian
SwapWideChars( pWc );
Inc( pWc );
End { If }
Else; // no byte order mark
strings.Text := WideChartoString( pWc );
finally
ms.free;
end;
End;

How to send control commands to POS printer from Delphi

I use this code to print text file to POS printer (EPSON):
AssignFile(prnfile, 'file.txt');
Reset(prnfile, 1);
AssignFile(port, 'COM3');
Rewrite(port, 1);
repeat
BlockRead(prnfile, buffer, SizeOf(buffer), Read);
BlockWrite(port, buffer, Read);
until EOF(prnfile) or (Read <> SizeOf(buffer));
CloseFile(prnfile);
CloseFile(port);
Text is printed, but I need to cut a receipt.
I have EPSON command codes, but I don't know how to send them to printer.
Can anybody write an example?
Thank You.
I've tried a lot and finally I've written this code that works:
procedure Cut();
var epsonprn : System.Text;
begin
try
AssignFile(epsonprn,'COM3');// the name of printer port, can be a network share
Rewrite(epsonprn);
Write(epsonprn,#29#86#66#0);//cut sequence
finally
CloseFile(epsonprn);
end;
end;
so the solution is:
procedure TForm1.Button1Click(Sender: TObject);
var prnfile,port:System.Text;
var buffer:String;
begin
try
AssignFile(prnfile, 'c:\file.txt');
Reset(prnfile);
AssignFile(port, 'COM3');
Rewrite(port);
while not eof(prnfile) do
begin
Readln(prnfile, buffer);
Writeln(port, buffer);
end;
finally
CloseFile(port);
CloseFile(prnfile);
end;
cut();
end;
Anyway, my suggestion is to use a tComPort component instead of a direct use of Writeln. Using a tComPort you can handle the return value from the printer in case of errors like "End Paper", "Printer OffLine" and so on.
You have to send an ESC/POS sequence like this
Definition of cut command:
//ASCII GS V m
//Hex 1D 42 m
//Decimal 29 66 m
var cut:String;
begin
cut:=Chr(29)+'V'+Chr(66)+Chr(0);
// send this sequence direct to com after the text file
end;
the complete esc/pos code here

How to pass multilined TStrings data from a TIdTCPServer to TIdTCPClient

I tried to pass a database record from my server-side application to my client-side application. On the client-side I need to store my data into a TStrings collection.
When I pass a multiline field, I receive two separate data items at the client-side, instead of one multiline data item! I've also tried to do that with Unicode UTF8 based commands, but unfortunately the result is same.
Server-side code:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
var
myData: TStrings;
begin
myData := TStringList.Create;
myData.Add('12'); // ID
myData.Add('This is a multi line' + #13#10 + 'description.'); // Descriptions
myData.Add('Thom Smith'); // Name
try
ASender.Context.Connection.Socket.Write(myData, True{, TIdTextEncoding.UTF8});
finally
myData.Free;
end;
end;
myData debug-time values on server-side are:
myData[0] = '12'
myData[1] = 'This is a multi line'#$D#$A'description.'
myData[2] = 'Thom Smith'
Client-side code:
procedure TForm1.Button1Click(Sender: TObject);
var
myData: TStrings;
begin
with TIdTCPClient.Create(nil) do
begin
Port := 1717;
Host := 'localhost';
try
Connect;
//IOHandler.DefStringEncoding := TIdTextEncoding.UTF8;
myData := TStringList.Create;
try
SendCmd('greating');
Socket.ReadStrings(myData, -1{, TIdTextEncoding.UTF8});
eID.Text := myData[0]; // ID TEdit
mDes.Text := myData[1]; // Descriptions TMemo
eTelNo.Text := myData[2]; // Name TEdit
finally
myData.Free;
end;
finally
Disconnect;
Free;
end;
end;
end;
myData debug-time valuese on client-side:
myData[0] = '12'
myData1 = 'This is a multi line'
myData[2] = 'description.'
Telnet result:
Actually, myData[2] that should keep 'Thom Smith' was replaced with the second line of the Description field! and there are no items after myData[2]. myData[3] is not accessible any more.
I think this issue is related to Indy's Write or ReadStrings procedures, because it sends ItemCount as 3, but it sends two items (one correct, and next beaked to two items!).
How can I pass a Carriage Return character to the other side without having the Write procedure break myData[1] into two separate lines?
Thanks a lot.
If you want TStrings.Text be oblivious to special characters - you should escape them before sending by net, and un-escape after that. There are a lot of ways of escaping, so choose one that suits you.
function EscapeString:(String): String --- your choice
function DeEscapeString:(String): String --- your choice
procedure SendEscapedStrings(const socket: TIdSocket; const data: TStrings);
var s: string; temp: TStringList;
begin
temp := TStringList.Create;
try
temp.Capacity := data.Count;
for s in data do
temp.Add( EscapeString( s ) );
socket.Write(temp);
finally
temp.Destroy;
end;
end;
procedure ReadDeescapedStrings(const socket: TIdSocket; const data: TStrings);
var s: string; temp: TStringList;
begin
temp := TStringList.Create;
try
Socket.ReadStrings(temp, -1);
data.Clear;
data.Capacity := temp.Count;
for s in temp do
temp.Add( DeEscapeString( s ) );
finally
temp.Destroy;
end;
end;
Now the question is what would you choose for DeEscapeString and EscapeString ? The options are many.
You can choose convert string to base64 before sending and from base64 after reading
You can choose UUEEncode for escapgin and UUEDecode for de-escaping
You can choose yEnc: http://en.wikipedia.org/wiki/YEnc
Or you can choose very simplistic functions StrStringToEscaped and StrEscapedToString from JclString unit of from Jedi CodeLib ( http://jcl.sf.net ):
what kind of escaping
If you ask for suggestion i would suggest not using raw TCP Server. There is well-known and standard HTTP protocol, there are many libraries for Delphi implementing both HTTP server and HTTP client. And in the protocol (and libraries) there are already decided things like ciphering, compressing, languages support, etc. And if somethign goes wrong - you can take any HTTP sniffer and see who is in the wrong- clent or server - with your own eyes. Debugging is much simpler.
If you are just starting, i suggest you looking into HTTP+JSON Synopse mORMot library, maybe it would cover your needs. You can take sample server code from http://robertocschneiders.wordpress.com/2012/11/22/datasnap-analysis-based-on-speed-stability-tests/ for example, or from demos in the lib.
Then, if to arrange around raw TCP server, i'd send compressed data, so it would work better (networks are slower than CPU usually). See http://docwiki.embarcadero.com/CodeExamples/XE5/en/ZLibCompressDecompress_(Delphi).
Sending:
1: Send into network (int32) - TStringList.Count
2: for every string doing
2.1 creating TStringStream from the string[i]
2.2 passing it via TZCompressionStream
2.3 sending (int32) size of compressed data
2.4 sending the data itself
2.5 freeing the temporary streams
Receiving
1: Receive from net (int32) - count of packets
1.1 ResultStringList.Clear; ResultStringList.Capacity := read_count.
2: for every string doing
2.1 creating TBytesStream
2.2 read from net (int32) size of compressed data
2.3 read N bytes from the network into BytesStream
2.4 unpack it via TZDecompressionStream into TStringStream
2.5 ResultStringList.Add( StringStream -> string );
2.6 freeing the temporary streams
Now, if you really don't want ot change almost anything, then JCL escaping would hopefully be enough for you. At least it worked for me, but my task was very different and was not about networks at all. But you can just test them all and see how it works for you.
Don't use the TStrings overload as it seems to use line breaks as separator between strings which does not work if your strings contain line breaks themselves.
You can easily write your own wrapper method to send a list of strings over the wire (take that as pseudocode):
procedure WriteStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
Str : String;
begin
IOHandler.WriteBufferOpen;
try
IOHandler.Write(Strings.Count);
for Str in Strings do
IOHandler.Write(Str);
finally
IOHandler.WriteBufferClose;
end;
end;
procedure ReadStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
Count, I : Integer;
begin
Count := IOHandler.ReadInteger;
for I := 1 to Count do
Strings.Add(IOHandler.ReadString);
end;

Issue in Converting my Project from D7 to XE4

I've now decided to convert my Delphi 7 project to XE4 , But in One of my Code lines i get an issue of witch i tried to Fix it but with no Hope , So i wish someone can help me in Fixing It .
Here's the issue :
In a Shared Unit used between a Server and Client App ( hotel rooms management system ) i have this Record type:
Type
THotelClientDetails = packed record
LSize: Integer;
ClientName: array[0..25] of char;
ClientRoomN: Integer;
RWithInternet: Boolean;
RoomStatus :Integer;
//... etc
end;
PHotelClientDetails = ^THotelClientDetails;
In the Client App i use the follwing Procedure :
procedure TCForm.SendClientDetailsClick(Sender: TObject);
var
pClientDetails: PHotelClientDetails;
iSize: Integer;
begin
iSize:= SizeOf(THotelClientDetails)+Length(ClientNameEd.Text)+1;
GetMem(pClientDetails,iSize);
ZeroMemory(pClientDetails,iSize);
pClientDetails.LSize := iSize;
StrCopy(pClientDetails.ClientName,PChar(ClientNameEd.Text));
pClientDetails.ClientRoomN :=StrToInt(ClientNEd.text);
pClientDetails.RWithInternet:=ClientWInternet.Checked;
pClientDetails.RoomStatus :=ClientRoomStatus.ItemIndex;
StrCopy(Pointer(Cardinal(pClientDetails)+SizeOf(THotelClientDetails)),
PChar(ClientNameEd.Text));
SendClientsBuffer(pClientDetails,iSize);// to the Server for Check
FreeMem(pClientDetails);
end;
And in the Server App i use the Following Procedure :
Procedure TSForm.GetClientDetails(pClientDetails:PHotelClientDetails; Cntx: Pointer);
var
ClientName: string;
begin
ClientName:=PChar(Cardinal(pClientDetails)+SizeOf(THotelClientDetails));
//*** just a test to get the ClientName
ShowMessage(ClientName);
//***
end;
So my problem is when using Delphi 7 i get the full name sent by the Client App :
for example if i want to send the Client " simon " or "matthew" to the Server
i get the correct name in :
ShowMessage(ClientName);//simon or matthew
But when using the same Procedures in XE4 i always get
sim for simon
and
matt for matthew
that means the Server is not receiving the full Client's Name as with Delphi7 project .
Although is added the unit " System.AnsiStrings; " in Both Projects .
So please how can i fix this Issue ?
and So many thanks .
Simon
Delphi 7 uses ANSI strings (single-byte Chars), while versions of Delphi from 2009 and up use Unicode strings (multi-byte Chars).
The most straightforward fix for your code is to change from Char to AnsiChar, string to AnsiString, and PChar to PAnsiChar:
Type
THotelClientDetails = packed record
LSize: Integer;
ClientName: array[0..25] of AnsiChar;
ClientRoomN: Integer;
RWithInternet: Boolean;
RoomStatus :Integer;
//... etc
end;
StrCopy(pClientDetails.ClientName, PAnsiChar(ClientNameEd.Text));
// and
StrCopy(Pointer(Cardinal(pClientDetails) + SizeOf(THotelClientDetails)),
PAnsiChar(ClientNameEd.Text));
Procedure TSForm.GetClientDetails(pClientDetails:PHotelClientDetails; Cntx: Pointer);
var
ClientName: string;
begin
ClientName := PAnsiChar(Cardinal(pClientDetails)+SizeOf(THotelClientDetails));
//*** just a test to get the ClientName
ShowMessage(ClientName);
//***
end;
There are literally dozens (if not hundreds) of questions relating to porting Delphi code from D2007 and earlier to D2009 and later here. You should spend some time browsing the delphi-2009, delphi-xe, and delphi-xe2 tags here.
I post this as an answer because it easier to format than comments to the question.
Some remarks on things you might want do:
With the Unicode support in Delphi 2009 and up, do not assume that Length will get you the actual number of bytes of a string.
In between Delphi 7 an XE4 the concept op methods on records have been introduced (I think in Delphi 2006). Move parts of the logic SendClientDetailsClick into methods of THotelClientDetails
Since THotelClientDetails.ClientName is limited to 26 bytes (25 AnsiChar character bytes plus a null terminating byte), there is no need for
iSize:= SizeOf(THotelClientDetails)+Length(ClientNameEd.Text)+1;
Which means that iSize:= SizeOf(THotelClientDetails).
The only reason you have a pointer pClientDetails: PHotelClientDetails is that you call
SendClientsBuffer(pClientDetails,iSize);// to the Server for Check
which you can replace by
SendClientsBuffer(ClientDetails,iSize);// to the Server for Check
There is no length guard to prevent copying of more than 25 bytes in
StrCopy(pClientDetails.ClientName,PChar(ClientNameEd.Text));
Use StrLCopy there, not StrCopy.
Why are you performing a copy action from ClientNameEd twice?
StrCopy(pClientDetails.ClientName,PChar(ClientNameEd.Text));
//...
StrCopy(Pointer(Cardinal(pClientDetails)+SizeOf(THotelClientDetails)),
PChar(ClientNameEd.Text));
If you insist on pointers, the FreeMem should be in a finally block.
Something like this is more appropriate:
procedure TCForm.SendClientDetailsClick(Sender: TObject);
var
ClientDetails: PHotelClientDetails;
iSize: Integer;
begin
iSize := SizeOf(THotelClientDetails);
ZeroMemory(#ClientDetails, iSize);
ClientDetails.LSize := iSize;
StrLCopy(ClientDetails.ClientName, PAnsiChar(ClientNameEd.Text), SizeOf(ClientDetails.ClientName)-1);
pClientDetails.ClientRoomN := StrToInt(ClientNEd.text);
pClientDetails.RWithInternet := ClientWInternet.Checked;
pClientDetails.RoomStatus := ClientRoomStatus.ItemIndex;
SendClientsBuffer(#ClientDetails,iSize); // to the Server for Check
end;

Get the name of the program executable (as in paramstr(0)) but get it as a Unicode string in Delphi 7?

I want to copy a file from the selflocation to another location like that:
var
NewFile : WideString;
MyOwnLocation : WideString;
begin
NewFile := 'C:\mycopy.exe';
// CopyFileW (PWideChar(paramstr(0)), PWideChar(NewFile), false); // ===> doesn't work
MyOwnLocation := paramstr(0);
CopyFileW (PWideChar(MyOwnLocation), PWideChar(NewFile), false); // ===> works but not sure if Unicode supported...
end;
it works when I copy paramstr(0) to a WideString, but I'm still not sure if paramstr(0) is already UNICODE. Is there maybe a WindowsAPI that returns the current location of my file in a wideString?
Thanks for help :)
Sure. You can use GetModuleFileNameW, the unicode version of the api that ParamStr(0) internally use:
var
NewFile: WideString;
MyOwnLocation: WideString;
Len: DWORD;
begin
NewFile := 'C:\mycopy.exe';
SetLength(MyOwnLocation, 260);
Len := GetModuleFileNameW(0, PWideChar(MyOwnLocation), Length(MyOwnLocation));
Win32Check(Bool(Len));
if GetLastError <> ERROR_INSUFFICIENT_BUFFER then begin
SetLength(MyOwnLocation, Len);
CopyFileW (PWideChar(MyOwnLocation), PWideChar(NewFile), false);
end else
// handle fail due to insufficient buffer
Calling CopyFileW with ParamStr(0) directly doesn't work because ParamStr(0) returns AnsiString in Delphi 7 (the default type for string), and so it doesn't match the first parameter type expected (PWideChar).
The only way to do it is the way you are - assign the content of ParamStr(0) to a WideString variable first, and then use that as a parameter to CopyFileW.
Windows does most conversions between Unicode and ANSI transparently without any effort on your part when using a non-Unicode API (CopyFileA, which Delphi 7's Windows unit maps CopyFile to, so you should just use it instead.

Resources