Delphi 2010 variant to unicode problem - delphi

I am working on a DLL in Delphi 2010. It exports a procedure that receives an array of variants. I want to be able to take one of these variants, and convert it into a string, but I keep getting ?????
I cannot change the input variable - it HAS to be an array of variants.
The host app that calls the DLL cannot be changed. It is written in Delphi 2006.
Sample DLL code:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: string;
begin
s:= string(String(Arruid[0]));
showmessage(s);
end;
Using D2006 my DLL works fine. I have tried using VartoStr - no luck. When I check the VarType I am getting a varString. Any suggestions how to fix this?

You host application is sending an AnsiString and you dll is expecting a UnicodeString.
Unicode strings was introduced in Delphi 2009, it does not exist in Delphi 2006. How to fix it? Try [untested]:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: AnsiString;
begin
s:= Ansistring(VarToStr(Arruid[0]));
showmessage(s);
end;
or maybe [also untested]:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: AnsiString;
begin
s:= Ansistring(AnsiString(Arruid[0]));
showmessage(s);
end;
You can also check if theres is a function Like VarToStr that accepts AnsiStrings (maybe in the AnsiStrings unit?).

1/ How have you call the VarToStr() function ? VarToString(Arruid[0]) ?
2/ Does your Delphi2006 Application send AnsiString or WideString to the DLL ?
If so, and if (1) is not working, try to cast to AnsiString instead of string.

Related

Pre-unicode compiled DLL gives access violations when calling from Delphi XE5

I have a DLL that was provided by a 3rd party company and when called from Delphi 2007, it worked perfectly fine. The following code is a sample of how the DLL was used in Delphi 2007:
Procedure XC_eXpressLink(hHandle: Hwnd; Parameters: pChar; Result: pChar); stdcall; external 'XCClient.dll';
Here is how the procedure was called:
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of char;
sParams: String;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(Handle, pChar(sParams), Result);
memoResults.Text := String(Result);
end;
I'm not sure what the DLL was compiled in, but I'm assuming it is expecting ansi and not unicode. After converting the code to ansi in Delphi XE5, the code is now as follows:
Procedure XC_eXpressLink(hHandle: Hwnd; Parameters: pAnsiChar; Result: pAnsiChar); stdcall; external 'XCClient.dll';
and
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of Ansichar;
sParams: AnsiString;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(Handle, pAnsiChar(sParams), Result);
memoResults.Text := AnsiString(Result);
end;
memoParameters is a TMemo on the form which provides the parameters for the dll procedure. The RemoveCRLF is a function that removes any carriage returns and line feeds from memoParameters. MemoResults is another TMemo on the form that provides the return results of the dll procedure.
I'm getting access violations when the changed code is run in Delphi XE5. Since I changed all the parameters to use ansi, shouldn't the dll be getting the same parameter format as before? Am I doing something wrong? Will I be able to get this older compiled DLL to work in Delphi XE5?
I contacted the company, OpenEdge, which supplies the dll for X-Charge (for credit card integration). To resolve the problem, the Handle must have a value of 0 and you have to add /IGNOREHANDLEPARAMETER to the parameters list that is sent to the dll. Note, this parameter will only work with the full version XC8.1.1.6.exe installation or later.
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of Ansichar;
sParams: AnsiString;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(0, pAnsiChar(sParams), Result);
memoResults.Text := AnsiString(Result);
end;

CharPrinter in ZEBRA Printer XE7

