Delphi, edit Auto-Complete access violation in tenumstring at TPointerlist() - delphi

In this autocomplete example by #KenWhite, the Next function has an access violation when TPointerList()[] is called ( by the windows autocomplete interface.)
D10.1u2, Win10.64
function TEnumString.Next(celt: Integer; out elt;
pceltFetched: PLongint): HResult;
var
I: Integer;
wStr: WideString;
begin
I := 0;
while (I < celt) and (FCurrIndex < FStrings.Count) do
begin
wStr := FStrings[FCurrIndex];
TPointerList(elt)[1] := PWideChar('abcd'); //access violation
TPointerList(elt)[1] := CoTaskMemAlloc(8); //access violation
TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1)); //access violation
StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
Inc(I);
Inc(FCurrIndex);
end;
if pceltFetched <> nil then
pceltFetched^ := I;
if I = celt then
Result := S_OK
else
Result := S_FALSE;
end;

In newer versions (IIRC XE2 and above), you can do what Remy says, but IMO you should not.
In versions before XE2 (or whatever version it was), the definition of TPointerList was:
type
...
TPointerList = array[0..MaxListSize] of Pointer;
In the newer versions, it is:
type
TPointerList = array of Pointer;
In other words, instead of a static array type (a value type), it has become a dynamic array type (a reference type) now. Casting the address of an untyped out parameter to such an array can turn out to be tricky.
The difference in definition explains why in newer versions, the code does not work properly: there is one extra level of indirection.
Now if you add the following declaration to the uAutoComplete.pas file:
type
TPointerList = array[0..65535] of Pointer; // assuming 65536 (2^16) entries are enough
then the rest of the file can remain what it used to be. Then:
TPointerList(elt)[I] := ...
works and does not require you to use a slightly tricky, indirect, cast to a Delphi dynamic array on something that is actually not. Note that this will also work in older versions.

