tags:

views:

277

answers:

2

I have a dll RL6_dll.dll from a routing program RouteLogix that is used to plan trucks etc.

Now we want to use that from Delphi 2007. We have a c++ header for the dll and a working example that use it in C++-Builder.

Here is an example from that file:

// Use this routine to find the directory where the data-xxx subdirectories
// are expected.
// char * vszBuf    - address of a character array to receive the (null-terminated) path.
// int    nBufSize  - is the size of the array
//                   (internally we allow paths up to 256 characters long)

DllFn(void) RL6_GetLocalGeoDir(char *vszBuf, int nBufSize);

My try from Delphi:

procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
  s1: PChar;
  IntValue : Integer;
  RL6_GetLocalGeoDir: function(vszBuf: pchar; nBufSize: Integer): integer; stdcall;
begin
  handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
  if handle <> 0 then
  begin
      @DllFn := GetProcAddress(handle, 'RL6_PREINIT');
      @RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');

      s1 := '                                                                                                                                        ';
      IntValue := length (s1);
      RL6_GetLocalGeoDir (s1, IntValue);
      showMessage(s1);
  end;
end;

So now I expect s1 contains a string, but instead the functions seems handle IntValue as string. It seems like the s1 and IntValue parameters are exchanged. We have of course tried RL6_GetLocalGeoDir (IntValue, s1) but that didn't work either. Any suggestions how to call it ?

A: 

You need to call the procedure with a preallocated buffer, and with the correct declaration, like so:

procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
  s: AnsiString;
  IntValue : Integer;
  RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); stdcall;
begin
  handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
  if handle <> 0 then
  begin
      @DllFn := GetProcAddress(handle, 'RL6_PREINIT');
      @RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');

      IntValue := 256;
      SetLength(s, IntValue);
      RL6_GetLocalGeoDir (PAnsiChar(s), IntValue);
      s := PAnsiChar(s);
      showMessage(s);
  end;
end;

Edit:

Your modified question still contains bad code. You use

var
  s1: PChar;

s1 := '                                                                                                                                        ';
IntValue := length (s1);

This is just wrong, as you don't provide a buffer but a pointer to a string constant in the code segment. Using this will lead to crashes, just try with for example the API function GetWindowsDirectory():

var
  P: PAnsiChar;
begin
  P := '                                                                                ';
  GetWindowsDirectory(P, 80);
end;

Running this will result in an access violation in ntddll.dll, write of an address in the code area (for example $0044C6C0).

mghie
Oops, I had simplified the example too much. The example is corrected now.
Roland Bengtsson
I don't think it's corrected, why would you call `Length()` on a `PChar`?
mghie
Better make it PAnsiChar and AnsiString because in C a "char" is a 1 byte char.
Andreas Hausladen
The OP stated Delphi 2007, but you're right.
mghie
`Length` is a perfectly valid thing to call on a `PChar`. The input gets converted to a temporary string first, but it will return the same value you'd get from `StrLen`, except that `StrLen` can be undefined when the input pointer is null.
Rob Kennedy
+1  A: 

The quest title mentions the Pascal calling convention, but the question body never comes back to that topic. Does the documentation for the DLL say it uses the Pascal calling convention? It's a very rare calling convention to use nowadays. (It was used in the Windows API in the 16-bit days, and although some headers from those times still say PASCAL today, that macro has been redefined to refer to the stdcall calling convention instead.)

You haven't shown the definition of DllFn — neither in the C code nor the Delphi code. In C, I imagine it's a macro that includes the function's calling convention, so go find that definition to confirm what's really being used. In Delphi, it looks like you're using it as a function pointer. I encourage you to use the same function names in your application as the DLL uses. It makes life easier for everyone involved — no more does anyone look at code and wonder what function is really being called.

If you confirm that the DLL really uses the Pascal calling convention, then specifying it in Delphi is as simple as changing the "stdcall" directive on your function declaration to "pascal":

RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); pascal;

I've also changed the PChar argument to use PAnsiChar because now that some versions of Delphi are Unicode, the PChar type might mean PWideChar, and you don't want that here.

Rob Kennedy