I used the CharPrinter.pas unit to send commands in ZPLII for a ZEBRA printer in Delphi RAD2007 and everything worked well, but I change to XE7 and tried to use the same functions and sending to the printer and does not work, and does not give any error message. Does it have to do the new data type versions XE ?
This happened cause Delphi switched to Unicode strings in Delphi 2009.
Unicode strings use 2 bytes for every char. Older Delphi versions used 1 byte for every char.
So you must make some changes in ChatPrinter.pas.
Try to edit this (not tested):
procedure TCharPrinter.SendData (aData : String);
var
Data : array[0..255] of char;
cnt : integer;
ss : TStringStream;
begin
try
ss := TStringStream.Create(aData,TEncoding.ANSI);
fStream.CopyFrom (ss,0);
finally
ss.Free;
end;
// for cnt := 0 to length(aData) - 1
// do Data[cnt] := aData[cnt+1];
end;
Or simpler:
procedure TCharPrinter.SendData (aData : String);
var
Data : AnsiString;
begin
Data := AnsiString(aData);
fStream.Write(PAnsiChar(Data)^, Length(Data));
end;

How to store string in stream in Delphi 7 and restore on mobile app in XE6?

I develop a server and a mobile client that communicate over HTTP. Server is written in Delphi 7 (because it has to be compatible with old code), client is mobile application written in XE6. Server sends to client stream of data that contains strings. A problem is connected to encoding.
On the server I try to pass strings in UTF8:
//Writes string to stream
procedure TStreamWrap.WriteString(Value: string);
var
BytesCount: Longint;
UTF8: string;
begin
UTF8 := AnsiToUtf8(Value);
BytesCount := Length(UTF8);
WriteLongint(BytesCount); //It writes Longint to FStream: TStream
if BytesCount > 0 then
FStream.WriteBuffer(UTF8[1], BytesCount);
end;
As it's written in Delphi7, Value is a single byte string.
On the client I read string in UTF8 and encode it to Unicode
//Reads string from current position of stream
function TStreamWrap.ReadString: string;
var
BytesCount: Longint;
UTF8: String;
begin
BytesCount := ReadLongint;
if BytesCount = 0 then
Result := ''
else
begin
SetLength(UTF8, BytesCount);
FStream.Read(Pointer(UTF8)^, BytesCount);
Result := UTF8ToUnicodeString(UTF8);
end;
end;
But it doesn't work, when I display the string with ShowMessage the letters are wrong. So how to store string in Delphi 7 and restore it in XE6 on the mobile app? Should I add BOM at the beginning of data representing the string?
To read your UTF8 encoded string in your mobile application you use a byte array and the TEncoding class. Like this:
function TStreamWrap.ReadString: string;
var
ByteCount: Longint;
Bytes: TBytes;
begin
ByteCount := ReadLongint;
if ByteCount = 0 then
begin
Result := '';
exit;
end;
SetLength(Bytes, ByteCount);
FStream.Read(Pointer(Bytes)^, ByteCount);
Result := TEncoding.UTF8.GetString(Bytes);
end;
This code does what you need in XE6, but of course, this code will not compile in Delphi 7 because it uses TEncoding. What's more, your TStreamWrap.WriteString implementation does what you want in Delphi 7, but is broken in XE6.
Now it looks like you are using the same code base for both Delphi 7 and Delphi XE6 versions. Which means that you may need to use some conditional compilation to handle the treatment of text which differs between these versions.
Personally I would do this by following the example of TEncoding. What you need is a function that converts a native Delphi string to a UTF-8 encoded byte array, and a corresponding function in the reverse direction.
So, let's consider the string to bytes function. I cannot remember whether or not Delphi 7 has a TBytes type. I suspect not. So let us define it:
{$IFNDEF UNICODE} // definitely use a better conditional than this in real code
type
TBytes = array of Byte;
{$ENDIF}
Then we can define our function:
function StringToUTF8Bytes(const s: string): TBytes;
{$IFDEF UNICODE}
begin
Result := TEncoding.UTF8.GetBytes(s);
end;
{$ELSE}
var
UTF8: UTF8String;
begin
UTF8 := AnsiToUtf8(s);
SetLength(Result, Length(UTF8));
Move(Pointer(UTF8)^, Pointer(Result)^, Length(Result));
end;
{$ENDIF}
The function in the opposite direction should be trivial for you to produce.
Once you have the differences in handling of text encoding between the two Delphi versions encapsulated, you can then write conditional free code in the rest of your program. For example, you would code WriteString like this:
procedure TStreamWrap.WriteString(const Value: string);
var
UTF8: TBytes;
ByteCount: Longint;
begin
UTF8 := StringToUTF8Bytes(Value);
ByteCount := Length(UTF8);
WriteLongint(ByteCount);
if ByteCount > 0 then
FStream.WriteBuffer(Pointer(UTF8)^, ByteCount);
end;
Instead of
Utf8 : String;
Use
Utf8 : Utf8String;
on client. Then conversion is Automatic.
EDIT: Since the client is on a mobile platform, and Embarcadero has decided to eliminate the 8-bit strings in mobile compilers, the above won't work for this particular case. But in other cases where you have an 8-bit UTF-8 encoded string, the Utf8String can be used to seamlessly convert back and forth between UTF-8 and Unicode strings without the need to use explicit UTF-8 conversion functions. Just use it like
UnicodeStringVariable := Utf8StringVariable;
or
Utf8StringVariable := UnicodeStringVariable;
and the compiler will insert the appropriate conversion.

