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');
Related
Delphi RIO. I have built an Excel PlugIn with Delphi (also using AddIn Express). I iterate through a column to read cell values. After I read the cell value, I do a TRIM function. The TRIM is not deleting the last space. Code Snippet...
acctName := Trim(UpperCase(Acctname));
Before the code, AcctName is 'ABC Holdings '. It is the same AFTER the TRIM function. It appears that Excel has added some type of other char there. (new line?? Carriage return??) What is the best way to get rid of this? Is there a way I can ask the debugger to show me the HEX value for this variable. I have tried the INSPECT and EVALUATE windows. They both just show text. Note that I have to be careful of just deleting NonText characters, and some companies names have dashes, commas, apostrophes, etc.
**Additional Info - Based on Andreas suggestion, I added the following...
ShowMessage(IntToHex(Ord(Acctname[Acctname.Length])));
This comes back with '00A0'. So I am thinking I can just do a simple StringReplace... so I add this BEFORE Andreas code...
acctName := StringReplace(acctName, #13, '', [rfReplaceAll]);
acctName := StringReplace(acctName, #10, '', [rfReplaceAll]);
Yet, it appears that nothing has changed. The ShowMessage still shows '00A0' as the last character. Why isn't the StringReplace removing this?
If you want to know the true identity of the last character of your string, you can display its Unicode codepoint:
ShowMessage(IntToHex(Ord(Acctname[Acctname.Length]))).
Or, you can use a utility to investigate the Unicode character on the clipboard, like my own.
Yes, the character in question is U+00A0: NO-BREAK SPACE.
This is like a usual space, but it tells the rendering application not to put a line break at this space. For instance, in Swedish, at least, you want non-breaking spaces in 5 000 kWh.
By default, Trim and TStringHelper.Trim do not remove this kind of whitespace. (They also leave U+2007: FIGURE SPACE and a few other kinds of whitespace.)
The string helper method has an overload which lets you specify the characters to trim. You can use this to include U+00A0:
S.Trim([#$20, #$A0, #$9, #$D, #$A]) // space, nbsp, tab, CR, LF
// (many whitespace characters missing!)
But perhaps an even better solution is to rely on the Unicode characterisation and do
function RealTrimRight(const S: string): string;
var
i: Integer;
begin
i := S.Length;
while (i > 0) and S[i].IsWhiteSpace do
Dec(i);
Result := Copy(S, 1, i);
end;
Of course, you can implement similar RealTrimLeft and RealTrim functions.
And of course there are many ways to see the actual string bytes in the debugger. In addition to writing things like Ord(S[S.Length]) in the Evaluate/Modify window (Ctrl+F7), my personal favourite method is to use the Memory window (Ctrl+Alt+E). When this has focus, you can press Ctrl+G and type S[1] to see the actual bytes:
Here you see the string test string. Since strings are Unicode (UTF-16) since Delphi 2009, each character occupies two bytes. For simple ASCII characters, this means that every second byte is null. The ASCII values for our string are 74 65 73 74 20 73 74 72 69 6E 67. You can also see, on the line above (02A0855C) that our string object has reference count 1 and length B (=11).
As a demo, to show the unicode string:
program q63847533;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
array100 = array[0..99] of Byte;
parray100 = ^array100;
var
searchResult : TSearchRec;
Name : string;
display : parray100 absolute Name;
dummy : string;
begin
if findfirst('z*.mp3', faAnyFile, searchResult) = 0 then
begin
repeat
writeln('File name = '+searchResult.Name);
name := searchResult.Name;
writeln('File size = '+IntToStr(searchResult.Size));
until FindNext(searchResult) <> 0;
// Must free up resources used by these successful finds
FindClose(searchResult);
end;
readln(dummy);
end.
My directory contains two z*.mp3 files, one with an ANSI name and the other with a Unicode name.
WATCHing display^ as Hex or Memorydump will display what you seem to require (the Is there a way I can ask the debugger to show me the HEX value for this variable. of your question)
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;
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.
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.
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