当前位置: 首页 > news >正文

LuaJIT 学习(4)—— FFI 语义

文章目录

    • C Language Support
    • C Type Conversion Rules
      • Conversions from C types to Lua objects
        • 例子:访问结构体成员
      • Conversions from Lua objects to C types
      • Conversions between C types
        • 例子:修改结构体成员
      • Conversions for vararg C function arguments
    • Initializers
        • 例子:结构体初始化
        • 例子:固定大小数组初始化
        • 例子:变长数组初始化
        • 例子:字节数组初始化
        • 例子:嵌套结构体初始化
    • Table Initializers
        • 例子:使用 table 初始化数组或结构体
    • Operations on cdata Objects
      • Indexing a cdata object
      • Calling a cdata object
      • Arithmetic on cdata objects
      • Comparisons of cdata objects
      • cdata objects as table keys
    • Garbage Collection of cdata Objects
    • Callbacks
      • Callback resource handling
      • Callback performance
    • C Library Namespaces
    • No Hand-holding!
    • Current Status

Given that the FFI library is designed to interface with C code and that declarations can be written in plain C syntax, it closely follows the C language semantics, wherever possible. Some minor concessions【轻微的让步】 are needed for smoother interoperation with Lua language semantics.

C Language Support

【忽略复数和向量类型】

The FFI library has a built-in C parser with a minimal memory footprint. It’s used by the ffi.* library functions to declare C types or external symbols.

Its only purpose is to parse C declarations, as found e.g. in C header files. Although it does evaluate constant expressions, it’s not a C compiler. The body of inline C function definitions is simply ignored.

Also, this is not a validating C parser. It expects and accepts correctly formed C declarations, but it may choose to ignore bad declarations or show rather generic error messages. If in doubt, please check the input against your favorite C compiler.

The C parser complies to the C99 language standard plus the following extensions:

  • The '\e' escape in character and string literals.
  • The C99/C++ boolean type, declared with the keywords bool or _Bool.
  • Unnamed (‘transparent’) struct/union fields inside a struct/union.
  • Incomplete enum declarations, handled like incomplete struct declarations.
  • Unnamed enum fields inside a struct/union. This is similar to a scoped C++ enum, except that declared constants are visible in the global namespace, too.
  • Scoped static const declarations inside a struct/union (from C++).
  • Zero-length arrays ([0]), empty struct/union, variable-length arrays (VLA, [?]) and variable-length structs (VLS, with a trailing VLA).
  • C++ reference types (int &x).
  • Alternate GCC keywords with ‘__’, e.g. __const__.
  • GCC __attribute__ with the following attributes: aligned, packed, mode, vector_size, cdecl, fastcall, stdcall, thiscall.
  • The GCC __extension__ keyword and the GCC __alignof__ operator.
  • GCC __asm__("symname") symbol name redirection for function declarations.
  • MSVC keywords for fixed-length types: __int8, __int16, __int32 and __int64.
  • MSVC __cdecl, __fastcall, __stdcall, __thiscall, __ptr32, __ptr64, __declspec(align(n)) and #pragma pack.
  • All other GCC/MSVC-specific attributes are ignored.

The following C types are predefined by the C parser (like a typedef, except re-declarations will be ignored):

  • Vararg handling: va_list, __builtin_va_list, __gnuc_va_list.
  • From <stddef.h>: ptrdiff_t, size_t, wchar_t.
  • From <stdint.h>: int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, intptr_t, uintptr_t.
  • From <unistd.h> (POSIX): ssize_t.

You’re encouraged to use these types in preference to compiler-specific extensions or target-dependent standard types. E.g. char differs in signedness and long differs in size, depending on the target architecture and platform ABI.

The following C features are not supported:

  • A declaration must always have a type specifier; it doesn’t default to an int type.
  • Old-style empty function declarations (K&R) are not allowed. All C functions must have a proper prototype declaration. A function declared without parameters (int foo();) is treated as a function taking zero arguments, like in C++.
  • The long double C type is parsed correctly, but there’s no support for the related conversions, accesses or arithmetic operations.
  • Wide character strings and character literals are not supported.
  • See below for features that are currently not implemented.

C Type Conversion Rules

Conversions from C types to Lua objects

These conversion rules apply for read accesses to C types: indexing pointers, arrays or struct/union types; reading external variables or constant values; retrieving return values from C calls:

InputConversionOutput
int8_t, int16_t→sign-ext int32_tdoublenumber
uint8_t, uint16_t→zero-ext int32_tdoublenumber
int32_t, uint32_tdoublenumber
int64_t, uint64_tboxed value64 bit int cdata
double, floatdoublenumber
bool0 → false, otherwise trueboolean
enumboxed valueenum cdata
Pointerboxed valuepointer cdata
Arrayboxed referencereference cdata
struct/unionboxed referencereference cdata

Bitfields are treated like their underlying type.

Reference types are dereferenced before a conversion can take place — the conversion is applied to the C type pointed to by the reference.

例子:访问结构体成员
local ffi = require("ffi")ffi.cdef [[typedef enum {RED = 1,GREEN = 2,BLUE = 3
} Colors;typedef struct {float x, y;
} point;typedef struct {int8_t a;uint16_t b;uint32_t c;uint64_t d;double e;float f;bool g;Colors h;int *i;char j[100];point k;
} t;
]]local function print_type(v)if type(v) ~= "cdata" thenprint(type(v))elseprint(v)end
endlocal t = ffi.new("t")
print_type(t)   -- cdata<struct 106>: 0x7fa50ecd6228
print_type(t.a) -- int8_t -> number
print_type(t.b) -- uint16_t -> number
print_type(t.c) -- uint32_t -> number
print_type(t.d) -- uint64_t -> 0ULL 【64 bit int cdata】
print_type(t.e) -- double -> number
print_type(t.f) -- float -> number
print_type(t.g) -- bool -> boolean
print_type(t.h) -- enum -> cdata<enum 97>: 0 【boxed value】
print_type(t.i) -- pointer -> cdata<int *>: NULL 【boxed value】
print_type(t.j) -- array -> cdata<char (&)[100]>: 0x7fa50ecd6258 【boxed reference】
print_type(t.k) -- struct -> cdata<struct 102 &>: 0x7fa50ecd62bc 【boxed reference】

会发生 C 类型到 Lua 类型的转换

Conversions from Lua objects to C types

These conversion rules apply for write accesses to C types: indexing pointers, arrays or struct/union types; initializing cdata objects; casts to C types; writing to external variables; passing arguments to C calls:

InputConversionOutput
numberdouble
booleanfalse → 0, true → 1bool
nilNULL(void *)
lightuserdatalightuserdata address →(void *)
userdatauserdata payload →(void *)
io.* fileget FILE * handle →(void *)
stringmatch against enum constantenum
stringcopy string data + zero-byteint8_t[], uint8_t[]
stringstring data →const char[]
functioncreate callback →C function type
tabletable initializerArray
tabletable initializerstruct/union
cdatacdata payload →C type

