Pascal Script fails to retrieve wide string from a custom DLL - inno-setup

I need Inno Setup to load a wide string from a function in a custom DLL. I have no problem loading Ansi strings – this, for example, works fine:
int __stdcall GetStringA(char *lpText)
{
StringCchCopyA(lpText, 30, "New Ansi value");
return strlen(lpText);
}
Pascal Script
function GetStringA(lpText: AnsiString): Integer;
external 'GetStringA#files:MyDll.dll';
function LoadStringA(): AnsiString;
var
Str: AnsiString;
Length: Integer;
begin
Str := 'Initial Ansi value';
SetLength(Str, 30);
Length := GetStringA(Str);
SetLength(Str, Length);
Result := Str; // Str == 'New Ansi value'
end;
Doing the same thing with wide strings fails, however:
int __stdcall GetStringW(wchar_t *lpText)
{
StringCchCopyW(lpText, 30, L"New Wide value");
return wcslen(lpText);
}
Pascal Script
function GetStringW(lpText: WideString): Integer;
external 'GetStringW#files:MyDll.dll';
function LoadStringW(): WideString;
var
Str: WideString;
Length: Integer;
begin
Str := 'Initial Wide value';
SetLength(Str, 30);
Length := GetStringW(Str); // ** DOES NOT ALTER 'Str' **
SetLength(Str, Length);
Result := Str; // Str == 'Initial Wide v' (old string, new length)
end;
Sending a WideString to the DLL works fine. Changing the buffer contents in the DLL also works fine…but somehow that does not percolate back to my Pascal Script variable.
I suspect that some sort of marshaling goes on under the hood, so that the DLL actually manipulates a copy of my variable. Any insights or workarounds appreciated!
I'm using Inno Setup 6.2.0.

I made it work by replacing WideString with String:
function GetStringW(lpText: String): Integer;
external 'GetStringW#files:MyDll.dll';
function LoadStringW(): WideString;
var
Str: String;
Length: Integer;
begin
Str := 'Initial Wide value';
SetLength(Str, 30);
Length := GetStringW(Str); // ** ALTERS 'Str' **
SetLength(Str, Length);
Result := Str; // Str == 'New Wide value'
end;
I'd have thought WideString and String were equivalent, but apparently not quite. It is necessary to change both the function prototype and the Str declaration. It is not necessary to change the LoadStringW return type, though you may – and the returned value can in either case be assigned to a variable of type WideString or String without problem.
So: Problem solved. I'm still curious about the underlying behavior, though.

Related

Compile Error when trying to return PChar or OleVariant for UDF

The title doesn't quite capture the essence of the issue.
I have a UDF function that returns a PChar.
function AccountDescription(sAccountId: PChar) : PChar; stdcall;
This was working fine but I realized I wanted to return #N/A if the accountId was not found.
I discovered CVErr(xlErrNA) and changed the Signature to return OleVariant.
But now I am receiving [Error] Incompatible types: 'OleVariant' and 'PAnsiChar'.
I could not find any information on how to resolve this so I figure my understanding of the problem must not be correct.
I tried just passing a string which compiled but produced a runtime error of "Invalid variant type".
The full code is:
function AccountDescription(sAccountId: PChar): OleVariant; stdcall;
var
strResult: string;
strPChar : PChar;
begin
try
strResult:= repo.GetAccount(sAccountId).Description;
strPChar := strAlloc(length(strResult)+1) ;
StrPCopy(strPChar, strResult) ;
Result := strPChar;
except
Result := CVErr(xlErrNA);
end;
end;
Note: Is excel responsible for destroying the string or is that my cleanup? Should I be creating a copy or should I just be returning a pointer to an existing string. After typing it I feel like I should be returning a pointer.
Update:
Removed some irrelevant code in the example.
Now using:
function AccountDescription(sAccountId: PChar): OleVariant; stdcall;
var
strResult: string;
begin
try
Result := PChar(repo.GetAccount(sAccountId).Description);
except
Result := CVErr(xlErrNA);
end;
end;
You do not need the PChar cast, you can assign a String directly to an OleVariant (it will be converted by the RTL into a BSTR that the receiver will then free when done using it):
Result := repo.GetAccount(sAccountId).Description;
As for reporting an error, do you have a viable CVErr() function in your Delphi code? In VB, CVErr() returns a Variant of type Error (varError in Delphi) containing an error code (xlErrNA is 2042). Delphi has a VarAsError() function for that same purpose:
Result := VarAsError(2042);

