No Overload Version of Write - delphi

I am shareing a project with someone codeing in xe6, I am using xe2 version 16. I get this error
There is no overloaded version of 'write' that can be called with these arguments
at this code.
{$IF CompilerVersion >= 19}
// Modified code for Delphi XE5 & later
tcpConnection.IOHandler.Write(TheMsg, IndyTextEncoding.Default );
{$ELSE}
// Original XE2 code
tcpConnection.IOHandler.Write(TheMsg, TIdTextEncoding.Default);
{$IFEND}
I have also added idGlobal in the uses. Any other reason this would give error?

IndyTextEncoding() is a series of overloaded functions, all taking an input parameter, all returning an IIdTextEncoding interface:
function IndyTextEncoding(AType: IdTextEncodingType): IIdTextEncoding; overload;
function IndyTextEncoding(ACodepage: Word): IIdTextEncoding; overload;
function IndyTextEncoding(const ACharset: String): IIdTextEncoding; overload;
{$IFDEF DOTNET}
function IndyTextEncoding(AEncoding: System.Text.Encoding): IIdTextEncoding; overload;
{$ENDIF}
{$IFDEF HAS_TEncoding}
function IndyTextEncoding(AEncoding: TEncoding): IIdTextEncoding; overload;
{$ENDIF}
IIdTextEncoding does not have a Default property (or any other encoding type properties). IIdTextEncoding was introduced to break away from Embarcadero's TEncoding class and simplify Indy's memory management of codepage/charset handlers.
In earlier versions of Indy, the TIdTextEncoding.Default property represented the OS default encoding. The correct way to get the OS default encoding in the latest version of Indy is to use the IndyTextEncoding_OSDefault() function:
function IndyTextEncoding_OSDefault: IIdTextEncoding;
Or the IndyTextEncoding(IdTextEncodingType) function with encOSDefault as the input parameter.
{$IF CompilerVersion >= 19}
// Modified code for Delphi XE5 & later
tcpConnection.IOHandler.Write(TheMsg, IndyTextEncoding_OSDefault);
// or: tcpConnection.IOHandler.Write(TheMsg, IndyTextEncoding(encOSDefault));
{$ELSE}
// Original XE2 code
tcpConnection.IOHandler.Write(TheMsg, TIdTextEncoding.Default);
{$IFEND}
The IndyTextEncoding_Default() function, by comparison, returns an IIdTextEncoding that represents Indy's default encoding that is specified in the IdGlobal.GIdDefaultTextEncoding variable (7bit ASCII by default).
If you want to use something that works in both Indy versions without using an {$IFDEF}, use the deprecated IndyOSDefaultEncoding() function:
function IndyOSDefaultEncoding{$IFNDEF DOTNET}(const AOwnedByIndy: Boolean = True){$ENDIF}: IIdTextEncoding;
tcpConnection.IOHandler.Write(TheMsg, IndyOSDefaultEncoding);
That being said, note that the OS default encoding varies from one machine to another, and from one platform to another. You should not be using it as the byte encoding in communication protocols. Use a standardized encoding instead, such as UTF-8.
Lastly, if you are going to use {$IFDEF} or {$IF}, Indy has its own {$DEFINE} statements in IdCompilerDefines.inc, and global version constants in IdGlobal.pas, that you can use to detect Indy versions. You might consider using those instead of the CompilerVersion constant. If you were to ever upgrade Indy in XE2, for example, then your code would break. You should be checking for Indy versions, not Compiler/RTL versions, eg:
// Indy version constants were added in 10.5.9.4850
// TIdTextEncoding was replaced with IIdTextEncoding in 10.6.0.0
{$IF (gsIdVersionMajor > 10) OR ((gsIdVersionMajor = 10) AND (gsIdVersionMinor >= 6))}
tcpConnection.IOHandler.Write(TheMsg, IndyTextEncoding_OSDefault);
{$ELSE}
tcpConnection.IOHandler.Write(TheMsg, TIdTextEncoding.Default);
{$IFEND}

