Returning a String from a C# Dll with Unmanaged Exports to Inno Setup Script

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 with String.
  • 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



Leave a reply



Submit