Is it necessary to convert string to WideString in Delphi? - delphi

I found a Windows API function that performs "natural comparison" of strings. It is defined as follows:
int StrCmpLogicalW(
LPCWSTR psz1,
LPCWSTR psz2
);
To use it in Delphi, I declared it this way:
interface
function StrCmpLogicalW(psz1, psz2: PWideChar): integer; stdcall;
implementation
function StrCmpLogicalW; external 'shlwapi.dll' name 'StrCmpLogicalW';
Because it compares Unicode strings, I'm not sure how to call it when I want to compare ANSI strings. It seems to be enough to cast strings to WideString and then to PWideChar, however, I have no idea whether this approach is correct:
function AnsiNaturalCompareText(const S1, S2: string): integer;
begin
Result := StrCmpLogicalW(PWideChar(WideString(S1)), PWideChar(WideString(S2)));
end;
I know very little about character encoding so this is the reason of my question. Is this function OK or should I first convert both the compared strings somehow?

Keep in mind that casting a string to a WideString will convert it using default system codepage which may or may not be what you need. Typically, you'd want to use current user's locale.
From WCharFromChar in System.pas:
Result := MultiByteToWideChar(DefaultSystemCodePage, 0, CharSource, SrcBytes,
WCharDest, DestChars);
You can change DefaultSystemCodePage by calling SetMultiByteConversionCodePage.

The easier way to accomplish the task would be to declare your function as:
interface
function StrCmpLogicalW(const sz1, sz2: WideString): Integer; stdcall;
implementation
function StrCmpLogicalW; external 'shlwapi.dll' name 'StrCmpLogicalW';
Because a WideString variable is a pointer to a WideChar (in the same way an AnsiString variable is a pointer to an AnsiChar.)
And this way Delphi will automatically "up-convert" an AnsiString to a WideString for you.
Update
And since we're now in the world of UnicodeString, you would make it:
interface
function StrCmpLogicalW(const sz1, sz2: UnicodeString): Integer; stdcall;
implementation
function StrCmpLogicalW; external 'shlwapi.dll' name 'StrCmpLogicalW';
Because a UnicodeString variable is still a pointer to a \0\0 terminated string of WideChars. So if you call:
var
s1, s1: AnsiString;
begin
s1 := 'Hello';
s2 := 'world';
nCompare := StrCmpLogicalW(s1, s2);
end;
When you try to pass an AnsiString into a function that takes a UnicodeString, the compiler will automatically call MultiByteToWideChar for you in the generated code.
CompareString supports numeric sorting in Windows 7
Starting in Windows 7, Microsoft added SORT_DIGITSASNUMBERS to CompareString:
Windows 7: Treat digits as numbers during sorting, for example, sort "2" before "10".
None of this helps answer the actual question, which deals with when you have to convert or cast strings.