Calling delphi 2006 dll with a pchar parameter from delphiXE

Since I've upgraded from delphi 5 to XE I'm struggling to use specific Dlls that were compiled a while ago. My blocking point seems related to the unicode/ansi character but I haven't found out how to solve the problem
Here is an example of procedure:
procedure GetFilename(Buffer: PChar; BufSize: Integer); stdcall;
In my code I'm calling this that way
implementation
procedure GetFilename; external 'myDll.dll' name 'GetFilename';
procedure myproc
var
buffer : Array [0..255] of Char;
begin
GetFilename(buffer, length(buffer)-1);
Showmessage(buffer); //This gives me chinese character
end;
Buffer contains this:
byte((#buffer[0])^); // 67 which is the ASCII for C
byte((#buffer[1])^); // 92 which is the ASCII for \
what I'm expecting normal is a string starting with "C:\"
Has anyone faced the same problem?
Because the dll was made using a non-Unicode version of Delphi you must change the declaration from
procedure GetFilename(Buffer: PChar; BufSize: Integer); stdcall;
to
procedure GetFilename(Buffer: PAnsiChar; BufSize: Integer); stdcall;
and the buffer variable from
buffer : Array [0..255] of Char;
to
buffer : Array [0..255] of AnsiChar;
Additionally to learn about the Delphi Unicode support take a look to the Delphi and Unicode Whitepaper from Marco CantĂș.

How Do I Properly Call GetLongPathName Using Delphi 2009 and Unicode Strings?

I need to change the old Win98 short path names to long path names. I had a routine that worked fine with Delphi 4, but when I upgraded to Delphi 2009 and Unicode, it didn't work with Unicode strings.
I looked around and couldn't find a Unicode-compatible version of it.
It appears that the correct routine to use is GetLongPathName from the WinAPI. But it doesn't seem to be in the SysUtils library of Delphi 2009 and I have not been able to figure out how to declare it properly to access the WinAPI routine.
Also, it does seem that it may be tricky to call, because I've read the SO Question: Delphi TPath.GetTempPath result is cropped but that didn't help me get to first base.
Can someone please explain how to declare this function and use it properly passing a Unicode string in Delphi 2009?
Sure. You do need not a separate unit and can declare GetLongPathName anywhere:
function GetLongPathName(ShortPathName: PChar; LongPathName: PChar;
cchBuffer: Integer): Integer; stdcall; external kernel32 name 'GetLongPathNameW';
function ExtractLongPathName(const ShortName: string): string;
begin
SetLength(Result, GetLongPathName(PChar(ShortName), nil, 0));
SetLength(Result, GetLongPathName(PChar(ShortName), PChar(Result), length(Result)));
end;
procedure Test;
var
ShortPath, LongPath: string;
begin
ShortPath:= ExtractShortPathName('C:\Program Files');
ShowMessage(ShortPath);
LongPath:= ExtractLongPathName(ShortPath);
ShowMessage(LongPath);
end;

Resources