How can I send Unicode characters (16 bit) with Serial port in Delphi 2010? - delphi

I have a problem in Delphi 2010. I would like to send from my PC some Unicode (16 bits) characters to the printer with serial port (COM port).
I use the TCiaComPort component in D2010.
For example:
CiaComPort1.Open := True; \\I open the port
Data := #$0002 + UnicodeString(Ж) + #$0003;
CiaComPort1.SendStr(Parancs); //I send the data to the device
If the printer characterset is ASCII then the characters arrive, but the ciril character is '?' on the Printer screen. But if the printer characterset is Unicode then the characters do not arrive to the printer.
An Unicode character represented in 2 bytes. How can I decompose an Unicode character to byte for byte? For example #$0002?
And how can I send this strings byte for byte with the comport? Which function?

Under Windows (check your OS how to open and write to comm ports), I use the following function to write a UnicodeString to a COMM Port:
Bear in mind that the port have to be setup correctly, baud rate, number of bits, etc. See Device Manager => Comm Ports
function WriteToCommPort(const sPort:String; const sOutput:UnicodeString):Boolean;
var
RetW:DWORD;
buff: PByte;
lenBuff:Integer;
FH:THandle;
begin
Result:=False;
lenBuff:=Length(sOutput)*2;
if lenBuff = 0 then Exit; // Nothing to write, empty string
FH:= Windows.CreateFile(PChar(sPort), GENERIC_READ or GENERIC_WRITE, 0, Nil, OPEN_EXISTING, 0, 0);
if (FH <> Windows.INVALID_HANDLE_VALUE) then
try
Buff:=PByte(#sOutput[1]);
Windows.WriteFile(FH, buff^, lenBuff, RetW, Nil);
Result:= Integer(RetW) = lenBuff;
finally
Windows.CloseHandle(FH);
end;
end;

Does CiaComPort1.SendStr() accept an AnsiString or UnicodeString as input? Did you try using a COM port sniffer to make sure that CiaComPort is transmitting the actual Unicode bytes as you are expecting?
The fact that you are using #$0002 and #$0003 makes me think it is actually not, because those characters are usually transmitted on COM ports as 8-bit values, not as 16-bit values. If that is the case, then that would explain why the Ж character is getting converted to ?, if CiaComPort is performing a Unicode->Ansi data conversion before transmitting. In which case, you may have to do something like this instead:
var
B: TBytes;
I: Integer;
B := WideBytesOf('Ж');
SetLength(Data, Length(B)+2);
Data[1] := #$0002;
for I := Low(B) to High(B) do
Data[2+I] := WideChar(B[I]);
Data[2+Length(B)] #$0003;
CiaComPort1.SendStr(Data);
However, if CiaComPort is actually performing a data conversion internally, then you will still run into conversion issues for any encoded bytes that are above $7F.
In which case, look to see if CiaComPort has any other sending methods available that allow you to send raw bytes instead of strings. If it does not, then you are pretty much SOL and will need to switch to a better COM component, or just use OS APIs to access the COM port directly instead.

Related

With Delphi 6/7, how can I convert an AnsiString in a different CharSet, to hex String UTF-8?

I need to draw a barcode (QR) with Delphi 6/7. The program can run in various windows locales, and the data is from an input box.
On this input box, the user can choose a charset, and input his own language. This works fine. The input data is only ever from the same codepage. Example configurations could be:
Windows is on Western Europe, Codepage 1252 for ANSI text
Input is done in Shift-JIS ANSI charset
I need to get the Shift-JIS across to the barcode. The most robust way is to use hex encoding.
So my question is: how do I go from Shift-JIS to a hex String in UTF-8 encoding, if the codepage is not the same as the Windows locale?
As example: I have the string 能ラ. This needs to be converted to E883BDE383A9 as per UTF-8. I have tried this but the result is different and meaningless:
String2Hex(UTF8Encode(ftext))
Unfortunately I can't just have an inputbox for WideStrings. But if I can find a way to convert the ANSI text to a WideString, the barcode module can work with Unicode Strings as well.
If it's relevant: I am using the TEC-IT TBarcode DLL.
Creating and accessing a Unicode text control
This is easier than you may think and I did so in the past with the brand new Windows 2000 when convenient components like Tnt Delphi Unicode Controls were not available. Having background knowledge on how to create a Windows GUI program without using Delphi's VCL and manually creating everything helps - otherwise this is also an introduction of it.
First add a property to your form, so we can later access the new control easily:
type
TForm1= class(TForm)
...
private
hEdit: THandle; // Our new Unicode control
end;
Now just create it at your favorite event - I chose FormCreate:
// Creating a child control, type "edit"
self.hEdit:= CreateWindowW( PWideChar(WideString('edit')), PWideChar(WideString('myinput')), WS_CHILD or WS_VISIBLE, 10, 10, 200, 25, Handle, 0, HINSTANCE, nil );
if self.hEdit= 0 then begin // Failed. Get error code so we know why it failed.
//GetLastError();
exit;
end;
// Add a sunken 3D edge (well, historically speaking)
if SetWindowLong( self.hEdit, GWL_EXSTYLE, WS_EX_CLIENTEDGE )= 0 then begin
//GetLastError();
exit;
end;
// Applying new extended style: the control's frame has changed
if not SetWindowPos( self.hEdit, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOZORDER or SWP_NOSIZE ) then begin
//GetLastError();
exit;
end;
// The system's default font is no help, let's use this form's font (hopefully Tahoma)
SendMessage( self.hEdit, WM_SETFONT, self.Font.Handle, 1 );
At some point you want to get the edit's content. Again: how is this done without Delphi's VCL but instead directly with the WinAPI? This time I used a button's Click event:
var
sText: WideString;
iLen, iError: Integer;
begin
// How many CHARACTERS to copy?
iLen:= GetWindowTextLengthW( self.hEdit );
if iLen= 0 then iError:= GetLastError() else iError:= 0; // Could be empty, could be an error
if iError<> 0 then begin
exit;
end;
Inc( iLen ); // For a potential trailing #0
SetLength( sText, iLen ); // Reserve space
if GetWindowTextW( self.hEdit, #sText[1], iLen )= 0 then begin // Copy text
//GetLastError();
exit;
end;
// Demonstrate that non-ANSI text was copied out of a non-ANSI control
MessageBoxW( Handle, PWideChar(sText), nil, 0 );
end;
There are detail issues, like not being able to reach this new control via Tab, but we're already basically re-inventing Delphi's VCL, so those are details to take care about at other times.
Converting codepages
The WinAPI deals either in codepages (Strings) or in UTF-16 LE (WideStrings). For historical reasons (UCS-2 and later) UTF-16 LE fits everything, so this is always the implied target to achieve when coming from codepages:
// Converting an ANSI charset (String) to UTF-16 LE (Widestring)
function StringToWideString( s: AnsiString; iSrcCodePage: DWord ): WideString;
var
iLenDest, iLenSrc: Integer;
begin
iLenSrc:= Length( s );
iLenDest:= MultiByteToWideChar( iSrcCodePage, 0, PChar(s), iLenSrc, nil, 0 ); // How much CHARACTERS are needed?
SetLength( result, iLenDest );
if iLenDest> 0 then begin // Otherwise we get the error ERROR_INVALID_PARAMETER
if MultiByteToWideChar( iSrcCodePage, 0, PChar(s), iLenSrc, PWideChar(result), iLenDest )= 0 then begin
//GetLastError();
result:= '';
end;
end;
end;
The source codepage is up to you: maybe
1252 for "Windows-1252" = ANSI Latin 1 Multilingual (Western Europe)
932 for "Shift-JIS X-0208" = IBM-PC Japan MIX (DOS/V) (DBCS) (897 + 301)
28595 for "ISO 8859-5" = Cyrillic
65001 for "UTF-8"
However, if you want to convert from one codepage to another, and both source and target shall not be UTF-16 LE, then you must go forth and back:
Convert from ANSI to WIDE
Convert from WIDE to a different ANSI
// Converting UTF-16 LE (Widestring) to an ANSI charset (String, hopefully you want 65001=UTF-8)
function WideStringToString( s: WideString; iDestCodePage: DWord= CP_UTF8 ): AnsiString;
var
iLenDest, iLenSrc: Integer;
begin
iLenSrc:= Length( s );
iLenDest:= WideCharToMultiByte( iDestCodePage, 0, PWideChar(s), iLenSrc, nil, 0, nil, nil );
SetLength( result, iLenDest );
if iLenDest> 0 then begin // Otherwise we get the error ERROR_INVALID_PARAMETER
if WideCharToMultiByte( iDestCodePage, 0, PWideChar(s), iLenSrc, PChar(result), iLenDest, nil, nil )= 0 then begin
//GetLastError();
result:= '';
end;
end;
end;
As per every Windows installation not every codepage is supported, or different codepages are supported, so conversion attempts may fail. It would be more robust to aim for a Unicode program right away, as that is what every Windows installation definitly supports (unless you still deal with Windows 95, Windows 98 or Windows ME).
Combining everything
Now you got everything you need to put it together:
you can have a Unicode text control to directly get it in UTF-16 LE
you can use an ANSI text control to then convert the input to UTF-16 LE
you can convert from UTF-16 LE (WIDE) to UTF-8 (ANSI)
Size
UTF-8 is mostly the best choice, but size wise UTF-16 may need fewer bytes in total when your target audience is Asian: in UTF-8 both 能 and ラ need 3 bytes each, but in UTF-16 both only need 2 bytes each. As per your QR barcode size is an important factor, I guess.
Likewise don't waste by turning binary data (8 bits per byte) into ASCII text (displaying 4 bits per character, but itself needing 1 byte = 8 bits again). Have a look at Base64 which encodes 6 bits into every byte. A concept that you encountered countless times in your life already, because it's used for email attachments.

Delphi 7 printing in Turkish

I have some legacy code (1 million lines) written in Delphi 7 Pascal which for various reasons can't be upgraded to a more recent version of Delphi. The program outputs documents in about 30 languages and makes a very good job of producing the various characters in all languages apart from Turkish. The coding sets the charset to TURKISH_CHARSET (162). When it tries to print char #351 (ş, hex 15f), char #285 (ğ, hex 11f) or char #305 (ı, hex 131), it prints only "s", "g" or "i". It uses a simple
Printer.Canvas.TextOut(x, y, sText)
to output the text.
I tried compiling the code on different machines and running it on different versions of Windows but always with the same result.
In Delphi 7, string is an alias for AnsiString, which encodes Unicode characters as 8-bit bytes using Windows codepages. In some MBCS codepages, Unicode characters may require multiple bytes (Turkish is not one of them, though).
Microsoft has several codepages for Turkish:
857 (MS-DOS)
1254 (Windows)
10081 (Macintosh)
28599 (ISO-8859-9)
In both codepages 1254 and 28599 (where 1254 is the most likely one you will run into), the Unicode characters in question are encoded in 8-bit as hex $FE (ş), $F0 (ğ), and $FD (ı).
Make sure your sText string variable actually contains those byte values to begin with, and not ASCII bytes $73 (s), $67 (g), and $69 (i) instead. If it contains the latter, you are losing the Turkish data before it even reaches Canvas.TextOut(). That would be an issue earlier in your code.
However, If sText contains the correct bytes, then the problem has to be on the OS side, as TCanvas.TextOut() is just a thin wrapper for the Win32 API ExtTextOutA() function, where sText gets passed as-is to the API. Maybe the particular font you are using doesn't support Turkish, or at least those particular characters. Or maybe there is a problem with the printer driver. Either way, you might have to resort to converting your sText value to a WideString using MultiByteToWideChar() and then call ExtTextOutW() (not ExtTextOutA()) directly, eg:
var
wText: WideString;
size: TSize;
begin
//Printer.Canvas.TextOut(x, y, sText);
SetLength(wText, MultiByteToWideChar(1254{28599}, 0, PAnsiChar(sText), Length(sText), nil, 0));
MultiByteToWideChar(1254{28599}, 0, PAnsiChar(sText), Length(sText), PWideChar(wText), Length(wText)));
Windows.ExtTextOutW(Printer.Canvas.Handle, x, y, Printer.Canvas.TextFlags, nil, PWideChar(wText), Length(wText), nil);
size.cX := 0;
size.cY := 0;
Windows.GetTextExtentPoint32W(Printer.Canvas.Handle, PWideChar(wText), Length(wText), size);
Printer.Canvas.MoveTo(x + size.cX, Y);
end;

Sending text over a named pipe crashes Delphi application

I have a Delphi application, which sends piece of text to a named pipe via call
SendMessageToNamedPipe(hPipe, CurMsg);
It works fine for some messages, but sending other texts leads to a crash of the application.
The only difference between normal and "crashing" messages I'm aware of is that the crashing messages contain lots of Cyrillic characters.
How should I encode them in order for the aforementioned call to be executed properly?
Update 1: Here's the implementation of SendMessageToNamedPipe.
procedure SendMessageToNamedPipe(hPipe:THandle; msg:string);
const
OUT_BUF_SIZE = 100;
var
dwWrite : DWORD;
lpNumberOfBytesWritten : LongBool;
utf8String : RawByteString;
sendBuf: array[0..OUT_BUF_SIZE] of WideChar;
begin
utf8String := UTF8Encode(msg);
sendBuf[0] := #0;
lstrcatw(sendBuf, PChar(msg));
lpNumberOfBytesWritten := WriteFile(hPipe, sendBuf, OUT_BUF_SIZE, dwWrite, NIL);
if not lpNumberOfBytesWritten then
begin
OutputDebugString(PChar('Sending error: ' + SysErrorMessage(GetLastError)));
end
else
begin
OutputDebugString(PChar('Message sent, dwWrite: ' + IntToStr(dwWrite)));
end;
end;
Update 2: Version of the function, which seems to work.
procedure SendMessageToNamedPipe(hPipe:THandle; msg:string);
const
OUT_BUF_SIZE = 200;
var
dwWrite : DWORD;
Success : LongBool;
msgToSend : PChar;
utf8String : RawByteString;
sendBuf: array[0..OUT_BUF_SIZE-1] of WideChar;
AnsiCharString : PAnsiChar;
begin
OutputDebugString(PChar('SendMessageToNamedPipe.Length(msg): ' + IntToStr(Length(msg))));
OutputDebugString(PChar('Sending message: ' + msg));
utf8String := UTF8Encode(msg);
sendBuf[0] := #0;
lstrcatw(sendBuf, PChar(msg));
Success := WriteFile(hPipe, sendBuf, Length(sendbuf), dwWrite, NIL);
if not Success then
begin
OutputDebugString(PChar('Sending error: ' + SysErrorMessage(GetLastError)));
end
else
begin
OutputDebugString(PChar('Message sent, dwWrite: ' + IntToStr(dwWrite)));
end;
end;
Since the Windows pipe functions see the data written to the pipe as a binary stream it is not plausible that the type of data being written could cause a crash.
But SendMessageToNamedPipe is neither a Delphi library function nor a Windows API call. I think you need to look at what SendMessageToNamedPipe is doing, since that is almost certainly where the bug is. You might like to ask questions such as: what is the data type of CurMsg? How does SendMessageToNamedPipe calculate how many bytes to write to the pipe?
Update:
Reading through the implementation of SendMessageToNamedPipe that you've added to your question:
sendBuf is OUT_BUF_SIZE+1 wide characters. You probably meant to define it as array[0..OUT_BUF_SIZE-1]. I see this mistake all the time. (But it is not the cause of the crash.)
utf8String is assigned but never used.
I think the cause of the crash is lstrcatw(sendBuf, PChar(msg)). It would crash if the length of the string passed in is greater than OUT_BUF_SIZE + 1 characters because this would overflow the buffer sendBuf.
The test if not lpNumberOfBytesWritten is wrong. Or more to the point, the return value from WriteFile is a Boolean saying whether or not the write succeeded, not a count of the number of bytes written. WriteFile modifies the value of dwWrite on exit to give the count of the number of bytes written.
Just spotted another one: WriteFile is sending OUT_BUF_SIZE bytes, but OUT_BUF_SIZE is the count of the number of characters in sendBuf, not the number of bytes. A Char in Delphi 2009 is 2 bytes (utf-16). (However good code would always use SizeOf(Char) rather than 2 because it could change in a future Delphi version, and has already changed once in the past.)
As David wrote, you don't actually need to copy msg to a different buffer before writing it to the pipe. The expression PChar(msg) returns a pointer to the start of the a null-terminated array of Chars that comprises the data in msg.
Reflecting on your code, I'd ask whether you are clear in your own mind whether the program at the other end of the pipe expects to receive a utf-16 string or a utf-8 string (or even an ANSI string for that matter). You need to settle this question and then modify SendMessageToNamedPipe accordingly. (Also, if it expects a null-terminated string, rather than a fixed-length buffer, you should send just the intended number of bytes, not OUT_BUF_SIZE bytes.)
Reply to your comment below:
Your code above doesn't write utf-8 to the pipe. It writes utf-16. Although you called UTF8Encode you threw the result away, as I mentioned above.
You can pass utf8String directly to WriteFile as the buffer to send by casting it like this: PRawByteString(utf8String). That expression returns a pointer to the first character in the utf8String just as for PChar(msg) which I explained above.
You need to pass the correct number of bytes to write to WriteFile, instead of OUT_BUF_SIZE. Since it is a utf-8 string, a character can take up anything from 1 to 4 bytes. But as it happens, Delphi's Length function returns the number of bytes when applied to a Utf8String or a RawByteString so you can write Length(utf8String)+1. The +1 is to include the terminating #0 character, which is not included in the Length count. (If you were passing a utf-16 string Length would return the number of characters, so would need to be multiplied by SizeOf(Char)).
If you are still unclear, then you would probably benefit greatly from reading Delphi and Unicode.

Indy 9 read data in hexadecimal format

I need a method for reading data from TCP connection in hex format instead string format, to explain more lets give this example :
When we sent data like "Test" over TCP connection, each caracter is encoded with is ASCII code " 54 65 73 54 ", at the other side I use currentReadbuffer to read data, my need is to get data in ASCII hex format without converting to string values.
you can convert each character-pair from hex to byte by using something like this:
function HexToByte(const value: string): Byte;
begin
Result := StrToInt('$'+Value);
end;
depending on the Delphi version you use and character encoding you use (is it single-byte, multi-byte?) you need to convert these Bytes to characters.
Assuming you use single-byte character sets (ASCII, Windows-1252 or the like), and Delphi < 2009, the conversion is easy:
function ByteToChar(const value: Byte): Char;
begin
Result := Char(Byte);
end;
Edit your question to make it more specific, and you get a more focussed answer.
--jeroen

Reading data with TCP indy 9

in my project I need to developp server receiving frames from GPRS/GPS Box and decode theses frames to extract relevant data as latitude , longitude and more
the first part (TCP connection and receiving data) is done , the problem I had is with decode data, the firmware of GPRS box send data not in string format but in hex format , so the methode I used (currentReaderBuffer) ouput the frame with string format , let me explain with real example :
The data sent from GPRS BOX is : 0d 0a 1f 52
data received using currentReaderBuffer is : #$d#$a#1fR
the problem is how can I know if the caracter #$d correspond to 0d or each of the caracters (#,$,d) correspond to each ascii code
#$d means it's a Char (#) in hex ($) with the value D (13), which means it's a carriage return. If you're always getting 1 byte values (for instance 0D or `1F'), you can be pretty sure they're hex values and convert them.
Converting them is easy. Just use them.
For instance:
ShowMessage('You received hex 52, which is ' + #$52);
You're, in fact, receiving the correct data, but you're interpreting it in a bad way... and mixing some concepts:
Hex is just a representation of data... data in a computer is binary and computers does not understand nor work in hex.
You are choosing to represent a byte of data as a character, but you can treat it as a byte and from that perform a hex representation of that data.
For example:
var
aByte: Byte;
begin
//ReceivedStr is the string variable where you hold the received data right now
aByte := ReceivedStr[1];
//Warning! this will work only in pre-2009 Delphi versions
ShowMessage('Hexadecimal of first byte: ' + IntToHex(aByte);
end;
or
function StringToHex(const S: string): string; //weird!
begin
//Warning! this will work only in pre-2009 delphi versions
Result := '';
for I := 1 to Length(Result) do
Result := Result + IntToHex(Byte(S[I])) + ' ';
end;
function ReceiveData();
begin
//whatever you do to get the data...
ShowMessage('Received data in hex: ' + StringToHex(ReceivedStr));
end;
This said, IMHO is better to treat the data as binary from the start (Integers, bytes or any other suitable type), avoiding using strings. It will make your life easier now and then, when you want to upgrade to modern Delphi versions, where strings are Unicode.
Anyway, you may want to process that data, I don't think your intention is to show it directly to the user.
If you want to check if a particular byte against a hex value, you can use the $ notation:
if aByte = $0d then
ShowMessage('The hex value of the byte is 0d');

Resources