I am using the TComPort library, to send a request to the device I need to send the command in Hexadecimal data like the example below
procedure TForm1.Button3Click(Sender: TObject);
begin
ComPort1.WriteStr(#$D5#$D5);
end;
But that is a hardcoded example.
How can I convert S into a valid value for ComPort1.WriteStr
procedure TForm1.Button3Click(Sender: TObject);
var
S:String;
begin
Edit1.Text:='D5 D5';
ComPort1.WriteStr(Edit1.Text);
end;
You don't send actual hex strings over the port. That is just a way to encode binary data in source code at compile-time. #$D5#$D5 is encoding 2 string characters with numeric values of 213 (depending on {$HIGHCHARUNICODE}, which is OFF by default).
TComPort.WriteStr() expects actual bytes to send, not hex strings. If you want the user to enter hex strings that you then send as binary data, look at Delphi's HexToBin() functions for that conversion.
That being said, note that string in Delphi 2009+ is 16-bit Unicode, not 8-bit ANSI. You should use TComPort.Write() to send binary data instead of TComPort.WriteStr(), eg:
procedure TForm1.Button3Click(Sender: TObject);
var
buf: array[0..1] of Byte;
begin
buf[0] := $D5;
buf[1] := $D5;
ComPort1.Write(buf, 2);
end;
However, TComPort.WriteStr() will accept a 16-bit Unicode string and transmit it as an 8-bit binary string by simply stripping off the upper 8 bits of each Char. So, if you send a string containing two Char($D5) values in it, it will be sent as 2 bytes $D5 $D5.
Related
I have a list in a CDATA element. How do I get the elements from the CDATA and add this to TStringList?
If I get the CDATA string, it returns:
'string'#10#9#9#9'string'#10#9#9#9'string'#10#9#9#9'string'...
However the characters #10#9#9#9 are not strings; I cannot use use the StringChangeEx method to replace these characters.
Thanks
The string you got is a valid notation for a string containing non printable characters. But you don't need to worry that you lose any char when working with a string list or the StringChangeEx function. Let me convince you with this short script:
[Code]
const
PrintableString = 'string-string-string-string';
NonPrintableString = 'string'#10#9#9#9'string'#10#9#9#9'string'#10#9#9#9'string';
procedure InitializeWizard;
var
S: string;
StringList: TstringList;
begin
StringList := TstringList.Create;
try
StringList.Add(NonPrintableString);
S := StringList[0];
if S = NonPrintableString then
MsgBox('String list didn''t lose non printable chars!', mbInformation, MB_OK);
StringChangeEx(S, #10#9#9#9, '-', True);
if S = PrintableString then
MsgBox('String has been modified as expected!', mbInformation, MB_OK);
finally
StringList.Free;
end;
end;
However, I think your question has been raised just because you want to present these data to the user, and that might be sometimes difficult with non printable chars. One example for all. If you'll have a string which contains a null terminator in the middle and you will want to show such string to the user, e.g. in a message box, you'll get displayed only the part of that string which is before that terminator. But, it's not something you can affect. That's just how the Windows API function call which is behind treats the string:
[Code]
procedure InitializeWizard;
var
S: string;
begin
S := 'Hello'#0' world!';
MsgBox(S, mbInformation, MB_OK);
end;
As you can see, for the above case would be necessary to replace the null terminator char with some printable char, e.g. space.
I want to write a function GenString that returns a random string of length [0 .. 100] based on a parameterized function GenChar that users could customize.
That is, if one user wants to generate a string of all as, he could do that by passing in a function that always returns a, and another user could generate random alphabetic Hebrew words by passing in a custom GenChar function that returns random Hebrew letters.
Lemma 1: I want to write a function GenArray that generates an array of whatever type a passed-in function GenThing : sometype returns. Could this be done in Free Pascal.
Lemma 2: What I'm trying to do is port QuickCheck to Free Pascal.
Here's an example. Unicode string is used since international characters is mentioned.
//The functions
type
TGenChar = function: widechar;
function GenString(MaxLength: integer; GenChar: TGenChar): widestring;
var
i: integer;
begin
if MaxLength < 1 then
MaxLength:= 1; //minimum length is 1
setlength(result, random(MaxLength+1));
for i:= 1 to length(result) do
result[i]:= GenChar;
end;
//The usage example (remember to call randomize somewhere in your program)
var
SampleCharSet: widestring= 'ABCxyz';
function SampleGenChar: widechar;
begin
result:= SampleCharSet[random(length(SampleCharSet))+1];
end;
function GetRandomStringSample: widestring;
begin
result:= GenString(100, SampleGenChar);
end;
As the experts have been kindly suggested, TStringStream.DataString cannot be used to retrieve non-text data loaded by TStringStream.LoadFromFile, because TStringStream.GetDataString will call TEncoding's encoding methods, which, take TMBCSEncoding for example, will call TMBCSEncoding.GetChars which in turn calls TMBCSEncoding.UnicodeFromLocaleChars and finally Windows's MultiByteToWideChar.
TBytes is recommended to be used as data buffer/binary storage. (For this purpose, TBytes is recommended over AnsiString.)
The bytes can be retrieved from TStringStream.ReadBuffer method or TStringStream.Bytes property. Either way, TStream.Size should be considered.
====================================================
I am trying to use TStringStream and its DataString for base64-encoding/decoding purposes. It seems possible as indicated by Nils Haeck's reply here or here.
Using TStringStream.DataString in TMainForm.QuestionOfString_StringStream (No.2 to No.7) fail in that the information is corrupted (i.e., not the same as the original information). However, ss_loaded_2.SaveToFile (No.1) saves the original information, indicating TStringStream does hold decoded non-textual data correctly internally? Could you help to comment about the possible reasons of DataString corruption?
In Rob Kennedy's kind answer, he mentioned string or ansistring should be avoid in storing base64-decoded non-textual data, which makes great sense. However, as shown in TMainForm.QuestionOfString_NativeXML, the DecString of AnsiStringtype contains the decoded bytes so correctly that the data can be encoded back. Does this mean AnsiString can hold decoded non-texual data intact?
David Heffernan and Rob Kennedy have kindly commented about bytes/TBytes. However, bytes extracted in TMainForm.QuestionOfString_NativeXML_Bytes_1 , is different from TStringStream's Bytes in TMainForm.QuestionOfString_NativeXML_Bytes_2. (From Base64-encoding/decoding results, the TStringStream.Bytes is wrong. It is confusing because based on the above paragraph, TStringStream should contain the intact bytes internally?) Could you help to comment about the possible reason?
Thank you very much for your help!
PS: The sample files can be download from SkyDrive: REF_EncodedSample & REF_DecodedSample. (Zlib-compressed image file.).
PS: Delphi XE, Windows 7. (It seems TStringStream back in Delphi 7 doesn't have LoadFromFile or SaveToFile.)
sample code
unit uMainForm;
interface
uses
CodeSiteLogging,
NativeXml, // v3.10
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure QuestionOfString_StringStream;
procedure QuestionOfString_NativeXML;
procedure QuestionOfString_NativeXML_Bytes_1;
procedure QuestionOfString_NativeXML_Bytes_2;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
// http://stackoverflow.com/questions/773297/how-can-i-convert-tbytes-to-rawbytestring
function Convert(const Bytes: TBytes): RawByteString;
begin
SetLength(Result, Length(Bytes));
if Length(Bytes) > 0 then
begin
Move(Bytes[0], Result[1], Length(Bytes));
// SetCodePage(Result, CP_ACP, False);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
QuestionOfString_StringStream;
QuestionOfString_NativeXML;
QuestionOfString_NativeXML_Bytes_1;
QuestionOfString_NativeXML_Bytes_2;
end;
// http://www.delphigroups.info/2/3/321962.html
// http://borland.newsgroups.archived.at/public.delphi.graphics/200712/0712125679.html
procedure TMainForm.QuestionOfString_StringStream;
var
ss_loaded_2, ss_loaded_3: TStringStream;
dataStr: AnsiString;
hexOfDataStr: AnsiString;
begin
ss_loaded_2 := TStringStream.Create();
// load the file containing Base64-decoded sample data
ss_loaded_2.LoadFromFile('REF_DecodedSample');
// 1
ss_loaded_2.SaveToFile('REF_DecodedSample_1_SavedByStringStream');
// 2
ss_loaded_3 := TStringStream.Create(ss_loaded_2.DataString);
ss_loaded_3.SaveToFile('REF_DecodedSample_2_SavedByStringStream');
// 3
ss_loaded_3.Free;
ss_loaded_3 := TStringStream.Create(ss_loaded_2.DataString, TEncoding.ASCII);
ss_loaded_3.SaveToFile('REF_DecodedSample_3_SavedByStringStream');
// 4
ss_loaded_3.Free;
ss_loaded_3 := TStringStream.Create(ss_loaded_2.DataString, TEncoding.UTF8);
ss_loaded_3.SaveToFile('REF_DecodedSample_4_SavedByStringStream');
// 5
ss_loaded_3.Free;
ss_loaded_3 := TStringStream.Create(AnsiString(ss_loaded_2.DataString));
ss_loaded_3.SaveToFile('REF_DecodedSample_5_SavedByStringStream');
// 6
ss_loaded_3.Free;
ss_loaded_3 := TStringStream.Create(UTF8String(ss_loaded_2.DataString));
ss_loaded_3.SaveToFile('REF_DecodedSample_6_SavedByStringStream');
// 7
dataStr := ss_loaded_2.DataString;
SetLength(hexOfDataStr, 2 * Length(dataStr));
BinToHex(#dataStr[1], PAnsiChar(#hexOfDataStr[1]), Length(dataStr));
CodeSite.Send(hexOfDataStr);
ss_loaded_2.Free;
ss_loaded_3.Free;
end;
// http://www.simdesign.nl/forum/viewtopic.php?f=2&t=1311
procedure TMainForm.QuestionOfString_NativeXML;
var
LEnc, LDec: integer;
EncStream: TMemoryStream;
DecStream: TMemoryStream;
EncString: AnsiString;
DecString: AnsiString;
begin
// encode and decode streams
EncStream := TMemoryStream.Create;
DecStream := TMemoryStream.Create;
try
// load BASE64-encoded data
EncStream.LoadFromFile('REF_EncodedSample');
LEnc := EncStream.Size;
SetLength(EncString, LEnc);
EncStream.Read(EncString[1], LEnc);
// decode BASE64-encoded data, after removing control chars
DecString := DecodeBase64(sdRemoveControlChars(EncString));
LDec := length(DecString);
DecStream.Write(DecString[1], LDec);
// save the decoded data
DecStream.SaveToFile('REF_DecodedSample_7_SavedByNativeXml');
// EncString := sdAddControlChars(EncodeBase64(DecString), #$0D#$0A);
EncString := EncodeBase64(DecString);
// clear and resave encode stream as a copy
EncStream.Clear;
EncStream.Write(EncString[1], Length(EncString));
EncStream.SaveToFile('REF_EncodedSampleCopy');
finally
EncStream.Free;
DecStream.Free;
end;
end;
procedure TMainForm.QuestionOfString_NativeXML_Bytes_1;
var
LEnc, LDec: integer;
EncStream: TMemoryStream;
DecStream: TMemoryStream;
EncString: AnsiString;
DecString: AnsiString;
DecBytes: TBytes;
begin
// encode and decode streams
EncStream := TMemoryStream.Create;
DecStream := TMemoryStream.Create;
try
// load BASE64-decoded data
DecStream.LoadFromFile('REF_DecodedSample');
LDec := DecStream.Size;
SetLength(DecBytes, LDec);
DecStream.Read(DecBytes[0], LDec);
EncString := EncodeBase64(Convert(DecBytes));
// clear and resave encode stream as a copy
EncStream.Write(EncString[1], Length(EncString));
EncStream.SaveToFile('REF_EncodedSampleCopy_Bytes_1');
finally
EncStream.Free;
DecStream.Free;
end;
end;
procedure TMainForm.QuestionOfString_NativeXML_Bytes_2;
var
LEnc, LDec: integer;
EncStream: TMemoryStream;
DecStream: TStringStream;
EncString: AnsiString;
DecString: AnsiString;
DecBytes: TBytes;
begin
// encode and decode streams
EncStream := TMemoryStream.Create;
DecStream := TStringStream.Create;
try
// load BASE64-decoded data
DecStream.LoadFromFile('REF_DecodedSample');
DecBytes := DecStream.Bytes;
EncString := EncodeBase64(Convert(DecBytes));
// clear and resave encode stream as a copy
EncStream.Write(EncString[1], Length(EncString));
EncStream.SaveToFile('REF_EncodedSampleCopy_Bytes_2');
finally
EncStream.Free;
DecStream.Free;
end;
end;
end.
It's really no surprise that examples 3 through 7 fail. Your file is not textual data, so storing it in a text data structure is bound to show problems. Each of those tests involves converting the data from one encoding to another. Since your data isn't encoded as UTF-16 text to begin with, any conversion that expects the data to have that encoding is going to fail.
Example 2 probably fails because you have an odd number of bytes, and you're storing it in a string that by definition contains an even number of bytes. Somewhere, a byte is going to be introduced or dropped, causing different data to be stored.
Unless you're dealing with text, don't use TStringStream, string, or AnsiString. Try TBytesStream or TMemoryStream instead.
Feel free to store Base64-encoded data in a string. Base64 is a text format. But once you decode it, it's binary again, and has no business being in a text data structure anymore.
The reason you see different results now from what Nils Haeck suggested you should expect is that Haeck was writing in 2007, before Delphi strings became Unicode and the RTL did any automatic code-page conversions. You're using Delphi XE, where string is UnicodeString.
You are not taking into account that TStringStream derives from TMemoryStream and TByteStream in D2009+ but derived directly from TStream in earlier versions. TMemoryStream allocates memory differently than your code is expecting, and the TByteStream.Bytes property represents the entire memory block that TMemoryStream allocates, but that does not mean that the entire contents of that memory is filled in with actual data. There is some extra padding involved that your code needs to ignore.
See my answer to your other question for a more detailed explanation as to why your code is failing.
Strings in Delphi locating in dynamic memory.
How to calculate actual memory (in bytes) used by string variable?
I know the string must store some additional information, at least reference count and length, but how many bytes it uses except characters?
var
S: string;
Delphi 2010, XE, XE2 used
The layout on 32 bit UNICODE DELPHI taken from official Embarcadero documentation is like this:
Note that there's an additional longint field in the 64 bit version for 16 byte alignment. The StrRec record in 'system.pas' looks like this:
StrRec = packed record
{$IF defined(CPUX64)}
_Padding: LongInt; // Make 16 byte align for payload..
{$IFEND}
codePage: Word;
elemSize: Word;
refCnt: Longint;
length: Longint;
end;
The payload is always 2*(Length+1) in size. The overhead is 12 or 16 bytes, for 32 or 64 bit targets. Note that the actual memory block may be larger than needed as determined by the memory manager.
Finally, there has been much mis-information in this question. On 64 bit targets, strings are still indexed by 32 bit signed integers.
For String specifically, you can use SysUtils.ByteLength() to get the byte length of the character data, and if not zero then increment the result by SizeOf(System.StrRec) (which is the header in front of the character data) and SizeOf(Char) (for the null-terminator that is not included in the length), eg:
var
S: string;
len: Integer;
begin
S := ...;
len := ByteLength(s);
if len > 0 then Inc(len, SizeOf(StrRec) + SizeOf(Char));
end;
On the other hand, if you want to calculate the byte size of other string types, like AnsiString, AnsiString(N) (such as UTF8String), RawByteString, etc, you need to use System.StringElementSize() instead, eg:
var
S: SomeStringType;
len: Integer;
begin
S := ...;
len := Length(S) * StringElementSize(S);
if len > 0 then Inc(len, SizeOf(StrRec) + StringElementSize(s));
end;
In either case, the reason you only increment the length if the string has characters in it is because empty strings do not take up any memory at all, they are nil pointers.
To answer the question:
How to calculate actual memory (in bytes) used by string variable?
MemSize = Overhead + CharSize * (Length + 1)
CharSize = 1 // for Ansi strings
CharSize = 2 // for Unicode strings
Overhead = 8 // for 32 bit strings
Overhead = 16 // for 64 bit strings
I need to do something with a Unicode string of a tree of a TTreeView, so I want to load this string into a memory stream and then load the memory stream into the tree view. How can I do this?
You be tempted to use directly the TStringStream class intead of a TMemoryStream. But this TStringStream class will encode the UnicodeString into an AnsiString before storage, in the Unicode Delphi version...
So here are some functions to create a TMemoryStream instance with pure Unicode content, then retrieve back this text:
function StringToMemoryStream(const Text: string): TMemoryStream;
var Bytes: integer;
begin
if Text='' then
result := nil else
begin
result := TMemoryStream.Create;
Bytes := length(Text)*sizeof(Char);
result.Size := Bytes;
move(pointer(Text)^,result.Memory^,Bytes);
end;
end;
function MemoryStreamToString(MS: TMemoryStream): string;
begin
if MS=nil then
result := '' else
SetString(result,PChar(MS.Memory),MS.Size div sizeof(Char));
end;
Be sure that you Free the TMemoryStream when you won't need it any more.
By using sizeof(Char) and PChar, this code will also work with previous non-Unicode version of Delphi.