If the result type of this conversion doesn’t match the C type of the destination, the conversion rules between C types are applied.

Conversions between C types

These conversion rules are more or less the same as the standard C conversion rules. Some rules only apply to casts, or require pointer or type compatibility:

InputConversionOutput
Signed integer→narrow or sign-extendInteger
Unsigned integer→narrow or zero-extendInteger
Integer→rounddouble, float
double, float→trunc int32_t →narrow(u)int8_t, (u)int16_t
double, float→trunc(u)int32_t, (u)int64_t
double, float→roundfloat, double
Numbern == 0 → 0, otherwise 1bool
boolfalse → 0, true → 1Number
struct/uniontake base address (compat)Pointer
Arraytake base address (compat)Pointer
Functiontake function addressFunction pointer
Numberconvert via uintptr_t (cast)Pointer
Pointerconvert address (compat/cast)Pointer
Pointerconvert address (cast)Integer
Arrayconvert base address (cast)Integer
Arraycopy (compat)Array
struct/unioncopy (identical type)struct/union

Bitfields or enum types are treated like their underlying type.

Conversions not listed above will raise an error. E.g. it’s not possible to convert a pointer to a complex number or vice versa.

例子:修改结构体成员
local ffi = require("ffi")ffi.cdef [[typedef enum {RED = 1,GREEN = 2,BLUE = 3
} Colors;typedef struct {float x, y;
} point;typedef struct {int8_t a;uint16_t b;uint32_t c;uint64_t d;double e;float f;bool g;Colors h;int *i;char j[100];point k;
} t;
]]local t = ffi.new("t")
t.a = 1                            -- number -> double -> int8_t
t.b = 2                            -- number -> double -> uint16_t
t.c = 3                            -- number -> double -> uint16_t
t.d = 4                            -- number -> double -> uint64_t
t.e = 5                            -- number -> double
t.f = 6                            -- number -> double -> float
t.g = 7                            -- number -> double -> 1 【n == 0 → 0, otherwise 1】
t.h = "BLUE"                       -- string -> enmu 【match against `enum` constant】
local i = ffi.new("int [1]", 1)
t.i = i                            -- cdata -> int [1]【数组】 -> pointer 【take base address (compat)】
t.j = ffi.new("char [100]", "abc") -- cdata -> char [100]
t.k = ffi.new("point", 1, 2)       -- cdata -> point

会发生 Lua 类型到 C 类型的转换。如果按照转换规则无法转换,则运行时会报错。

Conversions for vararg C function arguments

The following default conversion rules apply when passing Lua objects to the variable argument part of vararg C functions:

InputConversionOutput
numberdouble
booleanfalse → 0, true → 1bool
nilNULL(void *)
userdatauserdata payload →(void *)
lightuserdatalightuserdata address →(void *)
stringstring data →const char *
float cdatadouble
Array cdatatake base addressElement pointer
struct/union cdatatake base addressstruct/union pointer
Function cdatatake function addressFunction pointer
Any other cdatano conversionC type

To pass a Lua object, other than a cdata object, as a specific type, you need to override the conversion rules: create a temporary cdata object with a constructor or a cast and initialize it with the value to pass:

Assuming x is a Lua number, here’s how to pass it as an integer to a vararg function:

ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("integer value: %d\n", ffi.new("int", x))

If you don’t do this, the default Lua number → double conversion rule applies. A vararg C function expecting an integer will see a garbled or uninitialized value.

Note: this is the only place where creating a boxed scalar number type is actually useful. Never use ffi.new("int"), ffi.new("float") etc. anywhere else!【只有在作为可变参数 C 函数的参数时才用得到这种写法!】

Ditto for ffi.cast(). Explicitly boxing scalars does not improve performance or force int or float arithmetic! It just adds costly boxing, unboxing and conversions steps. And it may lead to surprise results, because cdata arithmetic on scalar numbers is always performed on 64 bit integers.

Initializers

Creating a cdata object with ffi.new() or the equivalent constructor syntax always initializes its contents, too. Different rules apply, depending on the number of optional initializers and the C types involved:

  • If no initializers are given, the object is filled with zero bytes.
  • Scalar types (numbers and pointers) accept a single initializer. The Lua object is converted to the scalar C type.
  • Aggregate types (arrays and structs) accept either a single cdata initializer of the same type (copy constructor), a single table initializer, or a flat list of initializers.
例子:结构体初始化
local ffi = require("ffi")ffi.cdef [[typedef struct {int a,b,c;
} t;]]local t1 = ffi.new("t", { 1 })  -- 1       0       0local t2 = ffi.new("t", { 1, 2 }) -- 1       2       0local t3 = ffi.new("t", 1)      -- 1       0       0local t4 = ffi.new("t", 1, 2)   -- 1       2       0local t5 = ffi.new("t", t4)     -- 1       2       0-- local t6 = ffi.new("t", 1, 2, 3, 4) -- 报错local t7 = ffi.new("t", {1, 2, 3, 4}) -- 不会报错,忽略多余的元素
  • The elements of an array are initialized, starting at index zero. If a single initializer is given for an array, it’s repeated for all remaining elements. This doesn’t happen if two or more initializers are given: all remaining uninitialized elements are filled with zero bytes.
例子:固定大小数组初始化
local a1 = ffi.new("int [3]", 1) -- 1 1 1
print_array(a1, 3)
local a2 = ffi.new("int [3]", 1, 2) -- 1 2 0
print_array(a2, 3)local a3 = ffi.new("int [3]", {1}) -- 1 1 1
print_array(a3, 3)local a4 = ffi.new("int [3]", {1, 2}) -- 1 2 0
print_array(a4, 3)
  • Byte arrays may also be initialized with a Lua string. This copies the whole string plus a terminating zero-byte. The copy stops early only if the array has a known, fixed size.
例子:变长数组初始化
local a1 = ffi.new("int [?]", 3, 1) -- 1 1 1local a2 = ffi.new("int [?]", 3, 1, 2) -- 1 2 0local a3 = ffi.new("int [?]", 3, {1}) -- 1 ? ?local a4 = ffi.new("int [?]", 3, {1, 2}) -- 1 2 ?
例子:字节数组初始化
local s = ffi.new("char [100]", "hello world!")
print(ffi.string(s)) -- hello world!
  • The fields of a struct are initialized in the order of their declaration. Uninitialized fields are filled with zero bytes.
  • Only the first field of a union can be initialized with a flat initializer.
  • Elements or fields which are aggregates themselves are initialized with a single initializer, but this may be a table initializer or a compatible aggregate.