There might be an ANSI variant for your function to (I haven't checked). Most Wide API's are available as an ANSI version too, just change the W suffix to an A, and you're set. Windows does the back-and-forth conversion transparantly for you in that case.
PS: Here's an article describing the lack of StrCmpLogicalA : http://blogs.msdn.com/joshpoley/archive/2008/04/28/strcmplogicala.aspx

Use System.StringToOleStr, which is a handy wrapper around MultiByteToWideChar, see Gabr's answer:
function AnsiNaturalCompareText(const S1, S2: string): integer;
var
W1: PWideChar;
W2: PWideChar;
begin
W1 := StringToOleStr(S1);
W2 := StringToOleStr(S2);
Result := StrCmpLogicalW(W1, W2);
SysFreeString(W1);
SysFreeString(W2);
end;
But then, Ian Boyd's solution looks and is much nicer!

Related

Passing strings from Delphi to Labview DLL

I have a simple Labview dll that takes a PascalString then returns the pascal string with no changes. This is just testing what we can do. The header is as follows:
void __stdcall Read_String_In_Write_String_Out(PStr String_input,
PStr String_output);
the Delphi code is as follows:
var
hbar : thandle;
str, str2 : PChar;
StringFunction : function (TestString: PChar): PChar; stdcall;
begin
hbar := LoadLibrary('C:\Interface.dll');
if hbar >= 32 then begin
StringFunction := getprocaddress(hbar, 'Read_String_In_Write_String_Out');
str := 'test';
str2 := StringFunction(str);
end;
end;
When running the program i get an Access Violation. I have no issues when doing simple math functions using dll's, but when it comes to strings everything breaks.
Can anyone help?
You say that the DLL function is taking in a Pascal String. According to Labview's documentation:
Pascal String Pointer is a pointer to the string, preceded by a length byte.
Pascal-Style Strings (PStr)
A Pascal-style string (PStr) is a series of unsigned characters. The value of the first character indicates the length of the string. A PStr can have a range of 0 to 255 characters. The following code is the type definition for a Pascal string.
typedef uChar Str255[256], Str31[32], *StringPtr, **StringHandle;
typedef uChar *PStr;
This would be equivalent to Delphi's ShortString type (well, more accurately, PShortString, ie a pointer to a ShortString).
Based on the DLL function's declaration, its 2nd parameter is not a return value, it is an input parameter taking in a pointer by value. So your use of StringFunction is wrong on 2 counts:
Getting the output in the wrong place. StringFunction should be a procedure with 2 parameters. However, the function can't modify the pointer in the 2nd parameter, all it can do is read/write data from/to whatever memory the pointer is pointing at. So, for output, you will have to pre-allocate memory for the function to write to.
Passing around the wrong kind of string data. PChar is PWideChar in Delphi 2009+, but "Pascal strings" use AnsiChar instead. And your test data is not even a Pascal string, as it lacks the leading length byte.
So, try something more like this instead:
var
hbar : THandle;
str1, str2 : ShortString;
StringFunction : procedure (String_input, String_output: PShortString); stdcall;
begin
hbar := LoadLibrary('C:\Interface.dll');
if hbar >= 32 then
begin
StringFunction := GetProcAddress(hbar, 'Read_String_In_Write_String_Out');
str1 := 'test';
StringFunction(#str1, #str2);
end;
end;

Delphi XE Create structured array using MCR for use in Matlab Compiled dll

I'm using Delphi XE and the Matlab 2012B compiler on Windows 7.
I'm trying to write several wrapper functions so DLL files created with the Matlab 2012b Compiler can be more easily called from Delphi XE. I found that I should use the _proxy functions when using the MCR, which indeed allowed me to call several functions successfully. I can also pass strings to Matlab without problems by passing them as PAnsiChar.
I'm currently trying to create a StructArray with some field names.
As I've already successfully created numeric arrays and matrices, I'm pretty sure the first 2 parameters are OK. I expect the last one is causing the error, but I don't know how to solve this (yet). Looking at the Matlab help and example files I'm doing what should be done. Obviously I'm wrong...
I know that with Matlab r13 we had to pass the fieldnames as an array[0..n] of pAnsiChar instead of an array of pAnsiChar. I tried this here as well to no avail.
Can someone tell me if I have indeed made the correct function mapping to mxCreateStructArray(_730_proxy) and if I'm passing the parameters as expected?
type
mxArray = pointer;
// mxArray *mxCreateStructArray(mwSize ndim, const mwSize *dims, int nfields, const char **fieldnames);
function MCRdll_CreateStructArray(aDimCount: integer; aDims: pointer; aFieldCount: integer; aFields: PPAnsiChar): mxArray; cdecl; external 'mclmcrrt8_0.dll' name 'mxCreateStructArray_730_proxy';
function MCR_CreateStructArray(aFieldNames: TArray<string>): mxArray;
var
i: integer;
lstDims: array of integer;
lstNames: array of pAnsiChar;
begin
SetLength(lstNames, Length(aFieldNames));
for i := 0 to Length(aFieldNames) - 1 do
lstNames[i] := ToPAnsiChar(aFieldNames[i]); //Creates a new PAnsiChar with the content of aFieldNames[i]
SetLength(lstDims, 2);
lstDims[0] := 1;
lstDims[1] := Length(aFieldNames);
//This call raises an "External Exception" from Matlab.
Result := MCRdll_CreateStructArray(Length(lstDims), #lstDims, Length(lstNames), #lstNames);
end;
The MATLAB C API function is:
mxArray *mxCreateStructArray(mwSize ndim, const mwSize *dims,
int nfields, const char **fieldnames);
As I understand it, mwSize is by default the same as int. That translates to Integer in Delphi. The const char** parameter is the address of an array of const C strings. Translate that to Delphi and you have:
function MCRdll_CreateStructArray(ndim: Integer; dims: PInteger;
nFields: Integer; fieldnames: PPAnsiChar): mxArray; cdecl;
external 'mclmcrrt8_0.dll' name 'mxCreateStructArray_730_proxy';
Now, how to get the parameters. Well, assuming you want a vector, dims is an array of length 2, and ndim is that length. I'd declare that as a static array:
var
dims: array [0..1] of Integer;
As for the field names, those are variable length. So you need a dynamic array of PAnsiChar. That is:
var
fieldnames: array of PAnsiChar;
You also need to pass the vector length for your struct array to your function. That makes your function be something like this:
function MCR_CreateStructArray(len: Integer;
const aFieldNames: array of AnsiString): mxArray;
var
i: integer;
dims: array [0..1] of Integer;
fieldnames: array of PAnsiChar;
begin
if Length(aFieldNames)=0 then
begin
Result := nil;
exit;
end;
dims[0] := 1;
dims[1] := len;
SetLength(fieldnames, Length(aFieldNames));
for i := 0 to high(fieldnames) do
fieldnames[i] := PAnsiChar(aFieldNames[i]);
Result := MCRdll_CreateStructArray(Length(dims), #lstDims[0],
Length(fieldnames), #fieldnames[0]);
end;
An alternative to the final parameter is to pass PPAnsiChar(fieldnames). That works because a dynamic array variable is the address of the first element.
So, what was wrong with your version? The biggest mistake you made was to use untyped pointers for the two arrays that you pass to MCRdll_CreateStructArray. This means that the compiler cannot check that you got the indirection correct. And you did not.
First of all in your code you pass #lstDims to the second parameter. Now lstDims is a dynamic array in your code. The implementation of that has lstDims being a pointer to the first element. So, informally, lstDims has type ^Integer. And therefore #lstDims has type ^^Integer. That's one level of indirection too far. And you made the exact same mistake in the final parameter.
One final point. I've change the signature of the function to receive an array of AnsiString. That's the easy way for me to write the code because I don't need to worry about the UTF-16 to ANSI conversion, and can use a simple PAnsiChar cast. You'd probably benefit from this helper:
function ToAnsiStringArray(const arr: array of string): TArray<AnsiString>;
var
i: Integer;
begin
SetLength(Result, Length(arr));
for i := 0 to high(Result) do
Result[i] := AnsiString(arr[i]);
end;
I've not compiled any of this so there may be some imprecision. I trust you'll not be put off by that.

Why might SetString intrinsic cause an "Incompatible types" error on PChar argument?

Please excuse the silly question, but I'm confused. Consider the following method (sorry for noisy comments, this is a real code under development):
function HLanguages.GetISO639LangName(Index: Integer): string;
const
MaxIso639LangName = 9; { see msdn.microsoft.com/en-us/library/windows/desktop/dd373848 }
var
LCData: array[0..MaxIso639LangName-1] of Char;
Length: Integer;
begin
{ TODO : GetLocaleStr sucks, write proper implementation }
//Result := GetLocaleStr(LocaleID[Index], LOCALE_SISO639LANGNAME, '??');
Length := GetLocaleInfo(LocaleID[Index], LOCALE_SISO639LANGNAME, #LCData, System.Length(LCData));
Win32Check(Length <> 0);
SetString(Result, #LCData, Length); // "E2008 Incompatible types" here, but why?
end;
If I remove the reference operator then implicit cast from $X+ comes to the rescue and method compiles. Why compiler refuses this code with reference operator is beyond my understanding.
This is Delphi XE2 and this behaviour might be specific to it.
And if I add a test-case dummy with equivalent prototype as intrinsic one within the scope of HLanguages.GetISO639LangName this error will magically go away:
procedure SetString(var s: string; buffer: PChar; len: Integer);
begin
{ test case dummy }
end;
You have to explicitly convert it to PChar:
SetString(result,PChar(#LCData),Length);
As you stated, SetString() is very demanding about the 2nd parameter type. It must be either a PChar either a PWideChar either a PAnsiChar, depending on the string type itself.
I suspect this is due to the fact that SetString() is defined as overloaded with either a string, a WideString, or an AnsiString as 1st parameter. So in order to validate the right signature, it needs to have exact match of all parameters types:
SetString(var s: string; buf: PChar; len: integer); overload;
SetString(var s: AnsiString; buf: PAnsiChar; len: integer); overload;
SetString(var s: WideString; buf: PWideChar; len: integer); overload;
Of course, all those are "intrinsics", so you won't find such definition in system.pas, but directly some procedure like _LStrFromPCharLen() _UStrFromPCharLen() _WStrFromPWCharLen() or such.
This behavior is the same since early versions of Delphi, and is not a regression in XE2.
I think there's a compiler bug in there because the behaviour with SetString differs from the behaviour with overloaded functions that you provide. What's more there's an interaction with the Typed # operator compiler option. I don't know how you set that. I always enable it but I suspect I'm in the minority there.
So I cannot explain the odd behaviour, and answer the precise question you ask. I suspect the only way to answer it is to look at the internals of the compiler, and very few of us can do that.
Anyway, in case it helps, I think the cleanest way to pass the parameter is like so:
SetString(Result, LCData, Length);
This compiles no matter what you set Typed # operator to.
I know this doesn't answer the specific question regarding SetString, but I'd like to point out that you can do the same thing by simply writing
Result := LCData;
When assigning to a string, Delphi treats a static array of char with ZERO starting index, as a Null terminated string with maximum length. Consider the following:
var
IndexOneArray : array [ 1 .. 9 ] of char;
IndexZeroArray : array [ 0 .. 8 ] of char;
S : string;
T : string;
begin
IndexOneArray := 'ABCD'#0'EFGH';
IndexZeroArray := 'ABCD'#0'EFGH';
S := IndexOneArray;
T := IndexZeroArray;
ShowMessage ( 'S has ' + inttostr(length(S)) + ' chars. '
+ #13'T has ' + inttostr(length(T)) + ' chars. ' );
end;
This displays a message that S has 9 chars, while T has 4.
It will also work when the zero-index array has 9 non-null characters. The result will be 9 characters regardless of what's in the following memory locations.
Because LCData is pointer to the array, not to the Char. Sure, sometimes it happens that an array or a record or a class start with char-type variable, but consequences are not what statically-typed compiler should rely upon.
You have to take the pointer to a character in that array, not to the array itself.
SetString(Result, #LCData[Low(LCData)], Length);

Assign String to Array of Characters

Question One
I have
var example : array[0..15] of char;
I want to assign the value from an input to that variable
example := inputbox('Enter Name', 'Name', '');
In the highscores unit I have record and array
type
points = record
var
_MemoryName : array[0..15] of char;
_MemoryScore : integer;
end;
var
rank : array[1..3] of points;
var s: string;
a: packed array[0..15] of char;
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a)) ;
returns -> (186): E2010 Incompatible types: 'array[0..15] of Char' and 'PWideChar'
var s: string;
a: packed array[0..15] of char;
s := InputBox('caption', 'Caption', 'Caption');
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(a) * sizeof(char));
scores.rank[1]._MemoryName := <<tried both s and a>> ;
returns (189): E2008 Incompatible types
Question One
There are many ways. One is:
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
a: packed array[0..15] of char;
begin
s := InputBox(Caption, Caption, Caption);
assert(length(s) <= 16);
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(s) * sizeof(char));
end;
But there might be a more elegant solution to your original problem, I suspect.
Question Two
Every time you wish a function/procedure didn't have a particular argument, you should realize that there might be a problem with the design of the project. Nevertheless, it isn't uncommon that Sender parameters are superfluous, because they are almost omnipresent because of the design of the VCL (in particular, the TNotifyEvent). If you know that the receiving procedure doesn't care about the Sender parameter, simply give it anything, like Self or nil.
Question Three
Consider this code:
procedure TForm4.FormCreate(Sender: TObject);
var
a: packed array[0..15] of char;
b: packed array[0..15] of char;
begin
a := b;
end;
This doesn't work. You cannot treat arrays like strings; in particular, you cannot assign static arrays like this (a := b).
Instead, you have to do something like...
Move(b[0], a[0], length(a) * sizeof(char));
...or simply loop and copy one value at a time. But the above simple assignment (a := b) does work if you declare a static array type:
type
TChrArr = packed array[0..15] of char;
procedure TForm4.FormCreate(Sender: TObject);
var
a: TChrArr;
b: TChrArr;
begin
b := a;
end;
Andreas has you covered for question 1.
Question 2
I would arrange that your event handler called another method:
procedure TForm5.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
RespondToEditControlKeyPress;
end;
That way you can just call RespondToEditControlKeyPress directly.
I'd guess that you want to call it with no parameters because you want code to run when the edit control's text is modified. You could perhaps use the OnChange event instead. And it may be that OnChange is more appropriate because pressing a key is not the only way to get text into an edit control.
By the way, it's better to ask one question at a time here on Stack Overflow.
For a quick way to copy string-type values into array-of-character type values. I suggest a small helper function like this:
procedure StrToCharArray( inputStr:String; var output; maxlen:Integer);
type
ArrayChar = Array[0..1] of Char;
begin
StrLCopy( PChar(#ArrayChar(output)[0]),PChar(inputStr),maxlen);
end;
Each time you call it, pass in the maximum length to be copied. Remember that if the buffer length is 15, you should pass in 14 as the maxlen, so that you leave room for the terminating nul character, if you intend to always terminate your strings:
StrToCharArray( UserInputStr, MyRecord.MyField, 14 );
This function will ensure that the data you copy into the record is null terminated, assuming that's what you wanted. Remember that in a fixed length character array it's up to you to decide what the rules are. Null terminated? Fully padded with spaces or null characters.... Strings and arrays-of-characters are so different, that there exist multiple possible ways of converting between the two.
If you don't intend to terminate your strings with nul, then you should use the FillChar+Move combination shown in someone else's answer.
The obvious answer is of course.
Don't use a packed array of char.
Use a string instead.
If you use ansistring, 1 char will always take 1 byte.
If you use shortstring ditto.
Ansistring is compatible with Pchar which is a pointer to a packed array of char.
So you can write
function inputbox(a,b,c: ansistring): pchar;
begin
Result:= a+b+c;
end;
var s: ansistring;
begin
s:= inputbox('a','b','c');
end;
Some advice
It looks like your are translating code from c to Delphi.
a packed array of char is exactly the same as the old (1995) shortstring minus the length byte at the beginning of shortstring.
The only reason I can think of to use packed array of char is when you are reading data to and from disk, and you have legacy code that you don't want to change.
I would keep the legacy code to read and write from disk and then transfer the data into an ansistring and from there on only use ansistring.
It's soooooooo much easier, Delphi does everything for you.
And... ansistring is much faster, gets automatically created and destroyed, can have any length (up to 2GB), uses less memory --because identical strings only get stored once (which means stringa:= stringb where a string is 20 chars is at least 5x faster using ansistrings than array's of char).
And of course best of all, buffer overflow errors are impossible with ansistring.
What about unicodestring?
Unicodestring is fine to use, but sometimes translation of chars happens when converting between packed array of char and unicodestring, therefore I recommend using ansistring in this context.
What you try to do is impossible, indeed:
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a));
That tries to assign a pointer (the result of StrPLCopy, a PWideChar in the last few versions of Delphi) to an array, which is indeed impossible. You can't copy an array like that. I would do:
StrLCopy(highscoresdata.position[1]._MemoryName, PChar(s),
Length(highscoresdata.position[1]._MemoryName));
That should work, and is IMO the simplest solution to copy a string to an array of characters. There is no need to use a as some kind of intermediate, and using Move is, IMO, rather low level and therefore a little tricky (it is easy to forget to multiply by the size of a character, it is unchecked, it does not add a #0, etc.), especially if you don't know what exactly you are doing.
This solution should even work for versions of Delphi before Delphi 2009, as it does not rely on the size of the character.
FWIW, I would not use packed arrays. Packed doesn't have a meaning in current Delphi, but could confuse the compiler and make the types incompatible.

Passing an Ordinal Parameter

Is it possible to write a method which takes any ordinal type as a parameter? The same way Inc() or High() do?
I'm using Delphi 2007
You'd need to use an untyped parameter:
procedure Foo(const ordinal);
or
procedure Foo(var ordinal);
Of course, you are somewhat limited in what you could do inside such a routine because you have abandoned the type system.
Found a possible way, might not be what you expect, but hey, I found a way! Use Variants. The problem with passing typeless parameters to a procedure is that you get a plain pointer, no type information, so you can't do anything useful with it. Bytes are 1 byte, enums of up to 256 elements are 1 byte byte, enums of up to 2^16 elements are 2 bytes, integers are 4 bytes (unless they're 8). But there is one type that allows anything to be passed and cares enough type information to make things work: the Variant. I intentionally wrote the following example in Delphi 7, to make sure I don't accidentally use any Delphi 2010 or Delphi XE goodness.
Edit: Updated the code sample to handle any type that's considered Ordinal by the Variants.VarTypeIsOrdinal. That includes all integer types + Boolean. Apparently Enum is seen as Byte, so it swallows that too.
program Project1;
{$APPTYPE CONSOLE}
uses
ExceptionLog,
SysUtils, Variants;
type TSomeEnum = (e0, e1, e2, e3, e4);
procedure DoSomethingWithEnum(V: Variant);
var i: Integer;
b: Byte;
lw: LongWord; // Cardinal!
i64: Integer;
begin
case VarType(V) of
varInt64:
begin
i64 := V;
WriteLn(i64);
end;
varSmallint, varInteger, varShortInt:
begin
i := V;
WriteLn(i);
end;
varByte:
begin
b := V;
WriteLn(b);
end;
varWord, varLongWord:
begin
lw := V;
WriteLn(lw);
end;
varBoolean:
begin
if V then WriteLn('True') else WriteLn('False');
end;
else WriteLn('NOT a variant type (type = #' + IntToStr(Ord(VarType(V))));
end;
end;
var i: Integer;
b: Byte;
c: Cardinal;
enum: TSomeEnum;
w: Word;
si: Shortint;
begin
i := 1;
b := 2;
c := 3;
enum := e4;
w := 5;
si := -6;
DoSomethingWithEnum(i);
DoSomethingWithEnum(b);
DoSomethingWithEnum(c);
DoSomethingWithEnum(enum);
DoSomethingWithEnum(True);
DoSomethingWithEnum(w);
DoSomethingWithEnum(si);
Readln;
end.
The reason why doing this is difficult is that Inc(x), Dec(x) and others like Pred(x) and Succ(x) are actually, generated by the compiler, and are, if you like, merely Function style syntax sugar over an inherent compiler operation.
You can, as some people suggest, do some of this with overloading, some of it with clever use of generics, and some of it with variants. But nothing will be as convenient for emulating these functions, or exactly the same functionally.
The compiler implements Inc() for example, for all ordered types, including Enums, Integers, and subranges on those types (a now rather obscure feature of classic "Wirth" Pascal is that all types can have subranges defined on those types).
If you actually told us more about what you were doing, it might be possible to get closer. But the general answer is, No, there isn't even source code for Inc, and Dec, because these are compiler primitives. If there was RTL source code to the function Inc, you could go look at it, and adapt it.
inc(x) could be defined as x := Succ(x), but then, how do you define Succ(x)? As x := Inc(x)? You see. At some point, compiler "magic" takes over.

Resources