I'm try to port some code from D2007 to DXE2.
This simplified code compiles fine in D2007. In DXE2 it show this error:
[DCC Warning] Unit1.pas(10): W1050 WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit.
[DCC Error] Unit1.pas(37): E2010 Incompatible types: 'AnsiChar' and 'Char'
Propably a unicode issue.
Can someone tell me why this happen and how I should correct it ?
Regards
The code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TSetOfChar = Set of Char; // Line 10
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FCharacterSet: TSetOfChar;
public
property CharacterSet: TSetOfChar read FCharacterSet write FCharacterSet;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
CharacterSet: TSetOfChar;
j: Integer;
s: String;
begin
CharacterSet := [];
s := 'I''m just testing åäö';
for j := 1 to Length(s) do
Include(CharacterSet, s[j]); // <- Line 37
end;
end.
EDIT: Note that I am using Delphi 2007 that have no generics. I want code that still works in D2007 because there is a lot of code to port to Unicode. This is slow process.
When everything is ported, verified it works with XE2 then we can use XE2 things like generics. In the meantime we maintain the D2007 as usual and we want to avoid making a XE2 branch in the revision control system.
This is standard Unicode Delphi migration fodder. Required reading is Marco Cantù's paper
White Paper: Delphi and Unicode. If you haven't read that, do so. If you haven't read it recently, do so again.
The reason that set of char produces a warning is that the base type for sets cannot have more than 256 values. But since char is now UTF-16, that's a lot more than 256. All this means that your code can never work with sets and UTF-16 characters.
You could use set of AnsiChar and AnsiString. But if you want this code to work on Unicode data then you'll need to use something other than a set. For example TList<char> could be used.
var
CharacterSet: TList<char>;
s: string;
c: char;
.....
CharacterSet := TList<char>.Create;
s := 'I''m just testing åäö';
for c in s do
if not CharacterSet.Contains(c) then
CharacterSet.Add(c);
I would not recommend that for production. Its performance characteristics will be terrible. A hash based dictionary would do better. Best of all would be a dedicated large set class.
One final point. Characters are not the same as code points in UTF-16 which is a variable length encoding. The code in question and this answer make no allowance for that.
Related
If you make a new multi-device application project, set Project > Option > Compiling > Optimization : True, and then copy the code below to unit1.pas:
unit Unit1;
interface
uses
System.SysUtils,
FMX.Forms,
FMX.StdCtrls,
System.Classes,
FMX.Types,
FMX.Controls,
FMX.Controls.Presentation;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FKey: integer;
public
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Button1Click(Sender: TObject);
begin
FKey := 2;
var LCompareKey: integer := 2;
AtomicCmpExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
TThread.queue(nil,
procedure
begin
if LCompareKey <> FKey
then raise Exception.Create('Error 3');
end);
end;
end.
Why does this code crash on Win32 on if FKey <> LCompareKey then raise Exception.Create('Error 2');?
I'm using Delphi 10.4 Sydney Update 3. I didn't yet try in Delphi 11 Alexandria, so I don't know if it's working in that version.
Is there any workaround except deactivating the optimization?
Another question - is it really safe to activate the optimization?
Yes, codegen for AtomicCmpExchange is broken on Win32 compiler when optimization is turned on.
Problem happens in combination with anonymous method variable capture that happens in TThread.Queue call. Without variable capture, assembly code for AtomicCmpExchange is properly generated.
Workaround for the issue is using TInterlocked.CompareExchange.
...
var LCompareKey: integer := 2;
TInterlocked.CompareExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
...
TInterlocked.CompareExchange function still uses AtomicCmpExchange, but at place of call it works with captured variables through parameters instead of directly and generated code is correct in those situations.
class function TInterlocked.CompareExchange(var Target: Integer; Value, Comparand: Integer): Integer;
begin
Result := AtomicCmpExchange(Target, Value, Comparand);
end;
Another, less optimal solution would be turning off optimization around broken method Button1Click with {$O-} compiler directive and then turning it back on with {$O+}
Since AtomicCmpExchange is Delphi intrinsic function, its code is directly generated by compiler when it is called and bad codegen only affects that procedure, not general code - in other words anonymous method capture is working correctly in other code (unless there are other, bugs in compiler, unrelated to this particular one).
In other places in RTL where AtomicCmpExchange is used, there is no code where variable capture is involved, so RTL, VCL and FMX code is not affected by this issue and optimization can be turned on in application.
Note: There may be other optimization bugs in compiler that we don't know about.
Part of the code in which the error:
function ReadBytes(maxLen: integer): TBytes; virtual;
...
uses sysutils, windows, SysConst, commutil;
...
function TBasicBufferedStream.ReadBytes(maxLen: Integer): TBytes;
begin
if maxLen > 0 then
begin
if maxLen <= (fBufFill - fInBufPos) then
begin
SetLength(result, maxLen);
Move(fBuf[fInBufPos], result[0], maxLen);
inc(fInBufPos, maxLen);
end
else
begin
result := Peek(maxLen);
Skip(maxLen);
end;
end
else
result := nil;
end;
In Delphi 10.2 it doesn't compile, in Delphi 7 it compiled!
What is my mistake that I do not understand?
This is just an educated guess as the provided code is sparse. The small hints the provided code gave:
uses provided in the code sample after the declaration contain
System.SysUtils, the unit where TBytes is defined
The provided uses are after the declaration, so TBytes there must
have a different source than System.SysUtils
Therefore it is likely that TBytes is not actually referring to the same type in declaration and implementation part. This can be visualized for example by hovering the mouse over a type. The tool tip will tell you what the exact type is the compiler is referring to.
I can for example reproduce your problem with two small units. TBytes is declared in System.SysUtils, but I declare another one - like it is defined in Delphi 2009 (see below) in Unit3:
unit Unit3;
interface
type
TBytes = array of Byte;
implementation
end.
When I now create a unit like the following, I'm mixing up the usage of TBytes from two different Units, that aren't compatible:
unit Unit2;
interface
uses
Unit3;
function ReadBytes(maxLen: integer): TBytes;
implementation
uses
System.SysUtils;
function ReadBytes(maxLen: integer): TBytes;
begin
//
end;
end.
The types that the tool tip will show are type Unit3.TBytes: array of Byte and type System.SysUtils.TBytes: System.Array<System.Byte>.
So in fact the signature of my function in the declaration differs from the signature in the implementation.
This can be resolved by
Inspecting if the used units are actually correct and needed, can you
get rid of the one causing ambiguity?
If not possible to solve it with the first point, it is possible
to refer explicitly to which type is meant by prefixing with the
containing unit:
function ReadBytes(maxLen: integer): Unit3.TBytes;
I looked at the history of System.SysUtils.TBytes retroactively, couldn't find it for Delphi 7, but in Delphi 2009 the definition of TBytes was following: TBytes = array of Byte;
I have changed my code example with that in mind and rephrased part of the answer.
I'm becoming crazy.
I'm a beginner and I want to make my first DLL.
I've followed this guide:
http://www.tutorialspoint.com/dll/dll_delphi_example.htm
I want to set a text information about a program version and read it when I want, so display it to the user through main application. This is just an example to keep confidence with DLLs, I already know that there are many other way to achieve this.
Now I'm trying to read the variable "versione" from a DLL like this:
library Clientdll;
uses SysUtils, Classes, Dialogs;
{$R *.res}
function Versione(var messaggio, versione: String):string; export; stdcall;
begin
versione:='Nessun dato ricavato. Valore di chiamata alla DLL errato!';
if messaggio='chiama' then versione:='v4.0.0 build 31';
end;
exports versione;
begin
end.
In the main application I've write this:
[...]
implementation
uses unit2;
{$R *.dfm}
function Versione(var messaggio, versione:string):string; stdcall; external 'Clientdll.dll'
[...]
Now I said 'OK, I've just to call the DLL and that's all...'. So:
procedure TForm1.Button1Click(Sender: TObject);
var x, y:string;
begin
x:='chiama';
Versione(x,y);
showmessage(y);
end;
I can read v4.0.0 build 31 in the dialog box, but when I press OK I've receive this error:
"Invalid Pointer Operation".
Any ideas?
I've try to google it, but my english is poor and some answers are difficult to understand, also with translation tools!
Don't use String as the parameter type. This is clearly explained in the comment that the IDE generates when you use File->New->Other->Delphi Projects->DLL Wizard to create a new DLL:
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
In addition, using Delphi strings means that your DLL functions are not callable from other languages such as C.
You should also expect the calling application to provide you with the memory in which to place the results (and a length parameter that tells you how large that memory buffer is, too).
Here's a minimal (quite useless) example of a Delphi dll with a single function, along with a test application that calls it. (The DLL is, as I said, quite meaningless. Any actual DLL should be designed to put the functional code in its own unit and not in the project file.)
The sample DLL source:
library SimpleTest;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
SysUtils,
Classes;
{$R *.res}
// Parameters:
// arg: Argument that indicates whether this is a test or
// something else, so we know which value to return
// Buffer: The space in which to place the result
// Len: The length of the buffer provided
function TestDLL(const arg: PChar; const Buffer: PChar;
const Len: Integer): Boolean; stdcall;
begin
// Make sure we use the Len parameter, so we don't overflow
// the memory we were given. StrLCopy will copy a maximum of
// Len characters, even if the length of the string provided
// as the 'source' parameter is longer.
if arg = 'Test' then
StrLCopy(Buffer, 'Test result', Len)
else
StrLCopy(Buffer, 'Non-test result', Len);
Result := True;
end;
exports
TestDll;
begin
end.
The form for the test application that calls it:
unit DLLTestForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
function TestDLL(const arg: PChar; const Buffer: PChar; const Len: Integer): Boolean; stdcall;
external 'SimpleTest.dll';
procedure TForm4.Button1Click(Sender: TObject);
var
Parm1: String;
Parm2: String;
BuffLen: Integer;
begin
Parm1 := 'Test';
// Length of buffer (including null terminator) for DLL call
// Chosen arbitrarily - I know the DLL won't return more than 15 + the
// null. I'm pretending I don't, though, and allowing extra space. The
// DLL won't return more than 30 characters, even if it has more to say,
// because it uses StrLCopy to limit the result to Len characters.
BuffLen := 30;
// Allocate space for return value
SetLength(Parm2, BuffLen);
// Call the DLL with `Test` arg
if TestDLL(PChar(Parm1), PChar(Parm2), BuffLen) then
ShowMessage(Parm2);
// Call the DLL with a different parameter value
Parm1 := 'Other';
if TestDLL(PChar(Parm1), PChar(Parm2), BuffLen) then
ShowMessage(Parm2);
end;
end.
One of my coworkers show me a code written in Delphi-XE XE Version 15.0.3953.35171, which I believe it should raise an access violation. The code is bellow:
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
function test:TstringList;
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
var aStrList : TStringList;
begin
aStrList := TStringList.Create;
test;
FreeAndNil(aStrList);
end;
function TForm3.test: TstringList;
var i:Integer;
begin
for i:=0 to 1000 do
Result.Add('aaa');//AV?
end;
end.
Inspecting aStrList and Result has the following results:
aStrList: TStringList $12FEDC : $42138A
Result: TStringList $12FEC4 : $B01B90
I do not understand why it is working. Result.Add should raise an access violation
LE: It seems that is working only on Debug Build Configuration.
The Result variable in that function has not been initialized and could hold any value. Now, the implementation detail means that, in some combinations of compiler options, your code happens to run with Result referring to a valid object. But that's really just a coincidence of those implementation details.
If this were C++ then that function would exhibit undefined behaviour. Although that term does not have a formal meaning in Delphi, it can be helpful to use that term in a Delphi setting to mean the same thing as in the context of C++.
I would also make the point that even if Result did not refer to a valid string list object, your code would not be guaranteed to raise an access violation. It could be that Result points to a block of memory that just happens to look enough like a string list for that code to execute successfully.
If you do things properly, you can predict the behaviour of your program. If your code is flawed and induces undefined behaviour, then your program's behaviour becomes unpredictable. It may work. It may fail. Or that code may execute fine, but then lead to a failure later in the program's execution. And so on.
It is possible to have a parameter in a routine which can be in the same time either an type, either an string? I know I can accomplish this by overloading a routine, I ask if it possible to do it in another way.
Assume that I have this type - TTest = (t1,t2,t3). I want to have a routine which accepts a parameter of type TTest, but in the same time to be a String, so I can call it myproc(t1) or myproc('blabla')
You should use an overloaded function.
You already have the perfect solution to the problem and there is no need to look for a different way to do this. You could try with a single function that receives a Variant, but then that function will also receive anything which means that the following would also be legal:
myproc(0.5);
myproc(intf);
myproc(-666);
Using an overload allows you to maintain compile time type safety and there is absolutely no loss of generality in using an overload.
Even this can be easily accomplished with overloaded functions, considering it's a good exercise, based on David Hefferman's and Sertac Akyuz answers I made a small example to test both solutions. It is not perfect, it only shows both possibilities.
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
ttest = (t1,t2);
TForm4 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function my(aVar:Variant):String;
function MyUntype(const aVar):String;
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
{ TForm4 }
procedure TForm4.FormCreate(Sender: TObject);
var aTestTypeVar : ttest;
aString : String;
begin
my(t1);
my(t2);
my('ssss');
//with untyped params
aString := 'aaaa';
MyUntype(aString);
aTestTypeVar := t1;
aString := IntToStr(Ord(aTestTypeVar));
MyUntype(aString);//can not be a numeral due to delphi Help
end;
function TForm4.my(aVar: Variant): String;
begin
showmessage(VarToStr(aVar));//shows either the string, either position in type
end;
function TForm4.MyUntype(const aVar): String;
begin
//need to cast the parameter
try
ShowMessage(pchar(aVar))
except
showmessage(IntToStr(Ord(ttest(aVar))));
end;
end;
end.
Also I know that Variants are slow and must be used only needed.