Inno Setup loop from A to Z

I am trying to write a function that will check for the existence of a particular directory on each drive in turn, store the path where it is found as a variable and return true if it exists and false otherwise:
function IsDirPresent(): Boolean;
var
chrDriveLetter: Char;
strMyDir: String;
begin
for chrDriveLetter := 'A' to 'Z' do
begin
if DirExists(chrDriveLetter + ':\MyDir') then
strMyDirPath := chrDriveLetter + ':\MyDir'
end;
if strMyDirPath <> '' then
Result := True;
end;
I get a Type mismatch error on my loop line when compiling this. I am struggling to work out what is wrong with the loop and haven't been able to find any information on how to correctly create a loop from A to Z in Inno Setup.
Your loop is valid for Pascal language because the Char type is ordinal and its range constants are of the same type, so I guess this is a specific limitation of the Pascal Script language notation. I don't know if there's some syntax workaround nor if the Char type is even allowed to be used for the loop control variable.
I can only provide you a workaround working with integer type loop control variable. It uses the Ord and Chr functions. The first returns the ordinal value of a char, the latter ASCII char from the ordinal value:
var
C: Char;
I: Integer;
begin
for I := Ord('A') to Ord('Z') do
begin
C := Chr(I);
...
end;
end;

OleVariant and String assignments in Delphi 5

I have a Delphi 5 legacy application and there's a part in which a "string" value is been assigned to an "OleVariant" variable. Something like this:
var
X: OleVariant;
S: string;
Begin
S:= ‘This string should contain 200 characters as per design’;
X:= S;
End;
If the length of “S” is greater than 128, then the value of “X” gets truncated and it only holds a maximum of 128 characters.
Is there a way to overcome this?
I believe there is a way, because if I create my own demo application from scratch (in the same PC, with the same Delphi 5), it allows me to pass longer string values and no truncating is done.
Maybe it is something about the project settings or compiler directives. I have played around with this idea, but I have no workaround yet.
Any help is appreciated. Thanks.
Demo:
procedure TForm1.Button1Click(Sender: TObject);
var
X: OleVariant;
S: string;
begin
//in the Edit I pass a string of 240 chars, let's say;
S:= Edit1.Text;
X:= S;
ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
//this showmessage shows "128 : 240"
end;
Try this OleVariantToString and StringToOleVariant functions at http://www.foxbase.ru/delphi/vzaimnye-preobrazovaniya-olevariant-i-string.htm
They work perfectly for me.
uses Classes, Variants;
function OleVariantToString(const Value: OleVariant): string;
var ss: TStringStream;
Size: integer;
Data: PByteArray;
begin
Result:='';
if Length(Value) = 0 then Exit;
ss:=TStringStream.Create;
try
Size := VarArrayHighBound (Value, 1) - VarArrayLowBound(Value, 1) + 1;
Data := VarArrayLock(Value);
try
ss.Position := 0;
ss.WriteBuffer(Data^, Size);
ss.Position := 0;
Result:=ss.DataString;
finally
VarArrayUnlock(Value);
end;
finally
ss.Free;
end;
end;
function StringToOleVariant(const Value: string): OleVariant;
var Data: PByteArray;
ss: TStringStream;
begin
Result:=null;
if Value='' then Exit;
ss:=TStringStream.Create(Value);
try
Result := VarArrayCreate ([0, ss.Size - 1], varByte);
Data := VarArrayLock(Result);
try
ss.Position := 0;
ss.ReadBuffer(Data^, ss.Size);
finally
VarArrayUnlock(Result);
end;
finally
ss.Free;
end;
end;
One explanation is that OleVariant holds the entire string but that you are looking at the debugger tooltip. In older Delphi versions the debugger tooltip truncates at 128 characters for strings held in a variant. Note that the debugger tooltip for a plain string does not truncate at this length. Try showing the variant in a dialog box and you will see that the entire string is present.
I checked this out on Delphi 6 and there was no truncation with your code (other than the debugger tooltip). Andreas did likewise on Delphi 4 and Rodrigo did so with Delphi 5. I cannot imagine that it could really be the case that strings in a Delphi 5 OleVariant are truncated at 128 characters.
If you really are seeing what you are report then I can think of the following explanations:
Your code is erroneously truncating the string, but you have not yet found the code that does this. Only you can debug that.
You have a local bug private to your Delphi installation. Are you by any chance compiling your own RTL?
I made this work. Summary: instead of filling an “OleVariant” with a “string”; I filled a “Variant” and then typecasted that “Variant” to “OleVariant”. Take a look at the code below so that you can get the idea.
procedure TForm1.Button1Click(Sender: TObject);
var
//X: OleVariant;
X: Variant;
S: string;
begin
//Let's say in the Edit1 I pass a string of 240 chars,
S:= Edit1.Text;
X:= S;
//ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
ShowMessage(IntToStr(Length(OleVariant(X))) + ' : ' + IntToStr(Length(S)));
//This ShowMessage shows "128 : 240"
end;
Honestly, I don’t know for sure why this makes a difference, but it does. It works ok now.
Thanks a lot for your help folks!