例子:嵌套结构体初始化
local ffi = require("ffi")ffi.cdef [[typedef struct {float x, y;
} point;typedef struct {int a,b,c;point d;
} t;]]local t1 = ffi.new("t", 1, 2, 3, {1, 2})
local t2 = ffi.new("t", 1, 2, 3, ffi.new("point", 1, 2))
local t3 = ffi.new("t", {a = 1, d = {1, 2}})
  • Excess initializers cause an error.

Table Initializers

The following rules apply if a Lua table is used to initialize an Array or a struct/union:

  • If the table index [0] is non-nil, then the table is assumed to be zero-based. Otherwise it’s assumed to be one-based.
  • Array elements, starting at index zero, are initialized one-by-one with the consecutive table elements, starting at either index [0] or [1]. This process stops at the first nil table element.
  • If exactly one array element was initialized, it’s repeated for all the remaining elements. Otherwise all remaining uninitialized elements are filled with zero bytes.
  • The above logic only applies to arrays with a known fixed size. A VLA is only initialized with the element(s) given in the table. Depending on the use case, you may need to explicitly add a NULL or 0 terminator to a VLA.

参考上面的数组初始化例子。

  • A struct/union can be initialized in the order of the declaration of its fields. Each field is initialized with consecutive table elements, starting at either index [0] or [1]. This process stops at the first nil table element.
  • Otherwise, if neither index [0] nor [1] is present, a struct/union is initialized by looking up each field name (as a string key) in the table. Each non-nil value is used to initialize the corresponding field.

参考上面的嵌套结构体初始化例子。

  • Uninitialized fields of a struct are filled with zero bytes, except for the trailing VLA of a VLS.
  • Initialization of a union stops after one field has been initialized. If no field has been initialized, the union is filled with zero bytes.
  • Elements or fields which are aggregates themselves are initialized with a single initializer, but this may be a nested table initializer (or a compatible aggregate).

参考上面的嵌套结构体初始化例子。

  • Excess initializers for an array cause an error. Excess initializers for a struct/union are ignored. Unrelated table entries are ignored, too.
例子:使用 table 初始化数组或结构体
local ffi = require("ffi")ffi.cdef[[
struct foo { int a, b; };
union bar { int i; double d; };
struct nested { int x; struct foo y; };
]]ffi.new("int[3]", {})            --> 0, 0, 0
ffi.new("int[3]", {1})           --> 1, 1, 1
ffi.new("int[3]", {1,2})         --> 1, 2, 0
ffi.new("int[3]", {1,2,3})       --> 1, 2, 3
ffi.new("int[3]", {[0]=1})       --> 1, 1, 1
ffi.new("int[3]", {[0]=1,2})     --> 1, 2, 0
ffi.new("int[3]", {[0]=1,2,3})   --> 1, 2, 3
ffi.new("int[3]", {[0]=1,2,3,4}) --> error: too many initializersffi.new("struct foo", {})            --> a = 0, b = 0
ffi.new("struct foo", {1})           --> a = 1, b = 0
ffi.new("struct foo", {1,2})         --> a = 1, b = 2
ffi.new("struct foo", {[0]=1,2})     --> a = 1, b = 2
ffi.new("struct foo", {b=2})         --> a = 0, b = 2
ffi.new("struct foo", {a=1,b=2,c=3}) --> a = 1, b = 2  'c' is ignoredffi.new("union bar", {})        --> i = 0, d = 0.0
ffi.new("union bar", {1})       --> i = 1, d = ?
ffi.new("union bar", {[0]=1,2}) --> i = 1, d = ?    '2' is ignored
ffi.new("union bar", {d=2})     --> i = ?, d = 2.0ffi.new("struct nested", {1,{2,3}})     --> x = 1, y.a = 2, y.b = 3
ffi.new("struct nested", {x=1,y={2,3}}) --> x = 1, y.a = 2, y.b = 3

Operations on cdata Objects

All standard Lua operators can be applied to cdata objects or a mix of a cdata object and another Lua object. The following list shows the predefined operations.

Reference types are dereferenced before performing each of the operations below — the operation is applied to the C type pointed to by the reference.

The predefined operations are always tried first before deferring to a metamethod or index table (if any) for the corresponding ctype (except for __new). An error is raised if the metamethod lookup or index table lookup fails.

Indexing a cdata object

  • Indexing a pointer/array: a cdata pointer/array can be indexed by a cdata number or a Lua number. The element address is computed as the base address plus the number value multiplied by the element size in bytes. A read access loads the element value and converts it to a Lua object. A write access converts a Lua object to the element type and stores the converted value to the element. An error is raised if the element size is undefined or a write access to a constant element is attempted.
  • Dereferencing a struct/union field: a cdata struct/union or a pointer to a struct/union can be dereferenced by a string key, giving the field name. The field address is computed as the base address plus the relative offset of the field. A read access loads the field value and converts it to a Lua object. A write access converts a Lua object to the field type and stores the converted value to the field. An error is raised if a write access to a constant struct/union or a constant field is attempted. Scoped enum constants or static constants are treated like a constant field.

A ctype object can be indexed with a string key, too. The only predefined operation is reading scoped constants of struct/union types. All other accesses defer to the corresponding metamethods or index tables (if any).

Note: since there’s (deliberately) no address-of operator, a cdata object holding a value type is effectively immutable after initialization. The JIT compiler benefits from this fact when applying certain optimizations.

As a consequence, the elements of complex numbers and vectors are immutable. But the elements of an aggregate holding these types may be modified, of course. I.e. you cannot assign to foo.c.im, but you can assign a (newly created) complex number to foo.c.

The JIT compiler implements strict aliasing rules: accesses to different types do not alias, except for differences in signedness (this applies even to char pointers, unlike C99). Type punning through unions is explicitly detected and allowed.

Calling a cdata object

  • Constructor: a ctype object can be called and used as a constructor. This is equivalent to ffi.new(ct, ...), unless a __new metamethod is defined. The __new metamethod is called with the ctype object plus any other arguments passed to the constructor. Note that you have to use ffi.new inside the metamethod, since calling ct(...) would cause infinite recursion.
  • C function call: a cdata function or cdata function pointer can be called. The passed arguments are converted to the C types of the parameters given by the function declaration. Arguments passed to the variable argument part of vararg C function use special conversion rules. This C function is called and the return value (if any) is converted to a Lua object.
    On Windows/x86 systems, __stdcall functions are automatically detected, and a function declared as __cdecl (the default) is silently fixed up after the first call.

