英文:
free pascal bindings for a typedef function inside a DEFINE macro and a number outside
问题
这段代码是EyeLink眼动跟踪系统的sdl_expt.h
和sdl2_expt.h
头文件的一部分。它使用typedef
关键字定义了一个名为getExButtonStates
的函数指针类型。getExButtonStates
函数指针类型表示一个接受类型为CCDBS
的单个参数并返回整数值的函数。
此后,该代码使用#define MACRO
定义了常量。例如,它为EXTERNAL_DEV_NONE
分配了值((getExButtonStates)0)
。将宏括在括号中是为了在表达式中使用时能够正确替代。
下面是您提到的代码的翻译:
type
PCCDBS = ^CCDBS;
CCDBS = record
userdata: Pointer;
buttons: array[0..255] of Char;
internal: Pointer;
end;
TGetExButtonStatesFunction = function (ccdbs: PCCDBS): Int32; stdcall;
function getExButtonStates(ccdbs: PCCDBS): Int32; stdcall; external DLLNAME;
您提到的宏绑定可以像这样实现:
const
EXTERNAL_DEV_NONE: TGetExButtonStatesFunction = nil;
EXTERNAL_DEV_CEDRUS: TGetExButtonStatesFunction = @getExButtonStates;
EXTERNAL_DEV_SYS_KEYBOARD: TGetExButtonStatesFunction = @getExButtonStates;
这里,我们为每个宏定义了对应的函数指针变量,并将其设置为nil
或@getExButtonStates
,具体取决于您想要的宏的行为。
英文:
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.
答案1
得分: 1
从EyeLink程序员指南第311页的第26.23.3.2节中可以看出,这些常量仅用作传递给int enable_external_calibration_device (getExButtonStates buttonStatesfcn, const char* config, void* userData)
函数的虚拟指针值,以指示您只想使用特定的内置功能,而不是提供指向您真正回调的指针。引用:“启用非键盘设备用于校准控制。buttonStatesfcn
— 回调函数读取设备并返回适当的数据。使用EXTERNAL_DEV_NONE
来禁用设备。使用EXTERNAL_DEV_CEDRUS
来支持内置的Cedrus设备。”
正如Alan Birtles在他的评论中指出的,这些宏只是将整数值(0
、1
、2
)强制转换为指向函数类型(C代码中的getExButtonStates
)的指针。
“它们是否覆盖了指定的int函数结果?” — 不,函数结果与此无关。让我试着解释一下。
首先,enable_external_calibration_device()
函数允许您提供一个回调函数。回调函数是库在某些时候(可能是重复调用)将调用的函数。假设您已创建了一个函数(正如文档所解释的)“读取设备并返回适当的数据”(也许它填充按钮状态数组)。因此,您通过提供其地址将您的函数注册为回调,然后在以后的某个时候,库将在需要时调用您的函数,您的函数应该执行预期的操作(例如,使用接收到的CCDBS
结构的指针来填充其数据,并返回一些成功/错误代码;我不熟悉EyeLink的具体细节)。
但是,现在假设您根本不想提供任何回调函数。相反,您只想禁用校准设备,或者可能想使用内置的Cedrus设备(不管那是什么)。那么您根本就没有函数的地址。您可以传递0
(以禁用)或1
(用于Cedrus),而不是地址。
但是,如果您只关心这些EXTERNAL_DEV_…
宏的语法,那很简单。由于enable_external_calibration_device()
函数期望其第一个参数是指向函数的指针,因此您不能只是传递一些整数,例如1
或2
,而不是指向函数的指针。 (尽管0
是一个特例,可以工作,因为它隐式可转换为nullptr
,但这是另一回事。)这样的代码将无法编译;编译器将产生错误,例如“无法将int
转换为getExButtonStates
”,可能会略有不同的措辞。
因此,为了克服这个限制,这些宏使用了在C++中有时称为“神强制”的技巧,因为它允许将任何东西转换为几乎任何其他东西,压制编译器(就好像在说“我确实知道我在做什么,请相信我并只是按照我的说法去做”)。在C语言中,这是唯一可用的类型转换方式,因此它被广泛使用。相反,在C++语言中,这被认为是一种非常糟糕的做法,应尽可能避免使用。但是,您展示的代码确实是C代码,它具有像typedef struct _CDDBS { … } CDDBS;
这样的特殊技巧,这在C++中是不需要的(在C++中,只需struct CDDBS { … };
就足够了)。
现在,关于Free Pascal,我不确定是否甚至可以将纯整数参数传递给期望指向函数的指针参数的函数。(我最后一次使用Pascal是25年前,抱歉。)但是,您已经在评论中提到您已经找到了如何绕过这个限制并传递整数常量的方法。
此外,正如您已经理解的那样,您的function getExButtonStates(ccdbs: PCCDBS): Int32; sdtcall; external DLLNAME;
是不正确的,因为在他们的DLL中没有导出这样的函数。该函数应由他们DLL的用户创建,仅在涉及自定义外部校准设备时才会存在。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论