OleVariant and String assignments in Delphi 5 - string

I have a Delphi 5 legacy application and there's a part in which a "string" value is been assigned to an "OleVariant" variable. Something like this:
var
X: OleVariant;
S: string;
Begin
S:= ‘This string should contain 200 characters as per design’;
X:= S;
End;
If the length of “S” is greater than 128, then the value of “X” gets truncated and it only holds a maximum of 128 characters.
Is there a way to overcome this?
I believe there is a way, because if I create my own demo application from scratch (in the same PC, with the same Delphi 5), it allows me to pass longer string values and no truncating is done.
Maybe it is something about the project settings or compiler directives. I have played around with this idea, but I have no workaround yet.
Any help is appreciated. Thanks.
Demo:
procedure TForm1.Button1Click(Sender: TObject);
var
X: OleVariant;
S: string;
begin
//in the Edit I pass a string of 240 chars, let's say;
S:= Edit1.Text;
X:= S;
ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
//this showmessage shows "128 : 240"
end;

Try this OleVariantToString and StringToOleVariant functions at http://www.foxbase.ru/delphi/vzaimnye-preobrazovaniya-olevariant-i-string.htm
They work perfectly for me.
uses Classes, Variants;
function OleVariantToString(const Value: OleVariant): string;
var ss: TStringStream;
Size: integer;
Data: PByteArray;
begin
Result:='';
if Length(Value) = 0 then Exit;
ss:=TStringStream.Create;
try
Size := VarArrayHighBound (Value, 1) - VarArrayLowBound(Value, 1) + 1;
Data := VarArrayLock(Value);
try
ss.Position := 0;
ss.WriteBuffer(Data^, Size);
ss.Position := 0;
Result:=ss.DataString;
finally
VarArrayUnlock(Value);
end;
finally
ss.Free;
end;
end;
function StringToOleVariant(const Value: string): OleVariant;
var Data: PByteArray;
ss: TStringStream;
begin
Result:=null;
if Value='' then Exit;
ss:=TStringStream.Create(Value);
try
Result := VarArrayCreate ([0, ss.Size - 1], varByte);
Data := VarArrayLock(Result);
try
ss.Position := 0;
ss.ReadBuffer(Data^, ss.Size);
finally
VarArrayUnlock(Result);
end;
finally
ss.Free;
end;
end;

One explanation is that OleVariant holds the entire string but that you are looking at the debugger tooltip. In older Delphi versions the debugger tooltip truncates at 128 characters for strings held in a variant. Note that the debugger tooltip for a plain string does not truncate at this length. Try showing the variant in a dialog box and you will see that the entire string is present.
I checked this out on Delphi 6 and there was no truncation with your code (other than the debugger tooltip). Andreas did likewise on Delphi 4 and Rodrigo did so with Delphi 5. I cannot imagine that it could really be the case that strings in a Delphi 5 OleVariant are truncated at 128 characters.
If you really are seeing what you are report then I can think of the following explanations:
Your code is erroneously truncating the string, but you have not yet found the code that does this. Only you can debug that.
You have a local bug private to your Delphi installation. Are you by any chance compiling your own RTL?

I made this work. Summary: instead of filling an “OleVariant” with a “string”; I filled a “Variant” and then typecasted that “Variant” to “OleVariant”. Take a look at the code below so that you can get the idea.
procedure TForm1.Button1Click(Sender: TObject);
var
//X: OleVariant;
X: Variant;
S: string;
begin
//Let's say in the Edit1 I pass a string of 240 chars,
S:= Edit1.Text;
X:= S;
//ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
ShowMessage(IntToStr(Length(OleVariant(X))) + ' : ' + IntToStr(Length(S)));
//This ShowMessage shows "128 : 240"
end;
Honestly, I don’t know for sure why this makes a difference, but it does. It works ok now.
Thanks a lot for your help folks!

Related

Pascal Script fails to retrieve wide string from a custom DLL

I need Inno Setup to load a wide string from a function in a custom DLL. I have no problem loading Ansi strings – this, for example, works fine:
int __stdcall GetStringA(char *lpText)
{
StringCchCopyA(lpText, 30, "New Ansi value");
return strlen(lpText);
}
Pascal Script
function GetStringA(lpText: AnsiString): Integer;
external 'GetStringA#files:MyDll.dll';
function LoadStringA(): AnsiString;
var
Str: AnsiString;
Length: Integer;
begin
Str := 'Initial Ansi value';
SetLength(Str, 30);
Length := GetStringA(Str);
SetLength(Str, Length);
Result := Str; // Str == 'New Ansi value'
end;
Doing the same thing with wide strings fails, however:
int __stdcall GetStringW(wchar_t *lpText)
{
StringCchCopyW(lpText, 30, L"New Wide value");
return wcslen(lpText);
}
Pascal Script
function GetStringW(lpText: WideString): Integer;
external 'GetStringW#files:MyDll.dll';
function LoadStringW(): WideString;
var
Str: WideString;
Length: Integer;
begin
Str := 'Initial Wide value';
SetLength(Str, 30);
Length := GetStringW(Str); // ** DOES NOT ALTER 'Str' **
SetLength(Str, Length);
Result := Str; // Str == 'Initial Wide v' (old string, new length)
end;
Sending a WideString to the DLL works fine. Changing the buffer contents in the DLL also works fine…but somehow that does not percolate back to my Pascal Script variable.
I suspect that some sort of marshaling goes on under the hood, so that the DLL actually manipulates a copy of my variable. Any insights or workarounds appreciated!
I'm using Inno Setup 6.2.0.
I made it work by replacing WideString with String:
function GetStringW(lpText: String): Integer;
external 'GetStringW#files:MyDll.dll';
function LoadStringW(): WideString;
var
Str: String;
Length: Integer;
begin
Str := 'Initial Wide value';
SetLength(Str, 30);
Length := GetStringW(Str); // ** ALTERS 'Str' **
SetLength(Str, Length);
Result := Str; // Str == 'New Wide value'
end;
I'd have thought WideString and String were equivalent, but apparently not quite. It is necessary to change both the function prototype and the Str declaration. It is not necessary to change the LoadStringW return type, though you may – and the returned value can in either case be assigned to a variable of type WideString or String without problem.
So: Problem solved. I'm still curious about the underlying behavior, though.