Arithmetic on cdata objects

  • Pointer arithmetic: a cdata pointer/array and a cdata number or a Lua number can be added or subtracted. The number must be on the right-hand side for a subtraction. The result is a pointer of the same type with an address plus or minus the number value multiplied by the element size in bytes. An error is raised if the element size is undefined.
  • Pointer difference: two compatible cdata pointers/arrays can be subtracted. The result is the difference between their addresses, divided by the element size in bytes. An error is raised if the element size is undefined or zero.
  • 64 bit integer arithmetic: the standard arithmetic operators (+ - * / % ^ and unary minus) can be applied to two cdata numbers, or a cdata number and a Lua number. If one of them is an uint64_t, the other side is converted to an uint64_t and an unsigned arithmetic operation is performed. Otherwise, both sides are converted to an int64_t and a signed arithmetic operation is performed. The result is a boxed 64 bit cdata object.
    If one of the operands is an enum and the other operand is a string, the string is converted to the value of a matching enum constant before the above conversion.
    These rules ensure that 64 bit integers are “sticky”. Any expression involving at least one 64 bit integer operand results in another one. The undefined cases for the division, modulo and power operators return 2LL ^ 63 or 2ULL ^ 63.
    You’ll have to explicitly convert a 64 bit integer to a Lua number (e.g. for regular floating-point calculations) with tonumber(). But note this may incur a precision loss.
  • 64 bit bitwise operations: the rules for 64 bit arithmetic operators apply analogously.
    Unlike the other bit.* operations, bit.tobit() converts a cdata number via int64_t to int32_t and returns a Lua number.
    For bit.band(), bit.bor() and bit.bxor(), the conversion to int64_t or uint64_t applies to all arguments, if any argument is a cdata number.
    For all other operations, only the first argument is used to determine the output type. This implies that a cdata number as a shift count for shifts and rotates is accepted, but that alone does not cause a cdata number output.

Comparisons of cdata objects

  • Pointer comparison: two compatible cdata pointers/arrays can be compared. The result is the same as an unsigned comparison of their addresses. nil is treated like a NULL pointer, which is compatible with any other pointer type.
  • 64 bit integer comparison: two cdata numbers, or a cdata number and a Lua number can be compared with each other. If one of them is an uint64_t, the other side is converted to an uint64_t and an unsigned comparison is performed. Otherwise, both sides are converted to an int64_t and a signed comparison is performed.
    If one of the operands is an enum and the other operand is a string, the string is converted to the value of a matching enum constant before the above conversion.
  • Comparisons for equality/inequality never raise an error. Even incompatible pointers can be compared for equality by address. Any other incompatible comparison (also with non-cdata objects) treats the two sides as unequal.

cdata objects as table keys

Lua tables may be indexed by cdata objects, but this doesn’t provide any useful semantics — cdata objects are unsuitable as table keys! 【cdata 对象不适合作为 table 的 keys】

A cdata object is treated like any other garbage-collected object and is hashed and compared by its address for table indexing. Since there’s no interning for cdata value types, the same value may be boxed in different cdata objects with different addresses. Thus, t[1LL+1LL] and t[2LL] usually do not point to the same hash slot, and they certainly do not point to the same hash slot as t[2].

It would seriously drive up implementation complexity and slow down the common case, if one were to add extra handling for by-value hashing and comparisons to Lua tables. Given the ubiquity of their use inside the VM, this is not acceptable.

There are three viable alternatives, if you really need to use cdata objects as keys:

  • If you can get by with the precision of Lua numbers (52 bits), then use tonumber() on a cdata number or combine multiple fields of a cdata aggregate to a Lua number. Then use the resulting Lua number as a key when indexing tables.
    One obvious benefit: t[tonumber(2LL)] does point to the same slot as t[2].
  • Otherwise, use either tostring() on 64 bit integers or complex numbers or combine multiple fields of a cdata aggregate to a Lua string (e.g. with ffi.string()). Then use the resulting Lua string as a key when indexing tables.
  • Create your own specialized hash table implementation using the C types provided by the FFI library, just like you would in C code. Ultimately, this may give much better performance than the other alternatives or what a generic by-value hash table could possibly provide.

Garbage Collection of cdata Objects

All explicitly (ffi.new(), ffi.cast() etc.) or implicitly (accessors) created cdata objects are garbage collected. You need to ensure to retain valid references to cdata objects somewhere on a Lua stack, an upvalue or in a Lua table while they are still in use. Once the last reference to a cdata object is gone, the garbage collector will automatically free the memory used by it (at the end of the next GC cycle).

Please note, that pointers themselves are cdata objects, however they are not followed by the garbage collector. So e.g. if you assign a cdata array to a pointer, you must keep the cdata object holding the array alive as long as the pointer is still in use:

ffi.cdef[[
typedef struct { int *a; } foo_t;
]]local s = ffi.new("foo_t", ffi.new("int[10]")) -- WRONG!local a = ffi.new("int[10]") -- OK
local s = ffi.new("foo_t", a)
-- Now do something with 's', but keep 'a' alive until you're done.

Similar rules apply for Lua strings which are implicitly converted to "const char *": the string object itself must be referenced somewhere or it’ll be garbage collected eventually. The pointer will then point to stale data, which may have already been overwritten. Note that string literals are automatically kept alive as long as the function containing it (actually its prototype) is not garbage collected.

Objects which are passed as an argument to an external C function are kept alive until the call returns. So it’s generally safe to create temporary cdata objects in argument lists. This is a common idiom for passing specific C types to vararg functions.

Memory areas returned by C functions (e.g. from malloc()) must be manually managed, of course (or use ffi.gc()). Pointers to cdata objects are indistinguishable from pointers returned by C functions (which is one of the reasons why the GC cannot follow them).

Callbacks

The LuaJIT FFI automatically generates special callback functions whenever a Lua function is converted to a C function pointer. This associates the generated callback function pointer with the C type of the function pointer and the Lua function object (closure).

This can happen implicitly due to the usual conversions, e.g. when passing a Lua function to a function pointer argument. Or, you can use ffi.cast() to explicitly cast a Lua function to a C function pointer.

Currently, only certain C function types can be used as callback functions. Neither C vararg functions nor functions with pass-by-value aggregate argument or result types are supported. There are no restrictions on the kind of Lua functions that can be called from the callback — no checks for the proper number of arguments are made. The return value of the Lua function will be converted to the result type, and an error will be thrown for invalid conversions.

It’s allowed to throw errors across a callback invocation, but it’s not advisable in general. Do this only if you know the C function, that called the callback, copes with the forced stack unwinding and doesn’t leak resources.

One thing that’s not allowed, is to let an FFI call into a C function get JIT-compiled, which in turn calls a callback, calling into Lua again. Usually this attempt is caught by the interpreter first and the C function is blacklisted for compilation.

However, this heuristic may fail under specific circumstances: e.g. a message polling function might not run Lua callbacks right away and the call gets JIT-compiled. If it later happens to call back into Lua (e.g. a rarely invoked error callback), you’ll get a VM PANIC with the message "bad callback". Then you’ll need to manually turn off JIT-compilation with jit.off() for the surrounding Lua function that invokes such a message polling function (or similar).

Callback resource handling

