Inno Setup LoadStringFromFile fails when file is open in another process - inno-setup

To check to see when a database (SQL Anywhere) is fired up and ready to receive requests I am outputting the database message window to a log (text) file and then attempting to read this using LoadStringFromFile, which I then search for specific text using Pos. The problem is that this fails (I assume) as the file is in use.
Exec(strInstallPath + '\Bin32\dbeng17.exe', '-n ' + strEngineName + ' "' + strInstallPath + '\Database\Olympus.db" -n ' + strDatabaseName + ' -gdall -xtcpip -ti0 -c25p -ot "' + strTempPath + '\dbeng.log"', '', SW_HIDE,
ewNoWait, intResultCode);
if not LoadStringFromFile(strTempPath + '\dbeng.log', astrDatabaseEngineLog) then
begin
Log('Loading string from file failed.');
end;
I have also tried to copy the log file using FileCopy and attempt to read from the copy of the file, but FileCopy also fails.
if not FileCopy(strTempPath + '\dbeng.log', strTempPath + '\dbengcopy.log', False) then
begin
Log('File copy failed.');
end;
Is there any way to read from a file that is in use or another way to do this?

When spawning the database server, use the dbspawn utility, which is designed for this purpose. You spawn dbspawn along with the dbeng/dbsrv command you want to run, and it starts the server for you. The dbspawn utility doesn't return until the database server is up, running, and ready to accept requests, so there's no guesswork needed and no need to read the console log file.
I don't know what version of SQL Anywhere you're running, but here is the documentation for v17. It should be the same in any other version.

Use the TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone).
In Unicode version of Inno Setup (the only version as of Inno Setup 6), its use is tricky due to a bad interface of the class.
function BufferToAnsi(const Buffer: string): AnsiString;
var
W: Word;
I: Integer;
begin
SetLength(Result, Length(Buffer) * 2);
for I := 1 to Length(Buffer) do
begin
W := Ord(Buffer[I]);
Result[(I * 2)] := Chr(W shr 8); // high byte
Result[(I * 2) - 1] := Chr(Byte(W)); // low byte
end;
end;
function LoadStringFromLockedFile(
const FileName: string; var S: AnsiString): Boolean;
var
Buffer: string;
Stream: TFileStream;
begin
Result := True;
try
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
try
SetLength(Buffer, Stream.Size div 2);
Stream.ReadBuffer(Buffer, Stream.Size);
S := BufferToAnsi(Buffer);
finally
Stream.Free;
end;
except
Result := False;
end;
end;
The code is based on TLama's code posted in Read bytes from file at desired position with Inno Setup.

Related

Evaluate a collection of data from preprocessor on run time in Inno Setup Pascal Script

