c++interopheader-filesfreepascal

free pascal bindings for a typedef function inside a DEFINE macro and a number outside


This code is part of the sdl_expt.h and sdl2_expt.h headers, from EyeLink eye-tracking systems. It defines a function pointer type named getExButtonStates using the typedef keyword. The getExButtonStates function pointer type represents a function that takes a single argument of type CCDBS and returns an integer value.

Following that, the code defines constants using #define MACRO. For example, It assigns the value ((getExButtonStates)0) for EXTERNAL_DEV_NONE. The macro is enclosed in parentheses to ensure proper substitution when used in expressions.

typedef struct _CCDBS { // calibration control device button state structure
    void *userdata;   // user data passed in with enable_external_calibration_device
    char buttons[256];  // set button[i] to 1 for pressed 0 to released
    void *internal;   // for internal use.
}CCDBS;

typedef int (ELCALLBACK *getExButtonStates)(CCDBS *);  

#define EXTERNAL_DEV_NONE ((getExButtonStates)0)
#define EXTERNAL_DEV_CEDRUS ((getExButtonStates)1)
#define EXTERNAL_DEV_SYS_KEYBOARD ((getExButtonStates)2)

So, right now, I am binding this piece of code as such:

type
  PCCDBS = ^CCDBS;
  CCDBS = record
    userdata: Pointer;
    buttons: array[0..255] of Char;
    internal: Pointer;
  end;

  TGetExButtonStatesFunction = function (ccdbs: PCCDBS): Int32; sdtcall;

  function getExButtonStates(ccdbs: PCCDBS): Int32; sdtcall; external DLLNAME; 

However, I am really not sure how to binding the defined macros.


Solution

  • From section 26.23.3.2 on page 311 of the EyeLink Programmer's Guide, it appears that those constants are only used as dummy pointer values passed to the int enable_external_calibration_device (getExButtonStates buttonStatesfcn, const char* config, void* userData) function to indicate that instead of providing a pointer to your real callback, you just want to use a specific built-in functionality. Quote: "Enables non-keyboard devices to be used for calibration control. buttonStatesfcn — callback function reads the device and returns appropriate data. Use EXTERNAL_DEV_NONE to disable the device. Use EXTERNAL_DEV_CEDRUS for built-in Cedrus device support."

    As Alan Birtles has pointed out in his comment, those macros are just casting the integer values (0, 1, 2) to the pointer-to-function type (getExButtonStates in the C code).

    "Are they overriding the function result to the specified int?" — No, the function result is not involved here. Let me try to explain.

    First, the enable_external_calibration_device() function allows you to provide a callback. A callback is a function that will be called by the library at some point (perhaps repeatedly). Suppose you have created a function that (as the doc explains) "reads the device and returns appropriate data" (perhaps it fills the button states array). So, you "register" your function as a callback by providing enable_external_calibration_device() with its address. Then, at some later time(s), the library will call your function when needed, and your function should do what's expected (e.g., use the received pointer to a CCDBS structure to fill its data, and return some success/error code; I'm not familiar with EyeLink).

    But now, suppose you do not wish to provide any callback function at all. Instead, suppose you just want to disable the calibration device, or maybe you want to use the built-in Cedrus device (whatever that means). Then you don't have a function's address, of course. You pass either 0 (to disable) or 1 (for Cedrus) instead of an address.

    But if you are only interested in understanding the syntax of these EXTERNAL_DEV_… macros, it's simple. Since the enable_external_calibration_device() function expects a pointer-to-function as its first argument, you cannot just pass some integer, such as 1 or 2, instead of a pointer-to-function. (Although 0 is a special case that will work, because it is implicitly convertible to nullptr, but that's another story.) Such a code will not compile; the compiler will give an error such as "no known conversion from int to getExButtonStates possible", maybe worded slightly differently.

    So, to overcome this restriction, those macros are using what is sometimes called a "god-cast" in C++, because it allows to convert anything to almost anything else, shutting up the compiler (as if saying "I do really know what I'm doing, trust me and just do what I say"). In C language, this is the only kind of casting available, and so it is used extensively. In C++ language, on the contrary, this is considered a really bad practice, which should be always avoided if possible. But the code you've shown is really in C, it has specific tricks like typedef struct _CDDBS { … } CDDBS;, which are not needed in C++ (just struct CDDBS { … }; would be sufficient in C++).

    Now, regarding Free Pascal, I'm not sure if it is even possible to pass a plain integer argument to a function that expects a pointer-to-function parameter. (The last time I've used Pascal was 25 years ago, sorry.) However, you've mentioned in a comment that you've already found out how to work around this restriction and pass the integer constants.

    Also, as you have already understood, your function getExButtonStates(ccdbs: PCCDBS): Int32; sdtcall; external DLLNAME; is incorrect, because there is no such function exported in their DLL. The function is supposed to be created by the user of their DLL, and only if a custom external calibration device will be involved.