Related
Introduction
I encountered a problem with Currency in one of our applications. I was getting different results in Win32 and Win64. I found an article here that shows a similar problem but that one was fixed in XE6. The first thing I tried to do was create a MCVE to duplicate the problem. That's where the wheels fell off. What looks like the identical code in the MCVE produces a different result compared to the application. The generated code 64 bit is different. So my question morphed into why are they different and once I figure that out then I can create a suitable MCVE.
I have a method that is calculating a total. This method calls another method to get a value that needs to be added to the total. The method returns a single. I assign the single value to a variable and then add it to the total which is a Currency. In my main application the value for the total is used later on but adding that to the MCVE doesn't change the behavior. I made sure that the compiler options were the same.
In my main application, the result from the calculation is $2469.6001 in Win32 and 2469.6 in Win64 but I can't duplicate this in the MCVE. Everything on the Compiling options page was the same and optimizations were disabled.
Attempted MCVE
Here is the code for my attempted MCVE. This mimics the actions in the original application.
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TTestClass = class
strict private
FMyCurrency: Currency;
function GetTheValue: Single;
public
procedure Calculate;
property MyCurrency: Currency read FMyCurrency write FMyCurrency;
end;
procedure TTestClass.Calculate;
var
myValue: Single;
begin
FMyCurrency := 0.0;
myValue := GetTheValue;
FMyCurrency := FMyCurrency + myValue;
end;
function TTestClass.GetTheValue: Single;
var
myValueExact: Int32;
begin
myValueExact := 1159354778; // 2469.60009765625;
Result := PSingle(#myValueExact)^;
end;
var
testClass: TTestClass;
begin
testClass := TTestClass.Create;
try
testClass.Calculate;
WriteLn(CurrToStr(testClass.MyCurrency));
ReadLn;
finally
testClass.Free;
end;
end.
This code generates the following assembler for the last two lines of TTestClass.Calculate:
Project4.dpr.25: myValue := GetTheValue;
00000000004242A8 488B4D40 mov rcx,[rbp+$40]
00000000004242AC E83F000000 call TTestClass.GetTheValue
00000000004242B1 F30F11452C movss dword ptr [rbp+$2c],xmm0
Project4.dpr.26: FMyCurrency := FMyCurrency + myValue;
00000000004242B6 488B4540 mov rax,[rbp+$40]
00000000004242BA 488B4D40 mov rcx,[rbp+$40]
00000000004242BE F2480F2A4108 cvtsi2sd xmm0,qword ptr [rcx+$08]
00000000004242C4 F3480F5A4D2C cvtss2sd xmm1,qword ptr [rbp+$2c]
00000000004242CA F20F590D16000000 mulsd xmm1,qword ptr [rel $00000016]
00000000004242D2 F20F58C1 addsd xmm0,xmm1
00000000004242D6 F2480F2DC8 cvtsd2si rcx,xmm0
00000000004242DB 48894808 mov [rax+$08],rcx
Main Application
This is an extract from the main application. It's difficult to give more information but I don't think that will change the nature of the question. In this class, FBulkTotal is declared as a Currency that is strict private. UpdateTotals is public.
procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
bulkTotal: Single;
begin
..
bulkTotal := grouping.GetTotal(aMyObject, Self);
FBulkTotal := FBulkTotal + bulkTotal;
..
end;
The generated code for these two lines is:
TheCodeUnit.pas.7357: bulkTotal := grouping.GetTotal(aMyObject, Self);
0000000006DB0804 488B4D68 mov rcx,[rbp+$68]
0000000006DB0808 488B9598000000 mov rdx,[rbp+$00000098]
0000000006DB080F 4C8B8590000000 mov r8,[rbp+$00000090]
0000000006DB0816 E8551C0100 call grouping.GetTotal
0000000006DB081B F30F114564 movss dword ptr [rbp+$64],xmm0
TheCodeUnit.pas.7358: FBulkTotal := FBulkTotal + bulkTotal;
0000000006DB0820 488B8590000000 mov rax,[rbp+$00000090]
0000000006DB0827 488B8D90000000 mov rcx,[rbp+$00000090]
0000000006DB082E F3480F2A8128010000 cvtsi2ss xmm0,qword ptr [rcx+$00000128]
0000000006DB0837 F30F104D64 movss xmm1,dword ptr [rbp+$64]
0000000006DB083C F30F590D54020000 mulss xmm1,dword ptr [rel $00000254]
0000000006DB0844 F30F58C1 addss xmm0,xmm1
0000000006DB0848 F3480F2DC8 cvtss2si rcx,xmm0
0000000006DB084D 48898828010000 mov [rax+$00000128],rcx
What's strange is that the generated code is different. The MCVE has a cvtsi2sd followed by a cvtss2sd but that main application uses a movss in place of the cvtss2sd when copying the contents of the single value into the xmm1 register. I pretty sure that is what is causing the different result but without being able to create a MCVE, I can't even confirm that it is a problem with the compiler.
Question
My question is what can cause these differences in code generation? I assumed that the optimizations could do this type of thing but I made sure those were the same.
You should not be using any floating point type values when dealing with currency.
I recommend you watch Floating Point Numbers video from Computerphile where he explains of how floating point values are handled by computers and why they should not be used when handling currency.
https://www.youtube.com/watch?v=PZRI1IfStY0
I am trying to use a disconnected ADO Recordset in XE6. The idea is that you open the recordset normally, then you set the recordset's ActiveConnection to your language's equivalent of null/Nothing/nil:
rs.Set_ActiveConnection(null);
The following example from Delphi 5 works fine:
var rs: _Recordset;
rs := CoRecordset.Create;
rs.CursorLocation := adUseClient; //the default for a Recordset is adUseServer (Connection.Execute's default is adUseClient)
rs.CursorType := adOpenForwardOnly; //the default
rs.Open(CommandText, Conn,
adOpenForwardOnly, //CursorType
adLockReadOnly, //LockType
adCmdText);
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(nil);
It works in Delphi 5
The issue is that I cannot make it work in Delphi XE6. In Delphi 5 i would successfully call:
rs.Set_ActiveConnection(nil);
and everything worked splendidly. It worked because _Recordset interface was declared as:
procedure Set_ActiveConnection(const pvar: IDispatch); safecall;
So it was valid to pass nil; and it worked.
In XE6 the delcaration changed to:
procedure Set_ActiveConnection(pvar: OleVariant); safecall;
To which you cannot pass nil. The question then becomes, what is the OleVariant equivalent of nil?
Try #1
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(nil); //E2010 Incompatible types: 'OleVariant' and 'Pointer'
Try #2
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(Null);
causes exception:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another
Try #3
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(EmptyParam);
causes exception:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another
Try #4
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(Unassigned);
causes exception:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another
Try #5
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(OleVariant(nil)); //E2089 Invalid typecast
Try #6
//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(OleVariant(Null));
causes exception:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another
Try #7
It's clear to me that Codebarcadero got the declaration wrong. It really is supposed to be an IDispatch. This means i need to trick the compiler into passing an OleVariant located at address 0x00000000 (i.e. nil). That way ADO will see the value 0x00000000 on the stack, and know i mean null:
rs.Set_ActiveConnection(POleVariant(nil)^); //access violation before call
I'm sure Bo..Imp...Co..Embarcadero has the intended way to call this; i just cannot figure it out.
Delphi 5 assembly
Dephi 5 does the correct thing; it pushes $00 (i.e. nil) onto the stack:
rs.Set_ActiveConnection(nil);
push $0 ;push nil
mov eax,[ebp-$08] ;get address of rs
push eax ;push "this"
mov eax,[eax] ;get VMT of IRecordset
call dword ptr [eax+$28] ;call offset $28 of VMT
Whereas Delphi XE6 is going through heroic efforts to do something i don't know what:
rs.Set_ActiveConnection(nil);
lea eax,[ebp-$000000d8]
call Null
lea edx,[ebp-$000000d8]
lea eax,[ebp-$000000c8]
call #OleVarFromVar
push dword ptr [ebp-$000000bc]
push dword ptr [ebp-$000000c0]
push dword ptr [ebp-$000000c4]
push dword ptr [ebp-$000000c8]
mov eax,[ebp-$04]
push eax
mov eax,[eax]
call dword ptr [eax+$2c]
Bonus Reading
MSDN: Disconnecting and Reconnecting the Recordset
KB184397: How To Create ADO Disconnected Recordsets in VBA/C++/Java
MSDN: Recordset Object (ADO)
MSDN: ActiveConnection Property (ADO)
In D7 (don't have D5 to hand), AdoInt.Pas contains two flavours of Set_ActiveConnection, e.g.
Recordset15 = interface(_ADO)
['{0000050E-0000-0010-8000-00AA006D2EA4}']
procedure Set_ActiveConnection(const pvar: IDispatch); safecall;
procedure _Set_ActiveConnection(pvar: OleVariant); safecall;
and in Delphi XE6:
Recordset15 = interface(_ADO)
['{0000050E-0000-0010-8000-00AA006D2EA4}']
//...
procedure _Set_ActiveConnection(const pvar: IDispatch); safecall;
procedure Set_ActiveConnection(pvar: OleVariant); safecall;
So try the other version in XE6. Personally, I'd have tried
Set_ActiveConnection(IDispatch(Nil))
first, but you say in comments that _Set_ActiveConnection works for you.
The reason I'd have tried Set_ActiveConnection(IDispatch(Nil)) first, for an interface which requires an OleVariant to be passed, is this: Ever since interfaces were added into Delphi (in D3?), iirc in the version after variant-based OLE automation was added (D2), the compiler has known how to generate code to convert in both directions between an OleVariant and an IDispatch interface. So the "problem" is how to pass an IDispatch interface as an OleVariant argument. That bit, to my simple-minded way of looking at it, is easy, just write IDispatch() where the argument is supposed to be an OleVariant, and leave the compiler to sort out the code to generate. And if the value we want to pass as the IDisaptch interface is actually Nil, we just need to write
SomeInterfaceMemberExpectingAnOleVariant(IDispatch(Nil))
I have, on more than one occasion, advised people to use a return value of type WideString for interop purposes.
Accessing Delphi DLL throwing ocasional exception
ASP.NET web app calling Delphi DLL on IIS webserver, locks up when returning PChar string
Why can Delphi DLLs use WideString without using ShareMem?
The idea is that a WideString is the same as a BSTR. Because a BSTR is allocated on the shared COM heap then it is no problem to allocate in one module and deallocate in a different module. This is because all parties have agreed to use the same heap, the COM heap.
However, it seems that WideString cannot be used as a function return value for interop.
Consider the following Delphi DLL.
library WideStringTest;
uses
ActiveX;
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
function TestBSTR: TBstr; stdcall;
begin
Result := SysAllocString('TestBSTR');
end;
procedure TestWideStringOutParam(out str: WideString); stdcall;
begin
str := 'TestWideStringOutParam';
end;
exports
TestWideString, TestBSTR, TestWideStringOutParam;
begin
end.
and the following C++ code:
typedef BSTR (__stdcall *Func)();
typedef void (__stdcall *OutParam)(BSTR &pstr);
HMODULE lib = LoadLibrary(DLLNAME);
Func TestWideString = (Func) GetProcAddress(lib, "TestWideString");
Func TestBSTR = (Func) GetProcAddress(lib, "TestBSTR");
OutParam TestWideStringOutParam = (OutParam) GetProcAddress(lib,
"TestWideStringOutParam");
BSTR str = TestBSTR();
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
TestWideStringOutParam(str);
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
str = TestWideString();//fails here
wprintf(L"%s\n", str);
SysFreeString(str);
The call to TestWideString fails with this error:
Unhandled exception at 0x772015de in BSTRtest.exe: 0xC0000005: Access violation reading location 0x00000000.
Similarly, if we try to call this from C# with p/invoke, we have a failure:
[DllImport(#"path\to\my\dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();
The error is:
An unhandled exception of type 'System.Runtime.InteropServices.SEHException' occurred in ConsoleApplication10.exe
Additional information: External component has thrown an exception.
Calling TestWideString via p/invoke works as expected.
So, use pass-by-reference with WideString parameters and mapping them onto BSTR appears to work perfectly well. But not for function return values. I have tested this on Delphi 5, 2010 and XE2 and observe the same behaviour on all versions.
Execution enters the Delphi and fails almost immediately. The assignment to Result turns into a call to System._WStrAsg, the first line of which reads:
CMP [EAX],EDX
Now, EAX is $00000000 and naturally there is an access violation.
Can anyone explain this? Am I doing something wrong? Am I unreasonable in expecting WideString function values to be viable BSTRs? Or is it just a Delphi defect?
In regular Delphi functions, the function return is actually a parameter passed by reference, even though syntactically it looks and feels like an 'out' parameter. You can test this out like so (this may be version dependent):
function DoNothing: IInterface;
begin
if Assigned(Result) then
ShowMessage('result assigned before invocation')
else
ShowMessage('result NOT assigned before invocation');
end;
procedure TestParameterPassingMechanismOfFunctions;
var
X: IInterface;
begin
X := TInterfaceObject.Create;
X := DoNothing;
end;
To demonstrate call TestParameterPassingMechanismOfFunctions()
Your code is failing because of a mismatch between Delphi and C++'s understanding of the calling convention in relation to the passing mechanism for function results. In C++ a function return acts like the syntax suggests: an out parameter. But for Delphi it is a var parameter.
To fix, try this:
function TestWideString: WideString; stdcall;
begin
Pointer(Result) := nil;
Result := 'TestWideString';
end;
In C#/C++ you will need to define the Result as out Parameter, in order to maintain binary code compatibility of stdcall calling conventions:
Returning Strings and Interface References From DLL Functions
In the stdcall calling convention, the function’s result is passed via the CPU’s EAX register. However, Visual C++ and Delphi generate different binary code for these routines.
Delphi code stays the same:
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
C# code:
// declaration
[DllImport(#"Test.dll")]
static extern void TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s);
MessageBox.Show(s);
I asked a similar question about implicit interface variables not so long ago.
The source of this question was a bug in my code due to me not being aware of the existence of an implicit interface variable created by the compiler. This variable was finalized when the procedure that owned it finished. This in turn caused a bug due to the lifetime of the variable being longer than I had anticipated.
Now, I have a simple project to illustrate some interesting behaviour from the compiler:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := #I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal is compiled just as you would imagine. The local variable I, the function's result, is passed as an implicit var parameter to Create. The tidy up for StoreToLocal results in a single call to IntfClear. No surprises there.
However, StoreViaPointerToLocal is treated differently. The compiler creates an implicit local variable which it passes to Create. When Create returns, the assignment to P^ is performed. This leaves the routine with two local variables holding references to the interface. The tidy up for StoreViaPointerToLocal results in two calls to IntfClear.
The compiled code for StoreViaPointerToLocal is like this:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := #I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call #IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call #IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call #IntfClear
00435C9D C3 ret
I can guess as to why the compiler is doing this. When it can prove that assigning to the result variable will not raise an exception (i.e. if the variable is a local) then it uses the result variable directly. Otherwise it uses an implicit local and copies the interface once the function has returned thus ensuring that we don't leak the reference in case of an exception.
But I cannot find any statement of this in the documentation. It matters because interface lifetime is important and as a programmer you need to be able to influence it on occasion.
So, does anybody know if there is any documentation of this behaviour? If not does anyone have any more knowledge of it? How are instance fields handled, I have not checked that yet. Of course I could try it all out for myself but I'm looking for a more formal statement and always prefer to avoid relying on implementation detail worked out by trial and error.
Update 1
To answer Remy's question, it mattered to me when I needed to finalize the object behind the interface before carrying out another finalization.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
As written like this it is fine. But in the real code I had a second implicit local which was finalized after the GIL was released and that bombed. I solved the problem by extracting the code inside the Acquire/Release GIL into a separate method and thus narrowed the scope of the interface variable.
If there is any documentation of this behavior, it will probably be in the area of compiler production of temporary variables to hold intermediate results when passing function results as parameters. Consider this code:
procedure UseInterface(foo: IInterface);
begin
end;
procedure Test()
begin
UseInterface(Create());
end;
The compiler has to create an implicit temp variable to hold the result of Create as it is passed into UseInterface, to make sure that the interface has a lifetime >= the lifetime of the UseInterface call. That implicit temp variable will be disposed at the end of the procedure that owns it, in this case at the end of the Test() procedure.
It's possible that your pointer assignment case may fall into the same bucket as passing intermediate interface values as function parameters, since the compiler can't "see" where the value is going.
I recall there have been a few bugs in this area over the years. Long ago (D3? D4?), the compiler didn't reference count the intermediate value at all. It worked most of the time, but got into trouble in parameter alias situations. Once that was addressed there was a follow up regarding const params, I believe. There was always a desire to move disposal of the intermediate value interface up to as soon as possible after the statement in which it was needed, but I don't think that ever got implemented in the Win32 optimizer because the compiler just wasn't set up for handling disposal at statement or block granularity.
You can not guarantee that compiler will not decide to create a temporal invisible variable.
And even if you do, the turned off optimization (or even stack frames?) may mess up your perfectly checked code.
And even if you manage to review your code under all possible combinations of project options - compiling your code under something like Lazarus or even new Delphi version will bring hell back.
A best bet would be to use "internal variables can not outlive routine" rule. We usually do not know, if compiler would create some internal variables or not, but we do know, that any such variables (if created) would be finalized when routine exists.
Therefore, if you have code like this:
// 1. Some code which may (or may not) create invisible variables
// 2. Some code which requires release of reference-counted data
E.g.:
Lib := LoadLibrary(Lib, 'xyz');
try
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- May be not OK
end;
Then you should just wrap "Work with interface" block into subroutine:
procedure Work(const Lib: HModule);
begin
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
end; // <- Releases hidden variables (if any exist)
Lib := LoadLibrary(Lib, 'xyz');
try
Work(Lib);
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- OK!
end;
It is a simple, but effective rule.
Under Delphi XE, is there an ANSI version for Copy?
I am using Copy a lot to copy pieces of a ANSI strings.
Altar the Copy function in Delphi is a intrinsic function this means which is handled by the compiler rather than the run-time library. depending of the parameters passed this function call the LStrCopy or a UStrCopy internal functions
check this sample :
{$APPTYPE CONSOLE}
uses
SysUtils;
Var
s : AnsiString;
u : string;
begin
try
s:='this is a ansi string';
s:= Copy(s,1,5);
Writeln(s);
u:='this is a unicode string';
u:= Copy(u,1,5);
Writeln(u);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Now check the assembly code
Project91.dpr.12: s:='this is a ansi string';
004111DC B8787E4100 mov eax,$00417e78
004111E1 BA04134100 mov edx,$00411304
004111E6 E8314FFFFF call #LStrAsg
Project91.dpr.13: s:= Copy(s,1,5);
004111EB 68787E4100 push $00417e78
004111F0 B905000000 mov ecx,$00000005
004111F5 BA01000000 mov edx,$00000001
004111FA A1787E4100 mov eax,[$00417e78]
004111FF E8A050FFFF call #LStrCopy //call the ansi version of copy
Project91.dpr.14: Writeln(s);
00411204 A1EC2C4100 mov eax,[$00412cec]
00411209 8B15787E4100 mov edx,[$00417e78]
0041120F E84033FFFF call #Write0LString
00411214 E8DF33FFFF call #WriteLn
00411219 E8D22AFFFF call #_IOTest
Project91.dpr.15: u:='this is a unicode string';
0041121E B87C7E4100 mov eax,$00417e7c
00411223 BA28134100 mov edx,$00411328
00411228 E8534EFFFF call #UStrAsg
Project91.dpr.16: u:= Copy(u,1,5);
0041122D 687C7E4100 push $00417e7c
00411232 B905000000 mov ecx,$00000005
00411237 BA01000000 mov edx,$00000001
0041123C A17C7E4100 mov eax,[$00417e7c]
00411241 E8C654FFFF call #UStrCopy //call the unicode version of copy
Project91.dpr.17: Writeln(u);
00411246 A1EC2C4100 mov eax,[$00412cec]
Copy is a "compiler magic" routine, it is handled intrinsically by the compiler depending on what parameters you pass it (ANSI string, string, or dynamic array). You can just use Copy; it will work correctly with ANSI strings.
I have the same problem, see this code:
const
TheStart=13;
TheEnd=69;
type
TMyFileField: Array[TheStart..TheEnd] of Char; // This is a simplification of a field type on a file
procedure WriteWideStringToArrayOfChars(TheLiteral:WideString);
var
MyFileField:TMyFileField; // This is a simplification, it is really a Field inside a File
MyIndex:Integer;
begin
for MyIndex:=1 to Max(Length(TheLiteral),1+TheEnd-TheStart)
do begin // Will copy as many charactes as possible from TheLiteral to MyFileField
MyFileField[MyIndex]:=Copy(TheLiteral,MyIndex,1)[1]; // This gives Copile Error: Incompatible types 'Char' and 'WideChar'
end;
end;
The problem is that the WideString must be saved onto am Array of Char inside a file. So mix types must be done... and so, some loose of Unicode chars will occur, no way to avoid it.
The wanted: The compiler can compile it.
Solution1: Convert WideString to String prior to call Copy, or inside Copy.
Solution2: Convert WideChar to Char prior to assing.
Here are both solutions (remember some unicode chars could get lost)...
Solution1:
MyFileField[MyIndex]:=Copy(UTF8Encode(TheLiteral),MyIndex,1)[1]; // Note: Unicode chars will not get lost, but converted, so beware of accent vocals, etc...
or
MyFileField[MyIndex]:=Copy(String(TheLiteral),MyIndex,1)[1]; // Note: Unicode chars will get lost, they will be converted to '?'
Solution2:
MyFileField[MyIndex]:=Char(Copy(TheLiteral,MyIndex,1)[1]); // Note: Unicode chars will get lost, they will be converted to '?'
If anyone knows anthing better i would be glad to know.
I personally use Copy(String(Literal),Start,NumberOfChars) since normal accent letters are conserved and more important, length...
Example: Length(String('BlaBlaBlá')) -> 9
Example: Length(UTF8Encode('BlaBlaBlá')) -> More than 9 since the last 'á' is converted to multiple chars, etc...
Hope this can help someone!
I did my own function. It could be useful and get right results instead of Copy(string(ansistr), i, l) on the Linux platform:
function AnsiCopy(const s: ansistring; StartIndex, Lenght: integer): ansistring;
begin
SetLength(Result, Lenght);
Move(s[StartIndex], Result[1], Lenght);
end;