(elt) needs to be (#elt), and [1] needs to be [I]:
TPointerList(#elt)[I]
Then the code will not AV anymore.
Also, the output strings must be allocated with either SysAllocString...() or CoTaskMemAlloc(), as the caller is going to use the COM memory manager to free them. You can use the RTL's ComObj.StringToLPOLESTR() function to handle that for you, which makes a COM-allocated wide string copy of a Delphi String:
TPointerList(#elt)[I] := StringToLPOLESTR(FStrings[FCurrIndex]);
Alternatively, you can simply take ownership of the WideString's data pointer instead of making yet another copy in memory after WideString has already made one:
wStr := FStrings[FCurrIndex];
TPointerList(#elt)[I] := Pointer(wStr);
Pointer(wStr) := nil;

Related

Copy a file to clipboard in Delphi

I am trying to copy a file to the clipboard. All examples in Internet are the same. I am using one from, http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212186.html but it does not work.
I use Rad Studio XE and I pass the complete path. In mode debug, I get some warnings like:
Debug Output:
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
I am not sure is my environment is related: Windows 8.1 64 bits, Rad Studio XE.
When I try to paste the clipboard, nothing happens. Also, seeing the clipboard with a monitor tool, this tool shows me error.
The code is:
procedure TfrmDoc2.CopyFilesToClipboard(FileList: string);
var
DropFiles: PDropFiles;
hGlobal: THandle;
iLen: Integer;
begin
iLen := Length(FileList) + 2;
FileList := FileList + #0#0;
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen);
if (hGlobal = 0) then raise Exception.Create('Could not allocate memory.');
begin
DropFiles := GlobalLock(hGlobal);
DropFiles^.pFiles := SizeOf(TDropFiles);
Move(FileList[1], (PChar(DropFiles) + SizeOf(TDropFiles))^, iLen);
GlobalUnlock(hGlobal);
Clipboard.SetAsHandle(CF_HDROP, hGlobal);
end;
end;
UPDATE:
I am sorry, I feel stupid. I used the code that did not work, the original question that somebody asked, in my project, while I used the Remy's code, the correct solution, here in Stackoverflow. I thought that I used the Remy's code in my project. So, now, using the Remy's code, everything works great. Sorry for the mistake.
The forum post you link to contains the code in your question and asks why it doesn't work. Not surprisingly the code doesn't work for you any more than it did for the asker.
The answer that Remy gives is that there is a mismatch between ANSI and Unicode. The code is for ANSI but the compiler is Unicode.
So click on Remy's reply and do what it says: http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212187.html
Essentially you need to adapt the code to account for characters being 2 bytes wide in Unicode Delphi, but I see no real purpose repeating Remy's code here.
However, I'd say that you can do better than this code. The problem with this code is that it mixes every aspect all into one big function that does it all. What's more, the function is a method of a form in your GUI which is really the wrong place for it. There are aspects of the code that you might be able to re-use, but not factored like that.
I'd start with a function that puts an known block of memory into the clipboard.
procedure ClipboardError;
begin
raise Exception.Create('Could not complete clipboard operation.');
// substitute something more specific that Exception in your code
end;
procedure CheckClipboardHandle(Handle: HGLOBAL);
begin
if Handle=0 then begin
ClipboardError;
end;
end;
procedure CheckClipboardPtr(Ptr: Pointer);
begin
if not Assigned(Ptr) then begin
ClipboardError;
end;
end;
procedure PutInClipboard(ClipboardFormat: UINT; Buffer: Pointer; Count: Integer);
var
Handle: HGLOBAL;
Ptr: Pointer;
begin
Clipboard.Open;
Try
Handle := GlobalAlloc(GMEM_MOVEABLE, Count);
Try
CheckClipboardHandle(Handle);
Ptr := GlobalLock(Handle);
CheckClipboardPtr(Ptr);
Move(Buffer^, Ptr^, Count);
GlobalUnlock(Handle);
Clipboard.SetAsHandle(ClipboardFormat, Handle);
Except
GlobalFree(Handle);
raise;
End;
Finally
Clipboard.Close;
End;
end;
We're also going to need to be able to make double-null terminated lists of strings. Like this:
function DoubleNullTerminatedString(const Values: array of string): string;
var
Value: string;
begin
Result := '';
for Value in Values do
Result := Result + Value + #0;
Result := Result + #0;
end;
Perhaps you might add an overload that accepted a TStrings instance.
Now that we have all this we can concentrate on making the structure needed for the CF_HDROP format.
procedure CopyFileNamesToClipboard(const FileNames: array of string);
var
Size: Integer;
FileList: string;
DropFiles: PDropFiles;
begin
FileList := DoubleNullTerminatedString(FileNames);
Size := SizeOf(TDropFiles) + ByteLength(FileList);
DropFiles := AllocMem(Size);
try
DropFiles.pFiles := SizeOf(TDropFiles);
DropFiles.fWide := True;
Move(Pointer(FileList)^, (PByte(DropFiles) + SizeOf(TDropFiles))^,
ByteLength(FileList));
PutInClipboard(CF_HDROP, DropFiles, Size);
finally
FreeMem(DropFiles);
end;
end;
Since you use Delphi XE, strings are Unicode, but you are not taking the size of character into count when you allocate and move memory.
Change the line allocating memory to
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen * SizeOf(Char));
and the line copying memory, to
Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^, iLen * SizeOf(Char));
Note the inclusion of *SizeOf(Char) in both lines and change of PChar to PByte on second line.
Then, also set the fWide member of DropFiles to True
DropFiles^.fWide := True;
All of these changes are already in the code from Remy, referred to by David.

Pointer to Function of (Sub-)Method?

If you like to use a method's pointer as an argument, you need to type the method as function of object like this works good:
type TAcceptor = function(filename:string):boolean of object;
function acceptor(filename:string):boolean;
begin
result := filename <> '';
end;
What if you like to use the pointer of a sub-method? It does not work:
procedure TForm1.Button1Click(Sender:TObject);
function acceptor(filename:string):boolean of object;
begin
result := filename <> '';
end;
begin
end;
The error occour: ; expected but OF found!
Question: Is there any subfunction-pointer? Can i cast it?
I don't see how that this would be possible.
http://docwiki.embarcadero.com/RADStudio/XE6/en/Procedural_Types
If you look under the method pointers section, it specifically says that nested procedures and functions cannot be used:
"Nested procedures and functions (routines declared within other
routines) cannot be used as procedural values, nor can predefined
procedures and functions."
You might be able to work around it using an anonymous method. Something like:
procedure TForm1.Button1Click(Sender:TObject);
begin
DoSomethingWithAcceptor(function(FileName: string): Boolean
begin
Result := FileName <> '';
end);
end;
CAUTION
I know that the following is not universally applicable, but it works for all known Win32 versions of Delphi. As long as you are aware of this, and check its functionality in new versions, it is a viable hack, IMO.
Passing nested functions to methods
In older code, I used this to do some "poor man's anonymous methods":
type
TLocal = packed record
Code: Pointer; // local (nested) function
Frame: Pointer; // outer stack frame for local function
end;
To fill such a local inside a method, I wrote the function Local:
function Local(LocalFunction: Pointer): TLocal;
asm
MOV [EDX].TLocal.Frame,EBP
MOV [EDX].TLocal.Code,EAX
end;
Inside my unit (some kind of generic collection), I wrote a function to call them, passing one parameter (of type TGeneric, in this case, which is not important here, you can also pass a pointer or some such).
// Calls local function using local closure provided, passing
// T as parameter to the local.
function CallLocal(T: TGeneric; const Local: TLocal): TGeneric;
asm
PUSH [EDX].TLocal.Frame
CALL [EDX].TLocal.Code
ADD ESP,4
end;
It was used like this:
function TStdCollection.AsArray: TGenericArray;
var
I: Integer;
A: TGenericArray;
procedure ToArray(E: TGeneric);
begin
Result[I] := E.Traits.Copy(E);
Inc(I);
end;
begin
SetLength(A, Count);
I := 0;
ForEach(Local(#ToArray));
Assert(I = Count);
Result := A;
end;
The code in the nested function makes a copy of the element and stores it in the array. The main procedure then passes the nested function ToArray (together with its stack frame) as parameter to ForEach, which is implemented this way:
function TStdCollection.ForEach(Operation: TLocal): ICollection;
var
Enum: IEnumerator;
Elem: TGeneric;
begin
Enum := GetEnumerator;
Elem := Enum.First;
while Elem <> nil do
begin
CallLocal(Elem, Operation);
Elem := Enum.Next;
end;
Result := Self;
end;
These examples show how to use the Locals. I hope this more or less answers your question.
Note
Note that this code was written in the Delphi 6 timeframe. I know there are better alternatives these days, like generics and anonymous methods. But if compatibility with Delphi 7 is required, the above might be a solution.

GetAdaptersInfo not working on Delphi XE6

I finally bit the bullet and bought XE6 and as expected, the Unicode conversion is turning into a bit of a nightmare. So if anyone can enlighten me on why this simple Windows API call fails, it would be most appreciated. The function does not return an error, the first call gets the correct buffer length, the second call fills the record with garbage.
This works fine under Delphi 2007 but fails on XE6 with unicode garbage in the pAdapterinfo return record even though it is explicitly declared with AnsiString in IpTypes.pas
System is Win7(64) but compiling for 32 bits.
uses iphlpapi, IpTypes;
function GetFirstAdapterMacAddress:AnsiString;
var pAdapterInfo:PIP_ADAPTER_INFO;
BufLen,Status:cardinal; i:Integer;
begin
result:='';
BufLen:= sizeof(IP_ADAPTER_INFO);
GetAdaptersInfo(nil, BufLen);
pAdapterInfo:= AllocMem(BufLen);
try
Status:= GetAdaptersInfo(pAdapterInfo,BufLen);
if (Status <> ERROR_SUCCESS) then
begin
case Status of
ERROR_NOT_SUPPORTED: raise exception.create('GetAdaptersInfo is not supported by the operating ' +
'system running on the local computer.');
ERROR_NO_DATA: raise exception.create('No network adapter on the local computer.');
else
raiselastOSerror;
end;
Exit;
end;
while (pAdapterInfo^.AddressLength=0) and (pAdapterInfo^.next<>nil) do
pAdapterInfo:=pAdapterInfo.next;
if pAdapterInfo^.AddressLength>0 then
for i := 0 to pAdapterInfo^.AddressLength - 1 do
result := result + IntToHex(pAdapterInfo^.Address[I], 2);
finally
Freemem(pAdapterInfo);
end;
end;
UPDATE:
I did some more checking. I created a new simple application with one form and a button and called the routine when the button was pressed and it worked.
The differences are...in the working form the size of IP_ADAPTER_INFO is 640 bytes.
When this routine is used in a more complex application it fails and the size of IP_ADAPTER_INFO displays as 1192 bytes.
At this point, it seems the complier is unilaterally deciding to change the type of the ansi chars in the structures to unicode chars. The debugger is showing AdapterName and description fields in unicode form. I did a grep of the system source code, there are no other versions of this data type declared in the library code apart from in the Indy library and that is just a duplicate.
Here is the data structure definition in IPtypes
PIP_ADAPTER_INFO = ^IP_ADAPTER_INFO;
{$EXTERNALSYM PIP_ADAPTER_INFO}
_IP_ADAPTER_INFO = record
Next: PIP_ADAPTER_INFO;
ComboIndex: DWORD;
AdapterName: array [0..MAX_ADAPTER_NAME_LENGTH + 3] of AnsiChar;
Description: array [0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of AnsiChar;
AddressLength: UINT;
Address: array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;
Index: DWORD;
Type_: UINT;
DhcpEnabled: UINT;
CurrentIpAddress: PIP_ADDR_STRING;
IpAddressList: IP_ADDR_STRING;
GatewayList: IP_ADDR_STRING;
DhcpServer: IP_ADDR_STRING;
HaveWins: BOOL;
PrimaryWinsServer: IP_ADDR_STRING;
SecondaryWinsServer: IP_ADDR_STRING;
LeaseObtained: time_t;
LeaseExpires: time_t;
end;
Looks like a compiler bug.
There are several problems with your code:
You are not doing any error handling at all on the first call that calculates the buffer length. You don't even need that call, so get rid of it.
You are not doing adequate error handling on subsequent calls, in particular you are not handling the ERROR_BUFFER_OVERFLOW condition when GetAdaptersInfo() needs you to allocate more memory than you already have. Your are allocating only enough memory for one adapter, but GetAdaptersInfo() returns info for all adapters and thus needs a sufficient buffer to hold all of them at one time.
GetAdaptersInfo() does not use GetLastError(), so you need to call SetLastError() before you call RaiseLastOSError().
You are looping through the adapter list using the original pointer that you used to allocate the list, so you are causing a memory leak if the first adapter does not have a MAC address. You need to use a separate variable as the loop iterator so the original pointer is preserved so it can be freed correctly.
You are not taking into account the possibility that none of the adapters has a MAC address, so you will end up accessing a nil pointer after your while loop exits.
You appear to have multiple versions of the IpTypes unit on your machine, and the compiler is finding one that happens to use Char instead of AnsiChar in the IP_ADAPTER_INFO record so its size and field offsets are wrong.
With that said, try this instead:
uses
Winapi.iphlpapi, Winapi.IpTypes;
function GetFirstAdapterMacAddress: String;
var
pAdapterList, pAdapter: PIP_ADAPTER_INFO;
BufLen, Status: DWORD;
I: Integer;
begin
Result := '';
BufLen := 1024*15;
GetMem(pAdapterList, BufLen);
try
repeat
Status := GetAdaptersInfo(pAdapterList, BufLen);
case Status of
ERROR_SUCCESS:
begin
// some versions of Windows return ERROR_SUCCESS with
// BufLen=0 instead of returning ERROR_NO_DATA as documented...
if BufLen = 0 then begin
raise Exception.Create('No network adapter on the local computer.');
end;
Break;
end;
ERROR_NOT_SUPPORTED:
begin
raise Exception.Create('GetAdaptersInfo is not supported by the operating system running on the local computer.');
end;
ERROR_NO_DATA:
begin
raise Exception.Create('No network adapter on the local computer.');
end;
ERROR_BUFFER_OVERFLOW:
begin
ReallocMem(pAdapterList, BufLen);
end;
else
SetLastError(Status);
RaiseLastOSError;
end;
until False;
pAdapter := pAdapterList;
while pAdapter <> nil do
begin
if pAdapter^.AddressLength > 0 then
begin
for I := 0 to pAdapter^.AddressLength - 1 do begin
Result := Result + IntToHex(pAdapter^.Address[I], 2);
end;
Exit;
end;
pAdapter := pAdapter^.next;
end;
finally
FreeMem(pAdapterList);
end;
end;
The explanation is that the types declared in your third party IpTypes unit use Char. This is an alias to AnsiChar in pre-Unicode Delphi, and an alias to WideChar in Unicode Delphi. That would explain the fact that you see non-ANSI text when you inspect the content of the record.
The solution is to fix IpTypes to use AnsiChar in place of Char where appropriate. The best way to do that is to use the IpTypes shipped with Delphi rather than your third party version.
On top of that, the first call to GetAdaptersInfo is wrong. Not only do you fail to check the return value, but you pass nil for the buffer and yet also pass a non-zero length. I think it should go like this:
BufLen := 0;
if GetAdaptersInfo(nil, BufLen) <> ERROR_BUFFER_OVERFLOW then
raise ....
Of course, you way will work, but I'm just being a little pedantic here. Always check for errors when you call an API function.
Just to conclude this topic.
Changing IPtypes to winapi.IPtypes fixed the problem for me.
I think a third party component is doing something to confuse the compiler and giving the full link fixes it.

How to prevention when assignment a out param happening "Access Violation Exception"?

There is method:
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
a := b;
Result := T_Result;
end;
Now the exception happening on a := b; , happening Access violation Exception.
Ofcourse I can try and catch it. but I don't want to do that....
So Is there any way can determine use some way and skip the assignment. like:
if (! now I know it will happening that Exception){
a := b; // so I can skip
}
Result := T_Result;
Maybe it's very easy, but because I don't know use delphi, So hope your guys can help me. thanks.
Update1:
b: Boolean;//Some friend need to know what is the b param type.
Update2:
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Update3:
I'm trying to debug it, and when I'm debug, on the Local variables panel display:
a Inaccessible value
I'm use .NET called it. there is metadata:
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
actually I'm not use .NET access it. I'm use .NET access a DirectShow filter, and the directshow filter is current method(write by delphi)
Update4:
this is partial C# code
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), SuppressUnmanagedCodeSecurity, Guid("hidden")]
public interface IDCDSPFilterInterface{
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
.. hidden other ..
}}
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Pointer variables are ABC of Pascal. http://en.wikipedia.org/wiki/Pascal_(programming_language)#Pointer_types
So the proper way to write that check would be
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
var ptr: ^BOOL;
begin
ptr := #a;
if nil = ptr then ....
a := b;
Result := T_Result;
end;
That is the basic question to you explicit questions.
Now, in reality that check does not help. It would only protect your from nil/NULL pointers, but that is not what probably happens. What happens is probably a random garbage pointer instead of nil. Due to error in the calling code.
Again, you can check that via var ptr: Pointer {untyped}; ptr := #Self; if ptr = nil then ... or just if nil <> Self or just if Assigned(Self) - but that would only protect you from NIL pointers, not from RANDOM GARBAGE pointers.
More so, i think that actual garbage is not in pointer to the variable a, but to the pointer to Self and b being a member of TEST classm, thus the real statement is a := Self.b;.
Since you use stdcall i think you're trying to make a DLL for using from an EXE made in a in non-Delphi language. Most probably you either made a wrong definition for function in that client app code. Actually, you just can make a proper declaration is you Test is a class. You only can make a proper if get_Param is a method of RECORD Test or perhaps if it is STATIC CLASS method of Test class. So the proper way to write your function would be like following
function Test.get_Param(out a : BOOL): HRESULT;
begin
a := b;
Result := T_Result;
end;
function DLL_get_Param(const TestObject: pointer; out a : BOOL): HRESULT; stdcall;
var MyTest: Test;
begin
pointer(MyTest) := TestObject;
Result := MyTest.DLL_get_Param(a);
end;
export DLL_get_Param;
Read Delphi documentation what you can get/put to/from DLL functions.
Integers, floats, pointers, IInterface. You cannot pass into DLL complex and behaving objects like stings, dynamic arrays, object instances. And since you cannot pass an object instance, you cannot pass a Self variable and you cannot call a method.
One very expensive way to catch it would be like
{global} var TestInstances: TList;
type
TEST = class...
procedure AfterConstructon; override;
procedure BeforeConstructon; override;
....
procedure Test.AfterConstructon;
begin
inherited;
TestInstances.Add(Self); // single-thread assumption here
end;
procedure Test.BeforeConstructon;
begin
TestInstances.Remove(Self); // single-thread assumption here
inherited;
end;
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
if not ( TestInstances.IndexOf(Self) >= 0 {found!} ) // single-thread assumption here
then ... WTF ???
...
....
initialization
TestInstances := TList.Create;
finalization
TestInstances.Free;
end;
If your DLL can be used by multi-threaded application you should also wrap the marked calls into http://docwiki.embarcadero.com/Libraries/XE2/en/System.SyncObjs.TCriticalSection
There is a gross mismatch across the two sides of your interop boundary. Your Delphi function does not match the C# declaration.
The solution is not to test for parameter validity. Your Delphi code, given the declaration of the function in the question, is correct. The solution is to make both sides of the interop boundary match. I cannot tell you more than that until you show both sides of the interop boundary.
Since I can't see where you've decalred b, I'm going to assume it's a member of Test.
So one strong possibility is that you have an invalid instance of Test, and you get an Access Violation trying to read b in order to assign it to a. As an example the following use of get_Param would raise an exception.
var
LTest: Test;
LA: Boolean;
begin
LTest := nil;
LTest.get_Param(LA);
end;
The point is that you need a valid instance of Test in order to use it. E.g.
var
LTest: Test;
LA: Boolean;
begin
LTest := Test.Create;
try
LTest.get_Param(LA);
finally
LTest.Free;
end;
end;