Callbacks take up resources — you can only have a limited number of them at the same time (500 - 1000, depending on the architecture). The associated Lua functions are anchored to prevent garbage collection, too.

Callbacks due to implicit conversions are permanent! There is no way to guess their lifetime, since the C side might store the function pointer for later use (typical for GUI toolkits). The associated resources cannot be reclaimed until termination:

ffi.cdef[[
typedef int (__stdcall *WNDENUMPROC)(void *hwnd, intptr_t l);
int EnumWindows(WNDENUMPROC func, intptr_t l);
]]-- Implicit conversion to a callback via function pointer argument.
local count = 0
ffi.C.EnumWindows(function(hwnd, l)count = count + 1return true
end, 0)
-- The callback is permanent and its resources cannot be reclaimed!
-- Ok, so this may not be a problem, if you do this only once.

Note: this example shows that you must properly declare __stdcall callbacks on Windows/x86 systems. The calling convention cannot be automatically detected, unlike for __stdcall calls to Windows functions.

For some use cases, it’s necessary to free up the resources or to dynamically redirect callbacks. Use an explicit cast to a C function pointer and keep the resulting cdata object. Then use the cb:free() or cb:set() methods on the cdata object:

-- Explicitly convert to a callback via cast.
local count = 0
local cb = ffi.cast("WNDENUMPROC", function(hwnd, l)count = count + 1return true
end)-- Pass it to a C function.
ffi.C.EnumWindows(cb, 0)
-- EnumWindows doesn't need the callback after it returns, so free it.cb:free()
-- The callback function pointer is no longer valid and its resources
-- will be reclaimed. The created Lua closure will be garbage collected.

Callback performance

Callbacks are slow! First, the C to Lua transition itself has an unavoidable cost, similar to a lua_call() or lua_pcall(). Argument and result marshalling add to that cost. And finally, neither the C compiler nor LuaJIT can inline or optimize across the language barrier and hoist repeated computations out of a callback function.

Do not use callbacks for performance-sensitive work: e.g. consider a numerical integration routine which takes a user-defined function to integrate over. It’s a bad idea to call a user-defined Lua function from C code millions of times. The callback overhead will be absolutely detrimental for performance.

It’s considerably faster to write the numerical integration routine itself in Lua — the JIT compiler will be able to inline the user-defined function and optimize it together with its calling context, with very competitive performance.

As a general guideline: use callbacks only when you must, because of existing C APIs. E.g. callback performance is irrelevant for a GUI application, which waits for user input most of the time, anyway.

For new designs avoid push-style APIs: a C function repeatedly calling a callback for each result. Instead, **use pull-style APIs: call a C function repeatedly to get a new result. Calls from Lua to C via the FFI are much faster than the other way round. Most well-designed libraries already use pull-style APIs (read/write, get/put) **.

C Library Namespaces

A C library namespace is a special kind of object which allows access to the symbols contained in shared libraries or the default symbol namespace. The default ffi.C namespace is automatically created when the FFI library is loaded. C library namespaces for specific shared libraries may be created with the ffi.load() API function.

Indexing a C library namespace object with a symbol name (a Lua string) automatically binds it to the library. First, the symbol type is resolved — it must have been declared with ffi.cdef. Then the symbol address is resolved by searching for the symbol name in the associated shared libraries or the default symbol namespace. Finally, the resulting binding between the symbol name, the symbol type and its address is cached. Missing symbol declarations or nonexistent symbol names cause an error.

This is what happens on a read access for the different kinds of symbols:

  • External functions: a cdata object with the type of the function and its address is returned.
  • External variables: the symbol address is dereferenced and the loaded value is converted to a Lua object and returned.
  • Constant values (static const or enum constants): the constant is converted to a Lua object and returned.

This is what happens on a write access:

  • External variables: the value to be written is converted to the C type of the variable and then stored at the symbol address.
  • Writing to constant variables or to any other symbol type causes an error, like any other attempted write to a constant location.

C library namespaces themselves are garbage collected objects. If the last reference to the namespace object is gone, the garbage collector will eventually release the shared library reference and remove all memory associated with the namespace. Since this may trigger the removal of the shared library from the memory of the running process, it’s generally not safe to use function cdata objects obtained from a library if the namespace object may be unreferenced.

Performance notice: the JIT compiler specializes to the identity of namespace objects and to the strings used to index it. This effectively turns function cdata objects into constants. It’s not useful and actually counter-productive to explicitly cache these function objects, e.g. local strlen = ffi.C.strlen. OTOH, it is useful to cache the namespace itself, e.g. local C = ffi.C.

No Hand-holding!

The FFI library has been designed as a low-level library. The goal is to interface with C code and C data types with a minimum of overhead. This means you can do anything you can do from C: access all memory, overwrite anything in memory, call machine code at any memory address and so on.

The FFI library provides no memory safety, unlike regular Lua code. It will happily allow you to dereference a NULL pointer, to access arrays out of bounds or to misdeclare C functions. If you make a mistake, your application might crash, just like equivalent C code would.

This behavior is inevitable, since the goal is to provide full interoperability with C code. Adding extra safety measures, like bounds checks, would be futile. There’s no way to detect misdeclarations of C functions, since shared libraries only provide symbol names, but no type information. Likewise, there’s no way to infer the valid range of indexes for a returned pointer.

Again: the FFI library is a low-level library. This implies it needs to be used with care, but it’s flexibility and performance often outweigh this concern. If you’re a C or C++ developer, it’ll be easy to apply your existing knowledge. OTOH, writing code for the FFI library is not for the faint of heart and probably shouldn’t be the first exercise for someone with little experience in Lua, C or C++.

As a corollary of the above, the FFI library is not safe for use by untrusted Lua code. If you’re sandboxing untrusted Lua code, you definitely don’t want to give this code access to the FFI library or to any cdata object (except 64 bit integers or complex numbers). Any properly engineered Lua sandbox needs to provide safety wrappers for many of the standard Lua library functions — similar wrappers need to be written for high-level operations on FFI data types, too.

Current Status

The initial release of the FFI library has some limitations and is missing some features. Most of these will be fixed in future releases.

C language support is currently incomplete:

  • C declarations are not passed through a C pre-processor, yet.
  • The C parser is able to evaluate most constant expressions commonly found in C header files. However, it doesn’t handle the full range of C expression semantics and may fail for some obscure constructs.
  • static const declarations only work for integer types up to 32 bits. Neither declaring string constants nor floating-point constants is supported.
  • Packed struct bitfields that cross container boundaries are not implemented.
  • Native vector types may be defined with the GCC mode or vector_size attribute. But no operations other than loading, storing and initializing them are supported, yet.
  • The volatile type qualifier is currently ignored by compiled code.
  • ffi.cdef silently ignores most re-declarations. Note: avoid re-declarations which do not conform to C99. The implementation will eventually be changed to perform strict checks.

