6

I have a C# DLL which exposes a function using Unmanaged Exports which is called directly by an Inno Setup Pascal script. This function needs to return a string to Inno Setup. My question is how can I accomplish this?
My preferred method is to pass a buffer from Inno Setup to the C# function which will return the string inside this buffer. I've come up with this code:

C# function:

[DllExport("Test", CallingConvention = CallingConvention.StdCall)]
static int Test([Out, MarshalAs(UnmanagedType.LPWStr)] out string strout)
{
   strout = "teststr";
   return strout.Length;
}

Inno Setup script:

function Test(var res: String):Integer; external 'Test@files:testdll.dll stdcall';

procedure test1; 
var
    Res: String;
    l: Integer;
begin
    SetLength(Res,256);
    l := Test(Res);
    { Uncommenting the following line causes an exception }
    { SetLength(Res,l); }
    Log('"Res"');
end;

When I run this code the Res variable is empty (I see "" in the log)

How can I return a string from this DLL?

Note that I am using the Unicode version of Inno Setup. I also don't want to use COM to call this function nor to allocate a buffer in the DLL and return it to Inno Setup.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Ohad
  • 166
  • 1
  • 6
  • They don't document this. Best guess is StringBuilder without *out* and the MarshalAs attribute. If you get Chinese then use CharSet.Auto – Hans Passant Dec 25 '13 at 22:07

1 Answers1

8

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;
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
TLama
  • 75,147
  • 17
  • 214
  • 392
  • 1
    Some quirks here. No need for Out attribute. That's implied by out keyword. And pre allocating widestring is wrong. It's an out parameter. The callee will allocate with a call to SysAllocString. I would gladly edit for you. – David Heffernan Jan 16 '15 at 20:24
  • @David, thanks for your feedback! Of course feel free to edit (if that was meant so, as I don't know such idiom :) I'm not keen in interop in C# at all (it was something that I made up from available resources and tested just for if "it works"). – TLama Jan 16 '15 at 23:48
  • Try that. I'd prefer an out param in the pascal. Does Inno support out? – David Heffernan Jan 17 '15 at 08:55
  • @David, this works, thanks! And yes, Inno Setup supports `out`. – TLama Jan 17 '15 at 10:52
  • How is the memory handled with this configuration? I can't see how the c# side can request more memory if it blows that allocated by Delphi if Delphi is managing it, nor how the Delphi will be guaranteed to grab the string before it is eaten by c#'s GC if c# is managing the resource. I follow the 'normal' PChar / buffer method but this one eludes me. – Matt Allwood Mar 25 '15 at 12:04
  • 1
    To answer my above comment-question: http://stackoverflow.com/questions/9331026/why-can-delphi-dlls-use-widestring-without-using-sharemem – Matt Allwood Jun 24 '15 at 09:43
  • It seems Unmanaged Exports package breaks when using SDK style projects :( – Red Riding Hood Feb 20 '22 at 08:31
  • 1
    For people using newer SDK style projects, make sure to use the `UnmanagedExports.Repack` nuget package instead. – Red Riding Hood Feb 20 '22 at 08:59