How to access characters of a WideString by index?

i have the following code snippit that won't compile:
procedure Frob(const Grob: WideString);
var
s: WideString;
begin
s :=
Grob[7]+Grob[8]+Grob[5]+Grob[6]+Grob[3]+Grob[4]+Grob[1]+Grob[2];
...
end;
Delphi5 complains Incompatible types.
i tried simplifying it down to:
s := Grob[7];
which works, and:
s := Grob[7]+Grob[8];
which does not.
i can only assume that WideString[index] does not return a WideChar.
i tried forcing things to be WideChars:
s := WideChar(Grob[7])+WideChar(Grob[8]);
But that also fails:
Incompatible types
Footnotes
5: Delphi 5
The easier, and faster, in your case, is the following code:
procedure Frob(const Grob: WideString);
var
s: WideString;
begin
SetLength(s,8);
s[1] := Grob[7];
s[2] := Grob[8];
s[3] := Grob[5];
s[4] := Grob[6];
s[5] := Grob[3];
s[6] := Grob[4];
s[7] := Grob[1];
s[8] := Grob[2];
...
end;
Using a WideString(Grob[7])+WideString(Grob[8]) expression will work (it circumvent the Delphi 5 bug by which you can't make a WideString from a concatenation of WideChars), but is much slower.
Creation of a WideString is very slow: it does not use the Delphi memory allocator, but the BSTR memory allocator supplied by Windows (for OLE), which is damn slow.
Grob[7] is a WideChar; that's not the issue.
The issue seems to be that the + operator cannot act on wide chars. But it can act on wide strings, and any wide char can be cast to a wide string:
S := WideString(Grob[7]) + WideString(Grob[8]);
As Geoff pointed out my other question dealing with WideString weirdness in Delphi, i randomly tried my solution from there:
procedure Frob(const Grob: WideString);
var
s: WideString;
const
n: WideString = ''; //n=nothing
begin
s :=
n+Grob[7]+Grob[8]+Grob[5]+Grob[6]+Grob[3]+Grob[4]+Grob[1]+Grob[2];
end;
And it works. Delphi is confused about what type a WideString[index] in, so i have to beat it over the head.

Resources