The JIT compiler already handles a large subset of all FFI operations. It automatically falls back to the interpreter for unimplemented operations (you can check for this with the -jv command line option). The following operations are currently not compiled and may exhibit suboptimal performance, especially when used in inner loops:

  • Vector operations.
  • Table initializers.
  • Initialization of nested struct/union types.
  • Non-default initialization of VLA/VLS or large C types (> 128 bytes or > 16 array elements).
  • Bitfield initializations.
  • Pointer differences for element sizes that are not a power of two.
  • Calls to C functions with aggregates passed or returned by value.
  • Calls to ctype metamethods which are not plain functions.
  • ctype __newindex tables and non-string lookups in ctype __index tables.
  • tostring() for cdata types.
  • Calls to ffi.cdef(), ffi.load() and ffi.metatype().

Other missing features:

  • Arithmetic for complex numbers.
  • Passing structs by value to vararg C functions.
  • C++ exception interoperability does not extend to C functions called via the FFI, if the call is compiled.

相关文章:

LuaJIT 学习(4)—— FFI 语义

文章目录 C Language SupportC Type Conversion RulesConversions from C types to Lua objects例子&#xff1a;访问结构体成员 Conversions from Lua objects to C typesConversions between C types例子&#xff1a;修改结构体成员 Conversions for vararg C function argum…...

剑指 Offer II 078. 合并排序链表

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20078.%20%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8/README.md 剑指 Offer II 078. 合并排序链表 题目描述 给定一个链表数组&#xff0c;每个链…...

go回调函数的使用

在Go语言中&#xff0c;回调函数可以有参数&#xff0c;也可以没有参数。它们的定义和使用方式略有不同&#xff0c;但本质上都是将函数作为参数传递给另一个函数&#xff0c;并在适当的时候调用它。以下是带参数和不带参数的回调函数的示例和说明。 1. 不带参数的回调函数 不…...

CBNet:一种用于目标检测的复合骨干网架构之论文阅读

摘要 现代顶级性能的目标检测器在很大程度上依赖于骨干网络&#xff0c;而骨干网络的进步通过探索更高效的网络结构带来了持续的性能提升。本文提出了一种新颖且灵活的骨干框架——CBNet&#xff0c;该框架利用现有的开源预训练骨干网络&#xff0c;在预训练-微调范式下构建高…...

k8s中PAUSE容器与init容器比较 local卷与hostpath卷比较

目录 一、PAUSE容器与INIT容器比较 1. Pause 容器 作用 特点 示例 2. Init 容器 作用 特点 示例 3. Pause 容器 vs Init 容器 4. 总结 这两个哪个先启动呢&#xff1f; 详细启动顺序 为什么 Pause 容器最先启动&#xff1f; 示例 总结 二、local卷与hostpath卷…...

施耐德PLC仿真软件Modbus tcp通讯测试

安装仿真软件&#xff1a;EcoStruxure™ Control Expert - PLC 仿真器 下载地址&#xff1a;https://www.schneider-electric.cn/zh/download/document/EIO0000001719/ 配置CPU&#xff1a; 切换至仿真模式&#xff0c;系统托盘中出现仿真器图标 新建变量test&#xff0c;地址…...

TCP、UDP协议的应用、ServerSocket和Socket、DatagramSocket和DatagramPacket

DAY13.1 Java核心基础 TCP协议 TCP 协议是面向连接的运算层协议&#xff0c;比较复杂&#xff0c;应用程序在使用TCP协议之前必须建立连接&#xff0c;才能传输数据&#xff0c;数据传输完毕之后需要释放连接 就好比现实生活中的打电话&#xff0c;首先确保电话打通了才能进…...

【HarmonyOS Next】常见的字节转换

【HarmonyOS Next】常见的字节转换 字节转换、位运算在实际开发中具有广泛的应用价值&#xff0c;特别是在处理字节级数据时发挥着重要作用。例如&#xff0c;在网络通信中用于大小端序转换&#xff0c;在数据解析时进行位提取操作。这些特性使得位运算在USB通信、蓝牙&#x…...

Redis-锁-商品秒杀防止超卖

一、秒杀&#xff08;Seckill&#xff09;​ 1. ​定义 ​秒杀&#xff1a;短时间内&#xff08;如1秒内&#xff09;大量用户同时抢购 ​限量低价商品 的营销活动。​典型场景&#xff1a;双11热门商品抢购、小米手机首发、演唱会门票开售。 2. ​技术挑战 挑战点说明后果…...

RHCE(RHCSA复习:npm、dnf、源码安装实验)

七、软件管理 7.1 rpm 安装 7.1.1 挂载 [rootlocalhost ~]# ll /mnt total 0 drwxr-xr-x. 2 root root 6 Oct 27 21:32 hgfs[rootlocalhost ~]# mount /dev/sr0 /mnt #挂载 mount: /mnt: WARNING: source write-protected, mounted read-only. [rootlocalhost ~]# [rootlo…...

KNN算法性能优化技巧与实战案例

KNN算法性能优化技巧与实战案例 K最近邻&#xff08;KNN&#xff09;在分类和回归任务中表现稳健&#xff0c;但其计算复杂度高、内存消耗大成为IT项目中的主要瓶颈。以下从 算法优化、数据结构、工程实践 三方面深入解析性能提升策略&#xff0c;并附典型应用案例。 一、核心性…...

安装并使用anaconda(宏观版)

conda安装 windows 安装 1 官网下载-下载地址 2 配置环境 - 安装目录 bin,script 三个填入环境变量(windows “系统属性” -> “高级系统设置” -> “环境变量” ) 这些值可以被运行在操作系统上的程序使用。它是一个通用的概念&#xff0c;在不同的操作系统和应用程序…...

Qwen2.5-VL 开源视觉大模型,模型体验、下载、推理、微调、部署实战

一、Qwen2.5-VL 简介 Qwen2.5-VL&#xff0c;Qwen 模型家族的旗舰视觉语言模型&#xff0c;比 Qwen2-VL 实现了巨大的飞跃。 欢迎访问 Qwen Chat &#xff08;Qwen Chat&#xff09;并选择 Qwen2.5-VL-72B-Instruct 进行体验。 1. 主要增强功能 1&#xff09;直观地理解事物&…...

【sql靶场】第18-22关-htpp头部注入保姆级教程

目录 【sql靶场】第18-22关-htpp头部注入保姆级教程 1.回顾知识 1.http头部 2.报错注入 2.第十八关 1.尝试 2.爆出数据库名 3.爆出表名 4.爆出字段 5.爆出账号密码 3.第十九关 4.第二十关 5.第二十一关 6.第二十二关 【sql靶场】第18-22关-htpp头部注入保姆级教程…...

SpringBoot实现发邮件功能+邮件内容带模版