Inno Setup comma error

I have GetVersion function in [Code] that returns a string like this "1004", "1003", etc.
I created this function to check the registry value for lowest version numbers and uninstall them.
Here is a snippet of the code it is giving error point to StrtoInt conversion line stating
Comma (,) expected
Here is the snippet:
function DoesOldVersionsExist(): Boolean;
var
AppVersion: integer;
mstr: string;
VersionInstalled: cardinal;
begin
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
...
after that line I'm simple comparing the values and return true or false. Much Appreciated.
This is what error message says:
Line 55
Column 40.
Comma (,) expected
Thanks Deanna but unfortunately that is the error message pointing to this :
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
^
Here is the GetVersion function:
function GetVersion(AppVersion: String): String;
var
Version: String;
CharIndex: integer;
c: char;
begin
for CharIndex := 1 to Length(AppVersion) do begin
c := AppVersion[CharIndex];
if (c <> '.') then
Version := Version + c;
end;
Result := Version;
end;
I think that you can't just have Inno Setup constants in code like this, you have to use ExpandConstant():
AppVersion := StrToInt(GetVersion(ExpandConstant('{#MyAppVersion}')), 0);
You have not given us enough information to give a definite answer, but I think that the situation is as follows.
You have defined some constant called MyAppVersion which you let the ISPP (the Inno Setup pre-processor) substitute. Now, you have not told us what type this variable is, and you have not told us what the signature of GetVersion is (in particular, what type of argument does it expect?). However, if these types are strings, you need to write
StrToInt(GetVersion('{#MyAppVersion}'), 0);
in order to obtain, say,
StrToInt(GetVersion('Some string, this is!'), 0);
instead of
StrToInt(GetVersion(Some string, this is!), 0);
which is malformed (indeed, to such an extent that it hurts my eyes to look at it).

How do I load a Unicode string into a TTreeView via a TMemoryStream?

I need to do something with a Unicode string of a tree of a TTreeView, so I want to load this string into a memory stream and then load the memory stream into the tree view. How can I do this?
You be tempted to use directly the TStringStream class intead of a TMemoryStream. But this TStringStream class will encode the UnicodeString into an AnsiString before storage, in the Unicode Delphi version...
So here are some functions to create a TMemoryStream instance with pure Unicode content, then retrieve back this text:
function StringToMemoryStream(const Text: string): TMemoryStream;
var Bytes: integer;
begin
if Text='' then
result := nil else
begin
result := TMemoryStream.Create;
Bytes := length(Text)*sizeof(Char);
result.Size := Bytes;
move(pointer(Text)^,result.Memory^,Bytes);
end;
end;
function MemoryStreamToString(MS: TMemoryStream): string;
begin
if MS=nil then
result := '' else
SetString(result,PChar(MS.Memory),MS.Size div sizeof(Char));
end;
Be sure that you Free the TMemoryStream when you won't need it any more.
By using sizeof(Char) and PChar, this code will also work with previous non-Unicode version of Delphi.

Resources