I am trying to get Inno Setup define value in Code section but not with {#VersionTool1}. I need to pass defined name dynamically, because there are a lot of them (I want to avoid large switch case). I tried SetupSetting but it's not in Setup section (it's before it). Is there any way to do this?
#define VersionTool1 2019.01.1111
#define VersionTool2 2020.02.2111
...
[Code]
procedure SetSelectedTool(ToolName: String);
var
CurrentTool: string;
begin
...
CurrentTool := 'Version' + ToolName;
CurrentToolVersion := {#CurrentTool};
...
end;
Value of local variable CurrentTool wil for example be 'VersionTool1' and I want to get value of VersionTool1 preprocessor variable which is 2020.02.2111.
It's not possible, see Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
But there are other solutions.
For example:
[Code]
var
ToolNames: TStringList;
ToolVersions: TStringList;
function InitializeSetup(): Boolean;
begin
ToolNames := TStringList.Create;
ToolVersions := TStringList.Create;
#define AddToolVersion(Name, Version) \
"ToolNames.Add('" + Name + "'); ToolVersions.Add('" + Version +"');"
#emit AddToolVersion('Tool1', '2019.01.1111')
#emit AddToolVersion('Tool2', '2020.02.2111')
{ ... }
Result := True;
end;
(of course, the above makes sense only if you actually do not hardcode the version numbers, but use a code that only a preprocessor can do – something like GetStringFileInfo, what I've understood from your comments that you plan to)
And then you can have a function like:
function GetToolVersion(ToolName: string): string;
var
I: Integer;
begin
I := ToolNames.IndexOf(ToolName);
if I >= 0 then Result := ToolVersions[I];
end;
Another similar questions:
Array Variables and dynamic access in [Code] section
Scripting capabilities in the Registry section

Inno Setup: uninstall previous installed version failed [duplicate]

My requirement is to check for previous installation of SQL native Client 11, before installation and uninstall the previous version. I have been able to check for the previous installation with no problems, however, I am unable to uninstall the same.
I used the solution mentioned in the How to detect old installation and offer removal?
During run time, I am getting the following error
Exception: Internal error: Unknown constant "A22EED3F-6DB6-4987-8023-6C6B7030E554".
(where the constant is the GUID of the native client) during the execution of the line
Exec(ExpandConstant(sUnInstallString), '', '', SW_SHOW, ewWaitUntilTerminated, iResultCode);
The sUnInstallString is
MsiExec.exe /I{A22EED3F-6DB6-4987-8023-6C6B7030E554}
Thanks in advance.
That's not a (Inno Setup) constant. That's a GUID. Remove the ExpandConstant call.
And you need to split the uninstall string to a program path and its parameters.
var
P: Integer;
UninstallPath: string;
UninstallParams: string;
begin
// ...
// In case the program path is quoted, because it contains spaces.
// (it's not in your case, but it can be, in general)
if Copy(sUnInstallString, 1, 1) = '"' then
begin
Delete(sUnInstallString, 1, 1);
P := Pos('"', sUnInstallString);
end
else P := 0;
if P = 0 then
begin
P := Pos(' ', sUnInstallString);
end;
UninstallPath := Copy(sUnInstallString, 1, P - 1);
UninstallParams :=
TrimLeft(Copy(sUnInstallString, P + 1, Length(sUnInstallString) - P));
Exec(UninstallPath, UninstallParams, '', SW_SHOW, wWaitUntilTerminated,
iResultCode);
// ...
end;

Inno Setup: Reading a file from installer during uninstallation

Using the following code during uninstall
BitmapImage := TBitmapImage.Create(InstallTopPanel);
BitmapImage.AutoSize := True;
BitmapImage.Bitmap.LoadFromFile(
ExpandConstant( '{tmp}\WizardSmallImageFile.bmp') );
BitmapImage.Parent := InstallTopPanel;
BitmapImage.Top := (InstallTopPanel.ClientHeight - 58) / 2;
BitmapImage.Left := InstallTopPanel.ClientWidth - 55 - 10;
I get an error:
Exception : can not open file.
C:\users\xyz\AppData\Local\Temp\is-U3Q8P.tmp\WizardSmallImageFile.Bmp.
File not found.
I tried also to use ExtractTemporaryFile before I call LoadFromFile which is not supported during uninstall.
ExtractTemporaryFile('WizardSmallImageFile.bmp');
So, the question, how to view an image or specifically WizardSmallImageFile during uninstall?
My code above builds a custom form with a custom panel. Like here: Inno Setup Uninstall some components only.
Correct, the ExtractTemporaryFile extracts files from the installer. Therefore it cannot work in the uninstaller as the installer is not available anymore.
Also note that you cannot extract the file referenced to by the WizardSmallImageFile directive from the installer anyway. You have to add your own copy.
If you need to use some file during uninstallation, you have to install it in the installer and then use the installed copy in the uninstaller.
[Files]
Source: "WizardSmallImageFile.bmp"; DestDir: "{app}";
[Code]
function InitializeUninstall(): Boolean;
begin
...
BitmapImage := TBitmapImage.Create(...);
...
BitmapImage.Bitmap.LoadFromFile(
ExpandConstant('{app}\WizardSmallImageFile.bmp'));
...
end;
If you want to do without installing the file, you can embed the image data into the code.
Unfortunately the Unicode Inno Setup is quite limited when dealing with binary data as it tends to try to convert everything to UTF-8. But after numerous tries I've ended up with some working code.
Note that the code uses a PowerShell code invoked from Inno Setup preprocessor – The PowerShell is needed on compile-time only, not on run/install-time.
Add this code somewhere to the front of your [Code] section:
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinaryW#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
procedure WriteBinaryStringToStream(S: string; Stream: TStream);
var
Buffer: string;
Size: LongWord;
Len: Integer;
begin
Len := Length(S);
SetLength(Buffer, (Len div 4) + 1);
Size := Len div 2;
if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Len div 2) then
begin
RaiseException('Error decoding binary string');
end;
Stream.WriteBuffer(Buffer, Size);
end;
function StreamFromBinaryString(S: string): TStream;
begin
Result := TStringStream.Create('');
WriteBinaryStringToStream(S, Result);
Result.Position := 0;
end;
procedure LoadBitmapFromBinaryString(Bitmap: TBitmap; S: string);
var
Stream: TStream;
begin
Stream := StreamFromBinaryString(S);
try
Bitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure SaveBinaryStringToFile(FileName: string; S: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
WriteBinaryStringToStream(S, Stream);
finally
Stream.Free;
end;
end;
#define FileToBinaryString(str FileName) \
Local[4] = ExtractFileName(FileName), \
Local[0] = AddBackslash(GetEnv("TEMP")) + Local[4] + ".pas", \
Local[1] = \
"-ExecutionPolicy Bypass -Command """ + \
"Write-Host 'Generating code for " + Local[4] + "'; " + \
"$bytes = [System.IO.File]::ReadAllBytes('" + FileName + "'); " + \
"$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
"Set-Content -Path '" + Local[0] + "' -Value $s;" + \
"""", \
Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
Local[2] = FileOpen(Local[0]), \
Local[3] = FileRead(Local[2]), \
FileClose(Local[2]), \
DeleteFileNow(Local[0]), \
Local[3]
And then you can use the FileToBinaryString preprocessor macro to convert a file on compile-time (or more precisely, when pre-processing) to a hex string like:
'4D5A50000200000004000F00FFFF0000B800000....'
On runtime, you use the hex string with some of the functions WriteBinaryStringToStream, StreamFromBinaryString, LoadBitmapFromBinaryString or SaveBinaryStringToFile.
In your case you will use:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});
On compile-time, this gets converted to a code like:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B800000....');
The pre-processor/Pascal compiler has a limit of about 100M characters for a string. Though you will actually first hit a [compile-time] memory limit of the PowerShell script for files larger than about 20-30 MB. Though even for smaller sizes (larger than few MBs), the compile-time time performance of the PowerShell script is bad. The script can be optimized significantly though.
Due to the hex encoding, the size of the installer increases twice as much. This could be improved by using some more efficient encoding, like Base64 (CRYPT_STRING_BASE64). The code section is not even compressed too, comparing to files included with the [Files] section (not a problem for images as these are compressed already, but makes a difference with DLLs for example).
The code requires the Unicode version of Inno Setup. You should not use the Ansi version anyway, in the 21st century. Though ironically, implementing this in the Ansi version would be way easier. See my answer to Writing binary file in Inno Setup for a use of the CryptStringToBinary that's compatible with both Ansi and Unicode version of Inno Setup. Though in the Ansi version you can actually do with a binary string, instead of a hex string.

merge paint results in thread bitmap painting

I want to speed up painting a bitmap, therefore I designed a class like BITMAP THREAD CLASS. Once the individual painting of a partial image is finished I want to merge all image in the Thread.done procedure
My code goes like this
type
TbmpthreadForm = class(TForm)
.....
THreadImage: TImage;
procedure Button_threadstartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private-Deklarationen }
procedure ThreadDone(Sender: TObject);
public
{ Public-Deklarationen }
fserver, fdatabasename, ftablename: String;
global_thread_counter: Integer;
XPixel, YPixel: Integer;
Masterbitmap: TBitmap;
end;
var
bmpthreadForm: TbmpthreadForm;
implementation
{$R *.dfm}
procedure TbmpthreadForm.ThreadDone(Sender: TObject);
begin
dec(global_thread_counter);
MyStatusBar.SimpleText := 'Thread Count ->' + IntToStr(global_thread_counter);
Masterbitmap.Canvas.Lock;
with (Sender as TPaintBitmapThread) do
begin
bitblt(Masterbitmap.Canvas.handle, 0, 0, XPixel, YPixel,
bitmap.Canvas.handle, 0, 0, srcand);
THreadImage.Picture.Bitmap.Assign(Masterbitmap);
// lets see local tthread intermediate results and save it to HD
THreadImage.Picture.Bitmap.SaveToFile('c:\temp\myimage' + IntToStr(Index)
+ '.bmp');
end;
Masterbitmap.Canvas.UnLock;
if (global_thread_counter = 0) then
begin
...
end;
end;
procedure TbmpthreadForm.Button_threadstartClick(Sender: TObject);
var
.....
begin
index_max := 2000000;
threadCounter := 10;
Indexdelta := round(index_max / threadCounter);
///
///
....
Masterbitmap.Width := XPixel;
Masterbitmap.Height := YPixel;
for i := 0 to threadCounter - 1 do
begin
n := i * Indexdelta;
m := (i + 1) * Indexdelta;
// just a test sql string ....
sqlstr := 'select * from Mytable where objectindex <' + IntToStr(m) +
' and Objectindex >' + IntToStr(n);
aPaintBitmapThread := TPaintBitmapThread.Create(XPixel, YPixel, ...... , fserver, fdatabasename, ftablename,
sqlstr, i);
aPaintBitmapThread.OnTerminate := ThreadDone;
Memo1.Lines.Add('start thread->' + IntToStr(i));
inc(global_thread_counter);
end;
end;
The Thread.done design follows a previous topic here on SO ( reference question
As the resulting image/Masterbitmap looks a bit different from run to run , I guess my approach is not thread safe design for copy Thread bmp content into the masterbitmap in the VCL mainform,
I can not see any error in my code, what is wrong ????
additional question
Q1 : fbitmap inside TPaintBitmapThread is created inside the Thread.create procedure, for TAdoconnection I found the comment, it should be created inside the thread.execute. Must this also be done the the bitmap ?
Q2 : the attached image shows the expected result of an image(Bitmap) by an Thread and the actual image results (as seen by the THreadImage.Picture.Bitmap.SaveToFile command)
bitblt(Masterbitmap.Canvas.handle, 0, 0, XPixel, YPixel,
bitmap.Canvas.handle, 0, 0, srcand);
you explicitly called Masterbitmap.Canvas.Lock, however you didn't call bitmap.Canvas.Lock (so you can loose the canvas handle anytime within this call...)
Additionally, you need to consider thread safety within GDI itself: Sharing of any GDI objects between different threads should be avoided at all cost. For example if you select a bitmap into two different device contexts at the same time (but in different threads) you may run into problems within GDI itself...
Please note that older versions of delphi don't protect against sharing of cached handles (Font, Brush and Pen handles are cached within a global list. This was fixed in an XE3 service pack if I remember correctly).
In short: I'd consider to avoid TCanvas and TBitmap completely if you really need multi-threading. (It's much easier to be multi-threading safe that way)

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.

Resources