Related

How to conditionally compile a newer Indy feature?

I've already found this answer on how to check the Indy version at run-time, and there are multiple different ways. However I'm looking how to use conditionals to check the Indy version at compile-time. There's a feature in newer versions of Indy, and I want my open-source project to use this feature if it's available. But I need to conditionally compile it.
I've found IdVers.inc, but this file only contains constants - no version conditionals.
More specifically, the TIdHTTP has a property HTTPOptions which has a new choice hoWantProtocolErrorContent. If this is available, I'd like to use it.
How can I conditionally use this option if it's available?
I think you can get the result you're wanting to achieve using the
{$if declared ...
construct. There is an example of its usage in SysInit.Pas in the rtl:
function GetTlsSize: Integer;
{$IF defined(POSIX) and defined(CPUX86) and (not defined(EXTERNALLINKER))}
asm
// Use assembler code not to include PIC base gain
MOV EAX, offset TlsLast
end;
{$ELSE}
begin
Result := NativeInt(#TlsLast);
{$IF DECLARED(TlsStart)}
Result := Result - NativeInt(#TlsStart);
{$ENDIF}
[...]
As well as the article I mentioned in a comment, $If Declared,
there is also this in the D2009 online help.
$if declared works with methods of classes, e.g.
procedure TMyClass.DoSomething;
begin
{$if declared(TMyClass.Added)} // Added being a procedure of TMyClass
Added;
{$endif}
end;

Generics in Delphi and returning a reference to tlist<class>

I still use Delphi XE4 (newest compiler I use of multiple Delphi compilers) and need a specific workaround for the fact they completely hid FClients in TBasicAction in this version. I connect/disconnect clients runtime while setting enabled/disabled (to avoid flicker with ~100+ actions and ui elements) thus this workaround for XE4:
Here's my naive attempt and simply returning the field.
TmscBasicActionCrack = class(TBasicAction)
end;
{$IFDEF mymsDELPHIXE4}
TmscBasicActionHelper = class helper for TBasicAction
public
function Helper_Get_Private_FClients: TList<System.Classes.TBasicActionLink>;
end;
{$ENDIF}
{$IFDEF mymsDELPHIXE4}
//------------------------------------------------------------------------------
function TmscBasicActionHelper.Helper_Get_Private_FClients: TList<System.Classes.TBasicActionLink>;
begin
Result := Self.FClients;
end;
{$ENDIF}
However, I get error
E2003 Undeclared identifier: TList<>
I must admit I never go around to using generics with Delphi since I initially heard of stability problems + I need to maintain compability with Lazarus/FreePascal.
I am aware the most recent versions Delphi has altered class helpers again, but I am for now mostly interested in getting this to work with Delphi XE4
The error is indicating that the TList<T> type is unknown to the compiler. To use it you must include System.Generics.Collections in your uses clause.

Delphi 2007->10.1 Berlin Port: Solving E2251 Ambiguious overloaded call to StrLen

Background: Porting my code to Delphi 10.1 Berlin and working through the third party libraries. Some are no longer available so I will try to fix the code...
The following code (passing params from one instance of a program to another) raises E2251 Ambiguious overloaded call to StrLen. I understand why, I just don't know the best way to resolve it.
type
PInstInfo = ^TInstInfo;
TInstInfo = packed record
FirstInstanceWnd:HWND;
ParamCount:Integer;
Params:Array[0..MAX_PARAMS-1, 0..MAX_PARAM_SIZE] of Char;
end;
// Memory is filled with:
lpInfo^.ParamCount:=ParamCount;
if lpInfo^.ParamCount>MAX_PARAMS then
lpInfo^.ParamCount:=MAX_PARAMS;
for i:=0 to lpInfo^.ParamCount-1 do
begin
tempStr:=ParamStr(i+1);
if length(tempStr)>MAX_PARAM_SIZE then
setLength(tempStr,MAX_PARAM_SIZE);
StrCopy(#(lpInfo^.Params[i,0]),PChar(tempStr));
end;
// and notify the first instance
PostMessage(lpInfo^.FirstInstanceWnd, MSG_2ND_INSTANCE, 0, 0);
// And read using:
if lpInfo <> nil then
try
// get Parameters
params:=TStringList.Create;
try
for i:=0 to lpInfo^.ParamCount-1 do
begin
SetString(tempStr,
PChar(#(lpInfo^.Params[i,0])),
StrLen(#(lpInfo^.Params[i,0]))); <--- E2251 Ambiguious overloaded call to StrLen
params.Add(tempStr);
end;
InstanceStarted(params);
finally
params.Free;
end;
Thanks
By default, the # address operator produces an untyped pointer. There are two overloaded versions of StrLen(), one that takes a PAnsiChar and one that takes a PWideChar. An untyped pointer can be passed to both overloads, thus the ambiguity. The PWideChar overload did not exist in Delphi 2007, which is why the code compiled before.
To fix the code, you will have to either:
use the {$TYPEDADDRESS ON} or {$T+} compiler directive to enable Type-checked pointers so taking the address of a Char variable using the # operator will produce a typed PChar pointer instead of an untyped pointer.
{$TYPEDADDRESS ON}
SetString(tempStr,
#(lpInfo^.Params[i,0]),
StrLen(#(lpInfo^.Params[i,0])));
use the same type-cast you use in the 2nd parameter of SetString():
SetString(tempStr,
PChar(#(lpInfo^.Params[i,0])),
StrLen(PChar(#(lpInfo^.Params[i,0]))));
Get rid of the calls to SetString() and StrLen(), since a null-terminated character pointer can be assigned directly to a String variable:
tempStr := PChar(#(lpInfo^.Params[i,0]));
{$TYPEDADDRESS ON}
tempStr := #(lpInfo^.Params[i,0]);
With that said, do be aware that the Char data type changed from Ansi to Unicode in D2009, so this code will only work when sending the parameters to Unicode versions of your app, not to Ansi versions. If you need to continue supporting older versions of your app, you should define a different window message for passing Unicode parameters, and then have your Unicode app support both messages for receiving, and analyze the target HWND to decide which message to use for sending.

NPAPI plugin framework Error

I am trying to use NPAPI Framework from Yury Sidorov by following this answer:
How to embed Delphi VCL form into HTML page using NPAPI
but I get an error with NPPlugin.pas. I am using delphi XE7 and here what i did by following Krom Stern instructions
procedure DefDebugOut(const Msg: string);
begin
OutputDebugStringA(PAnsiChar(Msg + #13#10)); // Changed From Pchar To PAnsiChar
end;
but still getting error with compiler with this message
[dcc32 Error] NPPlugin.pas(2215): E2010 Incompatible types: 'PWideChar' and 'PAnsiChar'
Error raised on this procedure
procedure TPlugin.SetException(const msg: string);
var
s: String;
begin
try
{$ifopt D+} NPP_DebugOut('Exception: ' + msg); {$endif}
s:=UTF8Encode(msg);
NPN_SetException(m_pScriptableObject, PAnsiChar(s));
except
{ prevent any exception from leaking out of DLL }
end;
end;
here is the procedure NPN_SetException
procedure NPN_SetException(npobj: PNPObject; msg: PNPUTF8);
begin
NavigatorFuncs.SetException(npobj, msg);
end;
I'll start with a piece by piece breakdown of what we can see. Bear in mind that we don't have NPPlugin.pas at hand and have to infer its contents from the information in the question. All the same, I think it's possible for us to do that accurately.
s := UTF8Encode(msg);
Here s is of type string. That's an alias for UnicodeString, encoded as UTF-16. So you convert from UTF-16 to UTF-8 and then back to UTF16.
You need it like this:
NPN_SetException(m_pScriptableObject, PAnsiChar(UTF8Encode(msg)));
Alternatively, if you need a variable to hold UTF-8 encoded text, declare it to be UTF8String which is AnsiString(65001). If you changed the type of s to be UTF8String then the code in the question would be correct. Although somewhat more verbose than it would need to be.
Another problem is here:
OutputDebugStringA(PAnsiChar(Msg + #13#10));
Your cast doesn't make Msg actually be 8 bit encoded. However, you don't want to use the ANSI version of the function. You need this:
OutputDebugString(PChar(Msg + sLineBreak));
Your exception handler is misguided. It is the DLL's job not to leak exceptions. If you attempt to catch and suppress them all you will simply mask errors in your own code. You need to remove that exception handler and check for errors by following the instructions given by the library documentation.
Now to the bigger picture. None of the above explains your reported error. The only sound explanation for that is that your declaration for NPN_SetException accepts wide text. In which case you could make the code compile simply by writing this:
NPN_SetException(m_pScriptableObject, PChar(msg));
Of course, that makes the appearance of UTF-8 somewhat inexplicable. In fact the library Mozilla does accept 8 bit text, UTF-8 encoded. So why would NPN_SetException expect to be passed UTF-16 text? Well it doesn't. The explanation is that you have declared NPN_SetException incorrectly. So, just to be clear, whilst PChar(msg) would make your code compile, it would not resolve your problem. You would be left with code that failed at runtime.
So, how did this happen? You've taken a working piece of code that used PChar aliased to PAnsiChar onto a Delphi with PChar aliased to PWideChar and not translated correctly. Even when you get your code to compile, it will not work correctly. You started with code like this:
function NPN_SetException(..., Msg: PChar): ...;
On older Delphi versions where PChar was PAnsiChar then this was correct. You are compiling this now on XE7 where PChar is PWideChar and so this is not correct. It needs to be:
function NPN_SetException(..., Msg: PAnsiChar): ...;
Then the calling code can be:
NPN_SetException(m_pScriptableObject, PAnsiChar(UTF8Encode(msg)));
My advice is that you:
Step back and revisit the handling of Unicode in Delphi.
Go back to you original code and change all the Mozilla interface code that uses PChar to PAnsiChar.
Whenever you need to provide PAnsiChar do it with PAnsiChar(UTF8Encode(str)).
This NPAPI code was clearly designed for older versions of Delphi before the switch to Unicode in Delphi 2009. The default String/(P)Char types are no longer aliases for AnsiString/(P)AnsiChar, they are now aliases for UnicodeString/(P)WideChar. A UnicodeString cannot be casted to a PAnsiChar, just like an AnsiString could never be casted to a PWideChar.
In DefDebugOut(), the simplest fix is to change PAnsiChar to PChar and change OutputDebugStringA() to OutputDebugString():
procedure DefDebugOut(const Msg: string);
begin
OutputDebugString(PChar(Msg + #13#10));
end;
This is compatible with all Delphi versions (the code should have been doing this from the beginning - there was no reason to call OutputDebugStringA() directly). PChar and OutputDebugString() map to PAnsiChar and OutputDebugStringA() in Delphi 2007 and earlier, and to PWideChar and OutputDebugStringW() in Delphi 2009 and later. So everything matches.
In TPlugin.SetException(), UTF8Encode() returns a UTF8String in all versions of Delphi. However, prior to Delphi 2009, UTF8String was just an alias for AnsiString itself, but in Delphi 2009 it was changed to a true UTF-8 string type with full RTL support (it still has an AnsiString base, so it can still be casted to PAnsiChar). When a UTF8String is assigned to a UnicodeString, the compiler performs an implicit data conversion from UTF-8 to UTF-16. And as stated above, UnicodeString cannot be casted to PAnsiChar. So you need to change the s variable from String to UTF8String for all Delphi versions:
procedure TPlugin.SetException(const msg: string);
var
s: UTF8String;
begin
try
{$ifopt D+} NPP_DebugOut('Exception: ' + msg); {$endif}
s:=UTF8Encode(msg);
{
UTF8Encode() is deprecated in Delphi 2009+.
In those versions, you can use this instead:
s := UTF8String(msg);
}
NPN_SetException(m_pScriptableObject, PAnsiChar(s));
except
{ prevent any exception from leaking out of DLL }
end;
end;
With that said, if you are still getting the same error on the NPN_SetException() call, then it means the second parameter of NPN_SetException() is declared as PChar. It needs to be declared as PAnsiChar instead.

How to work with 0-based strings in a backwards compatible way since Delphi XE5?

I'm trying to convert my current Delphi 7 Win32 code to Delphi XE5 Android with minimal changes, so that my project can be cross-compiled to Win32 from a range of Delphi versions and Android from XE5.
Starting from XE5 there are breaking changes in language aimed at future. One of such changes is zero-based strings.
In older versions with 1-based strings the following code was correct:
function StripColor(aText: string): string;
begin
for I := 1 to Length(aText) do
but now this is obviously not right. Suggested solution is to use:
for I := Low(aText) to High(aText) do
This way XE5 Win32 handles 1-based strings and XE5 Android handles 0-based strings right. However there's a problem - previous Delphi versions (e.g. XE2) output an error on such code:
E2198 Low cannot be applied to a long string
E2198 High cannot be applied to a long string
I have quite a lot of string manipulation code. My question is - how to modify and keep above code to be compileable in Delphi 7 Win32 and Delphi XE5 Android?
P.S. I know I can still disable ZEROBASEDSTRINGS define in XE5, but that is undesired solution since in XE6 this define will probably be gone and all strings will be forced to be 0-based.
If you want to support versions that use one based strings then don't define ZEROBASEDSTRINGS. That's the purpose of that conditional.
There's no indication that I am aware of that the conditional will be removed any time soon. It was introduced in XE3 and has survived two subsequent releases. If Embarcadero remove it, none of their Win32 customers will not upgrade and they will go bust. Embarcadero have a track record of maintaining compatibility. You can still use TP objects and short strings. Expect this conditional to live as long as the desktop compiler does.
In fact, all the evidence points towards the mobile compilers retaining support for one based string indexing. All the utility string functions like Pos use one based indices, and will continue to do so. If Embarcadero really are going to remove support for one based string indexing, they'll be removing Pos too. I don't believe that is likely any time soon.
Taking your question at face value though it is trivial to write functions that return the low and high indices of a string. You just use an IFDEF on the compiler version.
function StrLow(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}low(S){$ELSE}1{$ENDIF}
end;
function StrHigh(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}high(S){$ELSE}Length(S){$ENDIF}
end;
Update
As Remy points out, the above code is no good. That's because ZEROBASEDSTRINGS is local and what counts is its state at the place where such functions would be used. In fact it's just not possible to implement these functions in a meaningful way.
So, I believe that for code that needs to be compiled using legacy compilers, as well as the mobile compilers, you have little choice but to disable. ZEROBASEDSTRINGS.
All of the RTL's pre-existing functions (Pos(), Copy(), etc) are still (and will remain) 1-based for backwards compatibility. 0-based functionality is exposed via the new TStringHelper record helper that was introduced in XE3, which older code will not be using so nothing breaks.
The only real gotchas you have to watch out for are things like hard-coded indexes, such as your loop example. Unfortunately, without access to Low/High(String) in older Delphi versions, the only way to write such code in a portable way is to use IFDEFs, eg:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := {$IFDEF XE3_OR_ABOVE}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3_OR_ABOVE}High(AText){$ELSE}Length(aText){$ENDIF} do
DoSomething(aText, I);
end;
Or:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := 1 to Length(aText) do
begin
DoSomething(aText, I{$IFDEF XE3_OR_ABOVE}-(1-Low(AText)){$ENDIF});
end;
end;
Conditional Expressions were introduced in Delphi 6, so if you don't need to support version earlier than Delphi 7, and don't need to support other compilers like FreePascal, then you can omit the {$IFDEF CONDITIONALEXPRESSIONS} check.
This is rather a sum up of the two answers:
As pointed out by Remy Lebeau, ZEROBASEDSTRINGS is a per-block conditional. That means that the following code will not work as expected:
const
s: string = 'test';
function StringLow(const aString: string): Integer; inline; // <-- inline does not help
begin
{$IF CompilerVersion >= 24}
Result := Low(aString); // Delphi XE3 and up can use Low(s)
{$ELSE}
Result := 1; // Delphi XE2 and below can't use Low(s), but don't have ZEROBASEDSTRINGS either
{$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS OFF}
Memo1.Lines.Add(Low(s).ToString); // 1
Memo1.Lines.Add(StringLow(s).ToString); // 1
{$ZEROBASEDSTRINGS ON}
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS ON}
Memo1.Lines.Add(Low(s).ToString); // 0
Memo1.Lines.Add(StringLow(s).ToString); // 1 <-- Expected to be 0
{$ZEROBASEDSTRINGS OFF}
end;
There are 2 possible solutions:
A. Every time there's string items access or iteration place an IFDEF around it, which is indeed a lot of clutter for the code, but will work properly irregardless of ZEROBASEDSTRINGS setting around it:
for I := {$IFDEF XE3UP}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3UP}High(aText){$ELSE}Length(aText){$ENDIF} do
B. Since the ZEROBASEDSTRINGS conditional is per-block it never gets spoiled by 3rd party code and if you don't change it in your code you are fine (above StringLow will work fine as long as the caller code has the same ZEROBASEDSTRINGS setting). Note that if target is mobile, you should not apply ZEROBASEDSTRINGS OFF globally in your code since RTL functions (e.g. TStringHelper) will return 0-based results because mobile RTL is compiled with ZEROBASEDSTRINGS ON.
On a side note - One might suggest to write an overloaded versions of Low/High for older versions of Delphi, but then Low(other type) (where type is array of something) stops working. It looks like since Low/High are not usual functions then can not be overloaded that simply.
TL;DR - Use custom StringLow and don't change ZEROBASEDSTRINGS in your code.
How about defining this as an inc file? Put additional ifdefs depending on what Delphi versions you want to support. Since this code is only for versions before the ZBS to make it possible to use Low and High on strings it will not run into the problem with the ZEROBASEDSTRINGS define only being local.
You can include this code locally (as nested routines) then which reduces the risk of colliding with System.Low and System.High.
{$IF CompilerVersion < 24}
function Low(const s: string): Integer; inline;
begin
Result := 1;
end;
function High(const s: string): Integer; inline;
begin
Result := Length(s);
end;
{$IFEND}
As LU RD told above Low and High functions for string were only introduced in XE3. So how can you use functions in earlier Delphi verions, that are missed? Just the same way as always do - if the function is missed - go and write it!
You should only activate those compatibility additions for Delphi beyond XE3 version, using conditional compilation. One way is described in other answers, using >= comparison. Another usual way would be reusing jedi.inc definitions file.
Then for earlier Delphi versions you would add your own implementations of those, like
function Low(const S: AnsiString): integer; overload;
Pay attention to the overload specifier - it is what would make the trick possible, don't forget it!
You would have to write 4 functions for Delphi 7 till 2007, covering combinations of Low/High fn name and AnsiString/WideString data type.
For Delphi 2009 till XE2 you would have to add two more functions for UnicodeString datatype.
And also mark those function inline for those Delphi versions, that support it (this is where jedi.inc comes handy again.
Hopefully you don't need supprot for UTF8String, but if you do - you know what to do about it now (if compiler would manage to tell it from AnsiString when overloading...)

Resources