3
votes

I am trying to call a dll from Delphi XE5.

I have spent a day or so Googling for things like "Call C DLL from Delphi" and found a number of pages but nothing really helped me.

I have received examples of how to call the dll in VB:


Declare Function IxCommand Lib "IxxDLL.dll" (ByVal command As String, ByVal mailbox As String) As Integer

...

Sub Command1_Click ()
         Dim command As String * 135
         Dim mailbox As String * 135

         command = "move:a,1000"
         IxCommand( command, mailbox)
End Sub

Also calling the DLL in VC 6.0:


#include "stdafx.h"

#include "windows.h"
#include "stdio.h"
#include "string.h"

typedef UINT (CALLBACK* LPFNIXDLLFUNC)(char *ixstr, char *mbstr);

int main(int argc, char* argv[])
{
    HINSTANCE hDLL;              // Handle to DLL
    LPFNIXDLLFUNC lpfnIxDllFunc; // Function pointer

    hDLL = LoadLibrary( "IxxDLL.dll" );

    if (hDLL == NULL) // Fails to load Indexer LPT
    {
        printf("Can't open IxxDLL.dll\n");
        exit(1);
    }
    else // Success opening DLL - get DLL function pointer
    {
        lpfnIxDllFunc = (LPFNIXDLLFUNC)GetProcAddress(hDLL, "IxCommand");
    }

    printf( "Type Indexer LPT command and press <Enter> to send\n" );
    printf( "Type \"exit\" and press <Enter> to quit\n\n" );

    while( 1 )
    {
        char ix_str[135];      // String to be sent to Indexer LPT
        char mailbox_str[135]; // Results from call into Indexer LPT

        gets( ix_str ); // Get the string from the console
        if( _stricmp( ix_str, "exit" ) == 0 ) // Leave this program if "exit"
            break;
        lpfnIxDllFunc( ix_str, mailbox_str ); // Otherwise call into Indexer LPT
        printf( "%s\n\n", mailbox_str );      // Print the results
    }

    FreeLibrary( hDLL );
    return 0;
}

A complication I have noticed is the need to define the size of the memory allocation before calling the DLL, as shown above.

The dll takes a command in the first argument and returns result text in the second argument.

Here is the Delphi code I have generated to try and call the DLL. I know the dll loads because it has a splash screen that shows. No error is generated when I call the dll. You will see that I used arrays to allocate space and then assigned their locations to Pchar variables. I do not have any header file for the original dll, nor the source code. You will see I declared the external function using stdcall but I have also tried cdecl with no change.

The Problem: The information returned in arg2 is not the expected text string but a string of what translates as non-english characters (looks like chinese).

I am guessing I am not sending the dll the correct variable types.

The Question: Can anyone help me formulate the declaration of the external function and use it correctly, so that I get back the text strings as desired?

See below:


function IxCommand (command : PChar; mailbox : PChar) : Integer; stdcall; external 'IxxDLL.dll';

...

procedure TfrmXYZ.btn1Click(Sender: TObject);

var
  LocalResult : Integer;
  arg1,
  arg2        : PChar;


  ArrayStrCmd : array[0..134] of char;
  ArrayStrMbx : array[0..134] of char;

begin

  ArrayStrCmd := 'Accel?:a' + #0;
  ArrayStrMbx := '          ' + #0;
  arg1 := @ArrayStrCmd;
  arg2 := @ArrayStrMbx;


  LocalResult := IxCommand(arg1, arg2);

end;

2

2 Answers

4
votes

The problem is character encodings. In Delphi 2009+, PChar is an alias for PWideChar, but the DLL is using Ansi character strings instead, so you need to use PAnsiChar instead of PChar.

Try this:

function IxCommand (command : PAnsiChar; mailbox : PAnsiChar) : Integer; stdcall; external 'IxxDLL.dll';

...

procedure TfrmXYZ.btn1Click(Sender: TObject);
var
  LocalResult : Integer;
  ArrayStrCmd : array[0..134] of AnsiChar;
  ArrayStrMbx : array[0..134] of AnsiChar;
begin
  ArrayStrCmd := 'Accel?:a' + #0;
  LocalResult := IxCommand(ArrayStrCmd, ArrayStrMbx);
end;

Alternatively:

function IxCommand (command : PAnsiChar; mailbox : PAnsiChar) : Integer; stdcall; external 'IxxDLL.dll';

...

procedure TfrmXYZ.btn1Click(Sender: TObject);
var
  LocalResult : Integer;
  ArrayStrCmd,
  ArrayStrMbx : AnsiString;
begin
  SetLength(ArrayStrCmd, 135);
  StrPCopy(ArrayStrCmd, 'Accel?:a');
  SetLength(ArrayStrMbx, 135);
  LocalResult := IxCommand(PAnsiChar(arg1), PAnsiChar(arg2));
end;
2
votes

You are using a Unicode version of Delphi for which Char is an alias to a 16 bit WideChar, and PChar is an alias to PWideChar.

Simply replace Char with AnsiChar and PChar with PAnsiChar.

function IxCommand(command, mailbox: PAnsiChar): Cardinal; 
  stdcall; external 'IxxDLL.dll';

The return value is UINT which maps to Cardinal in Delphi.

The calling code you have used is needlessly complex. I'd do it like this:

var
  retval: Cardinal;
  mailbox: array [0..134] of AnsiChar;
....
retval := IxCommand('Accel?:a', mailbox);
// check retval for errors

As you observe, there is scope for buffer overrun. I'm not sure how you are meant to guard against that. Documentation for the library, if it exists, would presumably explain how.