Question

I have two C functions in a DLL which are defined in the definition file and are exported for using in Inno Setup.

char* __stdcall GetName()
{
        return "Kishore";
}
void __stdcall getName(char* strName)
{
     strcpy(strName, "Kishore");
}

The Inno Setup code will load the custom DLL and call the function/procedure to return the names

{ Inno Setup script }
[Code]
procedure  getName(MacAddress: String);
external 'getName@files:MyDll.dll stdcall setuponly';

function  GetName():PAnsiChar;
external 'GetName@files:MyDll.dll stdcall setuponly';

function NextButtonClick(CurPage: Integer): Boolean;
var
  StrName: String;
begin
  SetLength(StrName,15);    
  getName(StrName); { displaying only single character }
  StrName := GetName(); { this call is crashing }
end

How can I retrieve the name in Inno Setup script without it crashing?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top