发送简单邮件模版邮件 1.pom引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId><version>2.5.13</version></dependency><dependency><groupId&…...

C# NX二次开发:矩形阵列和线性阵列等多种方法讲解

大家好&#xff0c;今天讲一些关于阵列相关的UFUN函数。 UF_MODL_create_linear_iset (view source)&#xff1a;这个函数为创建矩形阵列。 intmethodInputMethod: 0 General 1 Simple 2 Identicalchar *number_in_xInputNumber in XC direction.char *distance_xInputSpac…...

OpenBMC:BmcWeb添加路由1 getParameterTag

BmcWeb对于路由的设计其实是参考了Crow BMCWEB_ROUTE(app, "/upload/image/<str>").privileges({{"ConfigureComponents", "ConfigureManager"}}).methods(boost::beast::http::verb::post, boost::beast::http::verb::put)([](const cro…...

【Android性能】Systrace分析

1&#xff0c;分析工具 1&#xff0c;Systrace新UI网站 Perfetto UI 2&#xff0c;Systrace抓取 可通过android sdk中自带的systrace抓取&#xff0c;路径一般如下&#xff0c;..\AppData\Local\Android\Sdk\platform-tools&#xff0c; 另外需要安装python2.7&#xff0c;…...

多种语言请求API接口方法

在当今的互联网世界中&#xff0c;应用程序编程接口&#xff08;API&#xff09;扮演着至关重要的角色&#xff0c;它们允许不同的服务和应用程序之间进行数据交换和功能共享。无论是获取天气预报、社交媒体数据还是进行支付操作&#xff0c;API都是背后的关键。不同的编程语言…...

【css酷炫效果】纯CSS实现瀑布流加载动画

【css酷炫效果】纯CSS实现瀑布流加载动画 缘创作背景html结构css样式完整代码基础版进阶版(无限往复加载) 效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492012 缘 创作随缘&#xff0c;不定时更新。 创作…...

【第15届蓝桥杯】软件赛CB组省赛

个人主页&#xff1a;Guiat 归属专栏&#xff1a;算法竞赛真题题解 文章目录 A. 握手问题&#xff08;填空题&#xff09;B. 小球反弹&#xff08;填空题&#xff09;C. 好数D. R格式E. 宝石组合F. 数字接龙G. 爬山H. 拔河 正文 总共8道题。 A. 握手问题&#xff08;填空题&…...

20242817李臻《Linux⾼级编程实践》第四周

20242817李臻《Linux⾼级编程实践》第4周 一、AI对学习内容的总结 第5章 Linux进程管理 5.1 进程基本概念 进程与程序的区别 程序&#xff1a;静态的二进制文件&#xff08;如/bin/ls&#xff09;&#xff0c;存储在磁盘中&#xff0c;不占用运行资源。进程&#xff1a;程…...

【AI大模型】提示词(Prompt)工程完全指南:从理论到产业级实践

【AI大模型】提示词&#xff08;Prompt&#xff09;工程完全指南&#xff1a;从理论到产业级实践 一、Prompt 提示词介绍&#xff1a;AI的“密码本” 1. Prompt的底层定义与价值 本质&#xff1a;Prompt是人与AI模型的“协议语言”&#xff0c;通过文本指令激活模型的特定推理…...

HTML中required与aria required区别

在HTML中&#xff0c;required和aria-required"true"都用于标识表单字段为必填项&#xff0c;但它们的作用和适用场景有所不同&#xff1a; 1. required 属性 • 功能属性&#xff1a;属于HTML5原生属性&#xff0c;直接控制表单验证逻辑。 • 作用&#xff1a; • …...

MySQL 锁

MySQL中最常见的锁有全局锁、表锁、行锁。 全局锁 全局锁用于锁住当前库中的所有实例&#xff0c;也就是说会将所有的表都锁住。一般用于做数据库备份的时候就需要添加全局锁&#xff0c;数据库备份的时候是一个表一个表备份&#xff0c;如果没有加锁的话在备份的时候会有其他的…...

halcon几何测量(一)3d_position_of_rectangle

目录 一、提取目标区域&#xff0c;选择不和边缘相交的目标二、计算矩形工件的姿态三、显示矩形的立体结构 一、提取目标区域&#xff0c;选择不和边缘相交的目标 1、提取目标区域&#xff1a;mean_image 、dyn_threshold 、fill_up 、connection 、select_shape 2、选择不和边…...

docker可视化之dpanel

1. 使用镜像加速 vim /etc/docker/daemon.json{ "registry-mirrors": ["https://docker.registry.cyou","https://docker-cf.registry.cyou","https://dockercf.jsdelivr.fyi","https://docker.jsdelivr.fyi","https…...

Swagger 从 .NET 9 中删除:有哪些替代方案

微软已经放弃了对 .NET 9 中 Swagger UI 包 Swashbuckle 的支持。他们声称该项目“不再由社区所有者积极维护”并且“问题尚未得到解决”。 这意味着当您使用 .NET 9 模板创建 Web API 时&#xff0c;您将不再拥有 UI 来测试您的 API 端点。 我们将调查是否可以在 .NET 9 中使用…...

Qt 绘图

一、基础概念 ‌Qt 绘图基于 QPainter&#xff08;画家类&#xff09;、QPaintDevice&#xff08;绘图设备&#xff09;和 QPaintEngine&#xff08;绘图引擎&#xff09;的协作实现。其中&#xff1a; QPainter 提供绘制图形、文本和图像的接口&#xff08;如 drawLine()、d…...

用 Vue 3.5 TypeScript 重新开发3年前甘特图的核心组件

回顾 3年前曾经用 Vue 2.0 开发了一个甘特图组件&#xff0c;如今3年过去了&#xff0c;计划使用Vue 3.5 TypeScript 把组件重新开发&#xff0c;有机会的话再开发一个React版本。 关于之前的组件以前文章 Vue 2.0 甘特图组件 下面录屏是是 用 Vue 3.5 TypeScript 开发的目前…...

Python使用总结之Flask构建文件服务器,通过网络地址访问本地文件

Python使用总结之Flask构建文件服务器,通过网络地址访问本地文件 在 Web 开发中,静态文件(如图片、CSS、JavaScript)的管理是基础且重要的环节。Flask 提供的 send_from_directory 函数为开发者提供了灵活的文件服务解决方案。本文将详细解析其原理、用法及最佳实践。 一…...

从Excel到搭贝的转变过程

从Excel到搭贝 1. 简介 1.1 Excel简介 Excel 作为元老级的数据管理工具&#xff0c;功能强大且被广泛使用&#xff0c;但在现代工作场景中仍存在一些局限性&#xff0c;例如&#xff1a; 数据量处理有限&#xff1a;处理大规模数据时&#xff0c;Excel可能运行缓慢或崩溃。…...

C语言经典代码练习题

1.输入一个4位数&#xff1a;输出这个输的个位 十位 百位 千位 #include <stdio.h> int main(int argc, char const *argv[]) {int a;printf("输入一个&#xff14;位数&#xff1a;");scanf("%d",&a);printf("个位&#xff1a;%d\n"…...

Compose 的产生和原理

引言 compose 出现的目的&#xff1a; 重新定义android 上ui 的编写方式。为了提高android 原生ui开发效率。让android 的UI开发方式跟上时代。 正文 compose 是什么&#xff1f; 就是一套ui框架 和flutter 一样是一套ui框架 Flutter&#xff1a;跨平台开发趋势与企业应用的…...

JS做贪吃蛇小游戏(源码)

一、HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><link rel…...

c语言笔记 结构体内嵌套结构体的表示方式

目录 结构体内嵌套结构体 问&#xff1a;我们都该如何去访问该结构体里面的结构体的成员呢?怎么去给里面的成员赋值呢? 说明&#xff1a; 运行上述代码后&#xff0c;输出结果如下&#xff1a; 结构体内嵌套结构体 背景&#xff1a;如果我们在结构体中放结构体&#xff0…...

Vue3一个组件绑定多个 v-model,自定义 prop 和 event 名称

Vue3一个组件绑定多个 v-model&#xff0c;自定义 prop 和 event 名称 Vue3中v-model默认使用modelValue作为prop&#xff0c;update:modelValue作为事件&#xff0c;而Vue2使用的是value和input。此外&#xff0c;Vue3允许通过参数的方式为组件添加多个v-model绑定&#xff0…...

STM32---FreeRTOS事件标志组

一、简介 事件标志位&#xff1a;用一个位&#xff0c;来表示事件是否发生 事件标志组&#xff1a;一组事件标志位的集合&#xff0c;可以简单的理解时间标志组&#xff0c;就是一个整体。 事件标志租的特点&#xff1a; 它的每一个位表示一个时间&#xff08;高8位不算&…...

分享一个项目中遇到的一个算法题

需求背景&#xff1a; 需求是用户要创建一个任务计划在未来执行&#xff0c;要求在创建任务计划的时候判断选择的时间是否符合要求&#xff0c;否则不允许创建&#xff0c;创建的任务类型有两种&#xff0c;一种是单次&#xff0c;任务只执行一次&#xff1b;另一种是周期&…...

入门 Sui Move 开发:9. 一个 Sui dApp 前端项目

内容概览 接下来一起通过 PTB 和 Navi SDK 实现一个一键存入借出的简单 DApp。 本节分为两部分&#xff1a; 创建一个 DApp 前端项目以及 Sui dApp Kit 的使用&#xff1b;了解 Navi SDK&#xff0c;主要包含的功能以及如何实现存入和借出功能&#xff1b; 最终完成我们的项…...

如何打造安全稳定的亚马逊采购测评自养号下单系统?

在当今的电商领域&#xff0c;亚马逊作为全球领先的在线购物平台&#xff0c;其商品种类繁多&#xff0c;用户基数庞大&#xff0c;成为了众多商家和消费者的首选。而对于一些需要进行商品测评或市场调研的用户来说&#xff0c;拥有一个稳定、安全的亚马逊账号体系显得尤为重要…...

c语言笔记 结构体基础

目录 基础知识 结构体定义 基础知识 在c语言中变量是有类型的&#xff0c;比如整型&#xff0c;char型&#xff0c;浮点型等&#xff0c;这些都是单一的类型&#xff0c;那么如果说我要定义一个学生的信息&#xff0c;那么这些单一的类型是不足以表达一个学生的全部信息&#…...

添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎

文章目录 添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎如何添加步骤 1: 打开浏览器设置步骤 2: 添加新搜索引擎步骤 3: 保存设置 注意事项 添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎 在使用 ChatGPT/Grok/Gemini 进行对话时&#xff0c;每次都需要先打开对应的网页&#xff0…...

golang-struct结构体

struct结构体 概述 Go 语言中数组可以存储同一类型的数据&#xff0c;但在结构体中我们可以为不同项定义不同的数据类型。 结构体是 Golang 中一种复合类型&#xff0c;它是由一组具有相同或不同类型的数据字段组成的数据结构。 结构体是一种用户自定义类型&#xff0c;它可…...

矫平机:工业制造的“误差归零者”,如何重塑智造新生态?

在新能源汽车电池托盘的生产线上&#xff0c;一块2米长的铝合金板材因焊接应力产生了0.5毫米的隐形翘曲。这个看似微不足道的变形&#xff0c;却导致激光焊接工序的良率暴跌至65%。当工程师们尝试传统矫正方案时&#xff0c;发现高强度铝合金既不能加热校形&#xff0c;又无法承…...

Springboot中的@ConditionalOnBean注解:使用指南与最佳实践

在使用Spring Boot进行开发时&#xff0c;大家应该都听说过条件注解&#xff08;Conditional Annotations&#xff09;。其中的ConditionalOnBean注解就很有趣&#xff0c;它帮助开发者在特定条件下创建和注入Bean&#xff0c;让你的应用更加灵活。今天就来聊聊这个注解的使用场…...

Spring 中 BeanFactoryPostProcessor 的作用和示例

一、概览 1. 核心定位 BeanFactoryPostProcessor 是 Spring 容器级别的扩展接口&#xff0c;在 Bean 实例化之前&#xff0c;对 Bean 的配置元数据&#xff08;即 BeanDefinition&#xff09;进行动态修改或扩展。其核心功能围绕以下两点&#xff1a; 修改现有 Bean 的定义&…...

PDFMathTranslate 安装、使用及接入deepseek

PDFMathTranslate 安装、使用及接入deepseek 介绍安装及使用接入deepseek注意 介绍 PDFMathTranslate 是非常好用的科学 PDF 文档翻译及双语对照工具&#xff0c;可以将论文按照其原本的排版结构执行多种语言翻译&#xff0c;并且可以接入如&#xff1a;谷歌翻译、deepl、deep…...

Docker生存手册:安装到服务一本通

文章目录 一. Docker 容器介绍1.1 什么是Docker容器&#xff1f;1.2 为什么需要Docker容器&#xff1f;1.3 Docker架构1.4 Docker 相关概念1.5 Docker特点 二. Docker 安装2.1 查看Linux内核版本2.2 卸载老版本docker&#xff0c;避免产生影响2.3 升级yum 和配置源2.4 安装Dock…...

JAVA中关于图形化界面的学习(GUI)动作监听,鼠标监听,键盘监听

动作监听&#xff1a; 先创建一个图形化界面&#xff0c;接着创建一个按钮对象&#xff0c;设置按钮的大小。 添加一个addActionListener()&#xff1b; addActionListener() 方法定义在 java.awt.event.ActionListener 接口相关的上下文中&#xff0c;许多支持用户交互产生…...