0

I am trying to get a c-based DLL working in Delphi. I thought I'd try to get one of the functions to work before I set in on all the others, and I'm stymied. Here's the C call:

int FindCityCounty(char *zip, char cityname[28], char state[2], char countyname[25],
    char countynum[3]);

I'm confused by the notion of char cityname[25]. Is that:

array[0..24] of Char/AnsiChar;

?

What's the proper way to translate these constructs? All my attempts have ended in jibberish.

UPDATE:

Here's an example of the function in question being called:

#include <stdio.h>
#include <Windows.h>
#include <srv.h>        /* for CALLBACK, HINSTANCE */
#include <direct.h>     /* for _getdrive and _getdcwd */

#define cpy_blank(word,len) memset(word,' ',len)
#define cpy_word strncpy

typedef int (CALLBACK* FindCityCounty)(
        char ZIP[5],char cityname[28],char state[2],char countyname[25],char countynum[3]);


void runCA(FindCityCounty pCA)
{
    char ZIP[5], cityname[28], state[2], countyname[25], countynum[3];
    int rc;

    printf("CorrectAddress C API Demonstration\n\n");
    printf("FindCityCounty() function call\n");
    printf("==================================\n");

    /* set input parameters */  
    cpy_word(ZIP,"10601",5);

    printf("Input ZIP code: %.5s\n", ZIP);

    /* call function */
    rc = pCA(ZIP, cityname, state, countyname, countynum);


    /*** Now output results ***/
    printf("Output city: %.28s\n",cityname);
    printf("Output state: %.2s\n",state);
    printf("Output county name: %.25s\n",countyname);
    printf("Output county number: %.3s\n",countynum);

    printf("\n\n");
    printf("=====================================\n");
    printf("(c) Intelligent Search Technology Ltd\n");
    printf("=====================================\n");
}

int main() 
{                 
    HINSTANCE hDLL;
    FindCityCounty pCA;

    /* Load DLL and get function location */
    hDLL = LoadLibrary("CorrectA.dll");
    pCA = (FindCityCounty)GetProcAddress(hDLL,"FindCityCounty");

    runCA(pCA);

    getch();
    return 0;
}

Delphi Code:

   unit CorrectAdressAPI;

interface

type
  TZip        = array[0..4] of AnsiChar;
  TCityName   = array[0..27] of AnsiChar;
  TState      = array[0..1] of AnsiChar;
  TCountyName = array[0..24] of AnsiChar;
  TCountyNum  = array[0..2] of AnsiChar;

  PCityName   = ^TCityName;
  PState      = ^TState;
  PCountyName = ^TCountyName;
  PCountyNum  = ^TCountyNum;


type
  TFindCityCounty = function(aZip: PAnsiChar;
                         var aCity: TCityName;
                         var aState: TState;
                         var aCounty: TCountyName;
                         var aCountyNum: TCountyNum): Integer; stdcall;

  function Load(const AFileName : string) : Boolean;
  function Unload : Boolean;

   function FindCityCounty(aZip: PANSIChar;
                         var aCity: TCityName;
                         var aState: TState;
                         var aCounty: TCountyName;
                         var aCountyNum: TCountyNum): Integer;


implementation

uses
  Winapi.Windows, dialogs, SysUtils;

var
  hDll : THandle;
  PFindCityCounty: TFindCityCounty;

function Load(const AFileName : string) : Boolean;
begin
  hDll := LoadLibrary(PChar(AFileName));
  Result := hDll <> 0;
  if Result then
  begin
    PFindCityCounty := GetProcAddress(hDLL, PAnsiChar('FindCityCounty'));
    Assert(Assigned(PFindCityCounty));

  end;
end;

function Unload : Boolean;
begin
  Result := (hDll <> 0) and FreeLibrary(hDll);
end;


function  FindCityCounty(aZip: PANSIChar;
                         var aCity: TCityName;
                         var aState: TState;
                         var aCounty: TCountyName;
                         var aCountyNum: TCountyNum): Integer;
begin
  Result := PFindCityCounty(aZip, aCity, aState, aCounty, aCountyNum);
end;



end.
Nick Hodges
  • 16,902
  • 11
  • 68
  • 130
  • `CALLBACK` is `stdcall`. I don't really think your update is valid. Your original question was well answered three times. The update is a quite different question. Quite easy to answer, but it's not fair to keep adding new questions like that. – David Heffernan Dec 18 '14 at 21:38
  • My apologies -- just trying to give the best information. You did ask for an example of it being called.... – Nick Hodges Dec 18 '14 at 21:43
  • I said, *I think the question you have asked has been answered. If you want help with the calling of the function you need a new question.* Anyway, it's pretty simple to translate it. Surely you can get there from here. Use @Deltics code but it's `stdcall`. – David Heffernan Dec 18 '14 at 21:44
  • Your example might be [`something like this`](http://pastebin.com/U40gwpip). – TLama Dec 18 '14 at 22:52
  • @Nick - It would be useful to see the Delphi code that is failing, as well as this original, equivalent C code. – Deltics Dec 18 '14 at 23:03
  • @TLama Your code copies null terminators and so overruns buffers – David Heffernan Dec 18 '14 at 23:34
  • @David, did you address (the only) usage of `StrLCopy` I've had in that code (and which I've replaced with `Move` now) ? – TLama Dec 19 '14 at 10:48
  • @TLama Yes that is the case. Looks clean now. – David Heffernan Dec 19 '14 at 11:29
  • @Deltics -- Thanks for the help again -- code posted. – Nick Hodges Dec 19 '14 at 14:37
  • @TLama -- Thanks for the translation. It looks perfect, but alas, I get nothing but junk back. – Nick Hodges Dec 19 '14 at 15:11
  • I agree with David that we are now so diverged from the original question that a new, separate question would be most appropriate for the issues with the actual DLL call you are now facing. But, in pursuit of that latter problem... when you say you get "junk" back, does this mean you no longer AV but get a successful call, with an unexpected result ? Do you have documentation for the API you are calling ? Are those array's of chars expected to contain values formatted in a particular way perhaps ? Some special terminator or padding ? – Deltics Dec 19 '14 at 20:19