PopString and PushString methods in the assembled dll

During the execution of the dll the installer crashes on these methods
The methods are taken from the file nsis.pas
function PopString(): string;
var
th: pstack_t;
begin
if integer(g_stacktop^) <> 0 then begin
th := g_stacktop^;
Result := PChar(#th.text);
g_stacktop^ := th.next;
GlobalFree(HGLOBAL(th));
end;
end;
procedure PushString(const str: string='');
var
th: pstack_t;
begin
if integer(g_stacktop) <> 0 then begin
th := pstack_t(GlobalAlloc(GPTR, SizeOf(stack_t) + g_stringsize));
lstrcpyn(#th.text, PChar(str), g_stringsize);
th.next := g_stacktop^;
g_stacktop^ := th;
end;
end;
It looks like nsis.pas is perhaps not compatible with every Delphi version when compiling as Unicode. You did not specify your version so I don't know if your string type is AnsiString or WideString or UnicodeString.
I don't really know Pascal/Delphi anymore but I was able to get it working in FreePascal with this version. I'll try to fix it for the next release.
Make sure UNICODE is defined when using this in a Unicode plug-in.
The problem was very easy to solve
It was necessary to call Init() from nsis.pas
and then everything worked

Why does the usage of TStringStream.DataString fail when TStringStream loads binary non-textual file?

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.

Inno Setup comma error

I have GetVersion function in [Code] that returns a string like this "1004", "1003", etc.
I created this function to check the registry value for lowest version numbers and uninstall them.
Here is a snippet of the code it is giving error point to StrtoInt conversion line stating
Comma (,) expected
Here is the snippet:
function DoesOldVersionsExist(): Boolean;
var
AppVersion: integer;
mstr: string;
VersionInstalled: cardinal;
begin
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
...
after that line I'm simple comparing the values and return true or false. Much Appreciated.
This is what error message says:
Line 55
Column 40.
Comma (,) expected
Thanks Deanna but unfortunately that is the error message pointing to this :
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
^
Here is the GetVersion function:
function GetVersion(AppVersion: String): String;
var
Version: String;
CharIndex: integer;
c: char;
begin
for CharIndex := 1 to Length(AppVersion) do begin
c := AppVersion[CharIndex];
if (c <> '.') then
Version := Version + c;
end;
Result := Version;
end;
I think that you can't just have Inno Setup constants in code like this, you have to use ExpandConstant():
AppVersion := StrToInt(GetVersion(ExpandConstant('{#MyAppVersion}')), 0);
You have not given us enough information to give a definite answer, but I think that the situation is as follows.
You have defined some constant called MyAppVersion which you let the ISPP (the Inno Setup pre-processor) substitute. Now, you have not told us what type this variable is, and you have not told us what the signature of GetVersion is (in particular, what type of argument does it expect?). However, if these types are strings, you need to write
StrToInt(GetVersion('{#MyAppVersion}'), 0);
in order to obtain, say,
StrToInt(GetVersion('Some string, this is!'), 0);
instead of
StrToInt(GetVersion(Some string, this is!), 0);
which is malformed (indeed, to such an extent that it hurts my eyes to look at it).

How do I convert a non-null-terminated char array into a string?

I have a function that takes in a pointer to a series of char.
I wish to copy 256 chars from that point, and place them in a string.
msg is not null-terminated.
The below code seems to give me some problem.
Is there a correct way to do this?
Init( msg: PCHAR)
var
myStr: String;
begin
for i:= 1 to 256 do
begin
myStr[i] := msg[i-1];
end;
end;
SetString(myStr, msg, 256);
Your code misses SetLength, the corrected version is:
Init( msg: PCHAR)
var
myStr: String;
begin
SetLength(myStr, 256);
for i:= 1 to 256 do
begin
myStr[i] := msg[i-1];
end;
end;
The assignment can be done more efficiently as already answered.
Updated
SetLength allocates 256 characters + terminating 0 for myStr; without SetLength your code is an error: it writes to wild address and will finally result in access violation.
If msg is null-terminated, as it should be, and the 256 characters you want to obtain are followed by the null character, simply do
myStr := msg;
If msg is longer than that, you could just do
myStr := Copy(msg, 1, 256);
In this case, a better method is
myStr := WideCharLenToString(msg, 256);
assuming you are using Delphi 2009 or later, in which the strings are Unicode.
myStr := Copy(msg, 1, 256);

Resources