Returning a string from a C# DLL with Unmanaged Exports to Inno Setup script
I would suggest you to use the BSTR
type, which is used to be a data type for interop function calls. On your C# side you'd marshall your string as the UnmanagedType.BStr
type and on the Inno Setup side you'd use the WideString
, which is compatible with the BSTR
type. So your code would then change to this (see also the Marshalling sample
chapter of the Unmanaged Exports docs):
[DllExport("Test", CallingConvention = CallingConvention.StdCall)]
static int Test([MarshalAs(UnmanagedType.BStr)] out string strout)
{
strout = "teststr";
return 0; // indicates success
}
And on the Inno Setup side with the use of WideString
to this:
[Code]
function Test(out strout: WideString): Integer;
external 'Test@files:testdll.dll stdcall';
procedure CallTest;
var
retval: Integer;
str: WideString;
begin
retval := Test(str);
{ test retval for success }
Log(str);
end;
How do I return a string from DLL to Inno Setup Pascal Script
GetName
should return a const char *
, however it is otherwise fine as written. Note however that returning a string like this can only ever possibly work for literal string constants, as shown above. You cannot use this pattern to return a calculated string value (if you try that, it will likely crash or give corrupted data); thus getName
is wrong.
Also note that while C is case sensitive, Pascal is not, so getName
and GetName
are the same function in the Inno script. You might be getting away with this in the above case because the parameters are different, but I wouldn't rely on that -- you should give them distinct names. (Don't use the same name on the C side either, as DLL exports are sometimes looked up case-insensitively too.)
To return a calculated string, you should use a pattern like this:
DLL code:
void __stdcall CalculateName(char *buffer, size_t size)
{
strncpy(buffer, "whatever", size);
buffer[size-1] = 0;
}
Inno code:
procedure CalculateName(Buffer: AnsiString; Max: Cardinal);
external 'CalculateName@files:my.dll stdcall';
...
Max := 16;
Buffer := StringOfChar(#0, Max);
CalculateName(Buffer, Max);
SetLength(Buffer, Pos(#0, Buffer) - 1);
...
A few acceptable variations exist, for example you can make the DLL function return the number of characters actually written to the buffer, and use that in the subsequent SetLength rather than calling Pos to find the null terminator.
But you must:
- Ensure that both sides are using the same string types, either both ANSI or both Unicode.
- ANSI Inno Setup supports only ANSI strings with its
String
type. - Unicode Inno Setup supports either ANSI strings with
AnsiString
or Unicode strings withString
.
- ANSI Inno Setup supports only ANSI strings with its
- When using Unicode strings, ensure that both sides agree whether Max and/or the return value is specified in characters or bytes (the example code assumes it's in characters).
- Prior to calling the function, use either SetLength or StringOfChar to ensure that the buffer has been sized to the required maximum possible result length.
- Ensure the called function does not try to write past this maximum length (which is easier if this is provided as a parameter to the function).
- Ensure that if you're using Pos, the called function must ensure the value is null-terminated (or you need to be more careful than shown in the example).
- Ensure that after the call you truncate the string to the actual length, either by using a returned value or by finding the null terminator.
One of the constraints in play here is that memory allocated by one side must be freed by the same side. You cannot safely release memory allocated on the "wrong" side of the DLL boundary, in either direction.
How to return a string from a DLL to Inno Setup?
Here is a sample code of how to allocate a string that returns from a DLL:
[Code]
Function GetClassNameA(hWnd: Integer; lpClassName: PChar; nMaxCount: Integer): Integer;
External 'GetClassNameA@User32.dll StdCall';
function GetClassName(hWnd: Integer): string;
var
ClassName: String;
Ret: Integer;
begin
{ allocate enough memory (pascal script will deallocate the string) }
SetLength(ClassName, 256);
{ the DLL returns the number of characters copied to the buffer }
Ret := GetClassNameA(hWnd, PChar(ClassName), 256);
{ adjust new size }
Result := Copy(ClassName, 1 , Ret);
end;
Returning a string array from C# to Inno Setup
The UnmanagedType.LPArray
with string
maps to an array of pointers to char (C-style char**
). In Unicode Pascal Script that's array of PAnsiChar
. There's no way the Pascal Script can magically convert that to TArrayOfString
(array of string
).
You can convert it like this:
type
TArrayOfPAnsiChar = array of PAnsiChar;
procedure DummyMethod(out StringPtrs: TArrayOfPAnsiChar; out Count: Integer);
external 'DummyMethod@files:ArrayInno.dll stdcall';
function DummyMethodWrapper: TArrayOfString;
var
ArrayOfPAnsiChar: TArrayOfPAnsiChar;
I, Count: Integer;
begin
DummyMethod(ArrayOfPAnsiChar, Count);
SetArrayLength(Result, Count);
for I := 0 to Count - 1 do
begin
Result[I] := ArrayOfPAnsiChar[I];
end;
end;
(Tested with Unicode Inno Setup)
Side notes:
- It's not clear, who does a memory allocation for the
TArrayOfAnsiChar
and individual character arrays. I smell a memory leak, at best. - You probably want to use 16-bit character buffer, not 8-bit character buffer to allow full Unicode. Though note that this is more difficult to implement in the Pascal Script as it lacks 16-bit character pointer type.
C# string to Inno Setup
The .NET String
type definitely won't marshal to Pascal string
type. The .NET does not know anything about Pascal types.
The .NET can marshal strings to character arrays (and Pascal can marshal string from character arrays). But when the string is a return type of a function, there's problem with allocation of the memory for the string (who allocates the memory, who releases it).
That's why the solution in question you pointed to suggests you to use by ref/out parameter, because this way the caller can provide a buffer the .NET can marshal the string to. So there are no problems with allocation.
Calling a DLL function with an allocated character buffer that the function fills in Inno Setup
Pascal Script equivalent of the C declaration should be:
function GetDirVST2x86(lpString1: string): Integer;
external 'GetDirVST2x86@files:R2RINNO.DLL stdcall setuponly';
(i.e. no var
, as it is an input character pointer argument).
Assuming the function contract is that you (as a caller) allocate a buffer and provide it to the function to be filled in, you should call the function like this:
var
Buf: string;
begin
{ Allocate buffer for the result large enough according to the API specification }
SetLength(Buf, 1000);
GetDirVST2x86(Buf);
SetLength(Result, Pos(#0, Result) - 1);
end;
See also How to return a string from a DLL to Inno Setup?
Cannot Import dll when importing C# DLL in Inno Setup
Works for me, if I follow all the instructions from my answer to:
Calling .NET DLL in Inno Setup
So I would guess, that you didn't set the Platform target of your .NET/C# project to x86.
For more about returning strings from DLL, see:
Returning a string from a C# DLL with Unmanaged Exports to Inno Setup script
Inno Setup 6 cannot use DLL function with string parameters, while it works in Inno Setup 5
This is not a 5.x vs 6.x issue, nor a bitness issue. This is an Ansi vs. Unicode issue.
In 5.x, there were two versions of Inno Setup, Ansi version and Unicode version. You were probably using the Ansi version and your code is designed for it. In 6.x, there's only the Unicode version. Your code does not work in the Unicode version. By upgrading to 6.x, you inadvertently also upgraded from Ansi to Unicode.
A quick and dirty solution is to change the declaration of the getFlags
function to correctly declare the parameters to be Ansi strings (AnsiString
type):
function getFlags( secret, licenseCode : AnsiString) : Integer;
external 'getFlags@files:mydll.dll cdecl';
The correct solution would be to reimplement your DLL to use Unicode strings (wchar_t
pointers):
PS_EXPORT(int) getFlags( const wchar_t * secret, const wchar_t * license )
This is a similar question: Inno Setup Calling DLL with string as parameter.
See also Upgrading from Ansi to Unicode version of Inno Setup (any disadvantages).
Related Topics
String.Format - How It Works and How to Implement Custom Formatstrings
Which Files in a Visual C# Studio Project Don't Need to Be Versioned
Newtonsoft Inline Formatting for Subelement While Serializing
Is There a Complete Iequatable Implementation Reference
Where Is Httpcontent.Readasasync
What Are the Default Schedulers for Each Observable Operator
Why Would One Ever Use the "In" Parameter Modifier in C#
Differencebetween Xamarin.Form's Layoutoptions, Especially Fill and Expand
Center Messagebox in Parent Form
Process.Close() Is Not Terminating Created Process,C#
How to Overload the [] Operator in C#
Including Pictures in an Outlook Email
Singleton Httpclient VS Creating New Httpclient Request
How to Update a Cell Value in a Db Table, Using SQL Server Ce and C# (Visual Studio 2010)
When Not to Use Yield (Return)