2 Answers2

5

As Mason says, arrays passed as parameters in 'C' are always passed as a pointer to the declared array type. However, a char array of fixed size is not certain to be a null terminated array, so using a PChar, PWideChar or PANSIChar would be potentially wrong.

To answer your specific question regarding the dimensions of the arrays, you appear to have mixed up your city and county names in your example, but essentially you are correct. C arrays are 0-based so the C declaration:

char name[N]

is equivalent to the Pascal:

name: array[0..N-1] of Char;

But getting the correct number of elements in the array is not the end of the story. You also need to be careful to ensure your array element type is declared correctly.

In the case of the char type you need to be careful that 'Char' is the correct type for the corresponding C environment when converting any C code. In Delphi 1-2007 'Char' is a single byte ANSIChar. In Delphi 2009 onwards 'Char' is a 2-byte WideChar.

In most (if not all) C implementations, certainly for Windows, char is an 8-bit/1-byte value, so an ANSIChar is most likely to be the appropriate Pascal type and ensures a 1-byte char on any Delphi version.

Returning to your specific example, the fact that explicitly dimensioned arrays are involved, rather than explicitly (or implicitly) null terminated strings, I think it may be advisable in this particular case to declare types for each of the required array types, with corresponding pointer types:

type
  TCityName   = array[0..27] of ANSIChar;
  TState      = array[0..1] of ANSIChar;
  TCountyName = array[0..24] of ANSIChar;
  TCountyNum  = array[0..2] of ANSIChar;

  PCityName   = ^TCityName;
  PState      = ^TState;
  PCountyName = ^TCountyName;
  PCountyNum  = ^TCountyNum;

You then have two choices in your Pascal version of the C function declaration. You could explicitly use the implicit pointer types that the C code produces 'under the hood':

 function FindCityCounty(aZip: PANSIChar; 
                         aCity: PCityName; 
                         aState: PState; 
                         aCounty: PCountyName; 
                         aCountyNum: PCountyNum): Integer; cdecl;

Or you could use the array types themselves, but declare them as var parameters to co-erce the Pascal compiler to pass them by reference, thus producing implied pointers, as per the C code:

 function FindCityCounty(aZip: PANSIChar; 
                         var aCity: TCityName; 
                         var aState: TState; 
                         var aCounty: TCountyName; 
                         var aCountyNum: TCountyNum): Integer; cdecl;

The effect is essentially the same in both cases, though personally I would favour the explicit pointers since the use of var parameters overtly suggests that the parameter values may/will be modified by the function call which is almost certainly not the case.

Note that the zip parameter is already being passed explicitly as a pointer to a char in the C code, so using the PANSIChar pointer type here is appropriate for this parameter in either case.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Yep, I wasn't sure about the reliability of 8-bit char types in 'C' . Having checked, it is indeed only certain to be a *minimum* of 8 bits. But in the context of a Windows C compiler it is almost certainly an 8-bit char so I have updated the answer accordingly. – Deltics Dec 18 '14 at 20:37
  • Thank you -- this was helpful, but I'm still getting an AV when I try it. Argh! – Nick Hodges Dec 18 '14 at 20:40
  • On Windows, `char` is always 8 bits. That's the platform ABI. You have to look hard for a platform with a 16 bit `char`. http://stackoverflow.com/questions/2098149/what-platforms-have-something-other-than-8-bit-char – David Heffernan Dec 18 '14 at 20:42
  • @NickHodges We don't know how the function is meant to be called, who allocates the memory, how much needs to be allocated and so on. A function prototype is seldom enough information to specify the contract. We also don't know the calling convention. We presume `cdecl` since that is the C default. The first parameter is `char *zip` which implies modifiable. But that's surely wrong. Surely it should be `const char *zip`. I think the question you have asked has been answered. If you want help with the calling of the function you need a new question. – David Heffernan Dec 18 '14 at 20:44
  • @DavidHeffernan -- undestood. Imagine how I feel. ;-) I'm going to get with the vendor to see if I can find out any more, particularly about the calling convention. – Nick Hodges Dec 18 '14 at 20:45
  • @NickHodges Do you have the header file? If you have that it's all good. And an example call to the function usually tells us all we need to know. – David Heffernan Dec 18 '14 at 20:46
  • I do have the header file. But I'm utterly ignorant about how to read a header file other than the most basic C stuff. I've updated the question with an example of it being called. – Nick Hodges Dec 18 '14 at 21:22
2

If this was a struct, you would be correct. But in C, arrays as function parameters are always pointers. According to this tutorial, this is still the case even in an array parameter with an explicit size given. So this should be translated as PChar or PAnsiChar, as appropriate.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477