标题
[翻译]C语言深入介绍GDExtension:Hello GDExtension
clq
浏览(28) +
2025-02-15 14:55:11 发表
编辑
关键字:
[2025-03-05 11:28:14 最后更新]
https://www.bilibili.com/video/BV179AgetE8q/ 忘贴原文地址在视频了,原文来自 https://github.com/gilzoide/hello-gdextension/blob/main/1.hello-c/README.md 项目中有对应的源码全文。 它应该是参考了官方纯 C 示例 https://docs.godotengine.org/zh-cn/4.x/tutorials/scripting/gdextension/gdextension_c_example.html 不过官方这篇文章我查找多时始终没有找到对应的源码全文(希望有知道网友告知一下),不知道是不官方故意的,只留有推荐使用的 c++ 版本的。 这个接口是 4.0 后才有的,所以搜索引擎和 ai 中也根本没有。我查找某个函数时,整个 google 只找到了 5 个页面 ... 。这其实可以看下 golang 版本互为参考。 https://pkg.go.dev/github.com/godot-go/godot-go/pkg/gdextensionffi https://github.com/godot-go/godot-go 总之这应该是 godot 中技术难度最大的一部分了,不过有时候确实还是需要用到,看一下还是值得的。 -------------------------------------------------------- 原文来自老外 https://github.com/gilzoide/hello-gdextension/blob/main/1.hello-c/README.md 让 deepseek 翻译了一下,效果还是非常好的 [翻译] 从C语言深入介绍GDExtension:Hello GDExtension 在这篇与GDExtension API的首次互动中,我们将创建一个“Hello World”扩展,当它被加载时,会打印一条静态消息。 为了深入了解GDExtension,我们将使用纯C语言直接调用原始API。在后续的示例中,我们将使用C++和godot-cpp绑定,这将提供更好的可用性。 注意:本教程仅适用于Godot 4.1及更高版本 生成GDExtension API文件 首先,我们需要从godot-headers仓库下载gdextension_interface.h和extension_api.json文件,或者使用Godot编辑器生成它们。gdextension_interface.h包含GDExtension API的定义,而extension_api.json是一个包含所有Godot类、工具函数和枚举的元数据的巨大JSON文件。 为了保持组织有序,我们首先创建一个名为include的新文件夹: sh 复制 mkdir include 假设Godot 4已经安装在系统的PATH中,并且可以通过godot命令调用,那么在我们刚刚创建的include目录中运行以下命令来生成文件: sh 复制 godot --dump-extension-api --dump-gdextension-interface --headless 使用--dump-extension-api参数会让Godot生成extension_api.json文件。 使用--dump-gdextension-interface参数会让Godot生成gdextension_interface.h文件。 使用--headless参数会让Godot使用虚拟显示和音频驱动,从而加快整个过程。 最终,我们的目录结构应该如下所示: 复制 . └─ include/ ├─ extension_api.json └─ gdextension_interface.h 创建一个空库 好了,现在我们有了API文件,可以开始创建我们的扩展了。 首先,我们创建一个名为hello-gdextension.c的C文件。我们将包含gdextension_interface.h文件,并定义一个函数作为我们库的入口点,Godot在加载扩展时会调用这个函数。这个入口点必须遵循gdextension_interface.h中定义的GDExtensionInitializationFunction原型。 c 复制 // hello-gdextension.c // 1. 包含GDExtension API文件 #include "include/gdextension_interface.h" // 2. 定义与GDExtensionInitializationFunction原型匹配的函数 GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { // 返回0表示错误,返回非零表示成功 return 1; } 现在我们有了C文件,我们需要将其编译为共享库,也就是DLL。这个项目非常简单,只有一个源文件,我们可以直接运行C编译器。但为了简化操作并为将来处理更复杂的项目做准备,我们使用一个构建系统。 我们可以使用任何构建系统。有很多不错的选择,比如Make、CMake、Meson、SCons、xmake、Bazel等。在本教程中,我们将使用SCons,它与Godot和godot-cpp使用的构建系统相同。 安装SCons后,创建一个名为SConstruct的文件,内容如下: python 复制 SharedLibrary('hello-gdextension.c') 现在运行scons命令应该可以正确构建我们的共享库,无论我们运行的是哪个平台或安装了哪个C编译器: sh 复制 scons # scons: Reading SConscript files ... # scons: done reading SConscript files. # scons: Building targets ... # gcc -o hello-gdextension.os -c -fPIC hello-gdextension.c # gcc -o libhello-gdextension.so -shared hello-gdextension.os # scons: done building targets. 忽略scons生成的文件,我们当前的目录结构应该如下所示: 复制 . ├─ hello-gdextension.c ├─ include/ │ ├─ extension_api.json │ └─ gdextension_interface.h └─ SConstruct 配置扩展 除了包含构建代码的共享库外,扩展还由一个INI格式的文件组成,文件扩展名为.gdextension,其中包含入口点函数的符号、扩展支持的最低Godot版本以及每个平台/CPU架构的库路径。 让我们定义自己的hello.gdextension文件: ini 复制 [configuration] entry_symbol = "hello_extension_entry" compatibility_minimum = "4.1" [libraries] linux.x86_64 = "libhello-gdextension.so" windows.x86_64 = "hello-gdextension.dll" macos = "libhello-gdextension.dylib" 如果我们将来添加对其他平台(如Android或iOS)或CPU架构(如x86或arm64)的支持,我们需要在此文件中添加更多条目。 还要注意,在Linux和macOS上,scons生成的库名称前会加上lib前缀。 好了,现在当我们运行Godot 4并使用我们构建的扩展时,它应该会被正确加载。我在这个仓库的根目录中添加了一个示例项目文件,其中包含一个空场景,但你也可以创建自己的项目来测试。 sh 复制 godot --upwards --headless --quit # ERROR: Condition "initialization.initialize == nullptr" is true. # at: initialize_library (core/extension/gdextension.cpp:503) # Godot Engine v4.1.2.stable.official.399c9dc39 - https://godotengine.org # ... # ================================================================ # handle_crash: Program crashed with signal 11 # Engine version: Godot Engine v4.1.2.stable.official (399c9dc393f6f84c0b4e4d4117906c70c048ecf2) # Dumping the backtrace. Please include this when reporting the bug on: https://github.com/godotengine/godot/issues # [1] /usr/lib/libc.so.6 ... # -- END OF BACKTRACE -- # ================================================================ 我没有保证编辑器不会崩溃,只是说我们的库会被加载。我们的扩展仍然需要定义r_initialization->initialize和deinitialize函数。 当前和最终的目录结构: 复制 . ├─ hello.gdextension ├─ hello-gdextension.c ├─ include/ │ ├─ extension_api.json │ └─ gdextension_interface.h └─ SConstruct GDExtension生命周期 当Godot初始化时,它还会通过调用传递给入口点的GDExtensionInitialization指针中的initialize函数来初始化所有GDExtension。在退出时,Godot通过调用deinitialize函数来反初始化所有扩展。 作为扩展开发者,我们有责任实现这两个函数,并在初始化结构中设置它们。打开hello-gdextension.c并创建这两个函数。为了帮助我们理解库的运行情况,我们还可以添加一些调试日志。 c 复制 // `printf`是`stdio.h`头文件的一部分 #include #include "include/gdextension_interface.h" void initialize(void *userdata, GDExtensionInitializationLevel p_level) { printf("initialize at level %d\n", p_level); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { printf("deinitialize at level %d\n", p_level); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { // 设置`initialize`函数 r_initialization->initialize = &initialize; // 设置`deinitialize`函数 r_initialization->deinitialize = &deinitialize; return 1; } 使用scons重新编译,再次运行Godot并查看输出: sh 复制 godot --upwards --headless --quit # initialize at level 0 # Godot Engine v4.1.2.stable.official.399c9dc39 - https://godotengine.org # initialize at level 1 # # initialize at level 2 # initialize at level 3 # deinitialize at level 3 # deinitialize at level 2 # deinitialize at level 1 # deinitialize at level 0 如我们所见,Godot初始化并反初始化我们的扩展4次,每次对应一个初始化级别。GDExtensionInitializationLevel枚举列出了Godot使用的可能初始化级别: GDEXTENSION_INITIALIZATION_CORE:在引擎的核心模块初始化后立即发生。 GDEXTENSION_INITIALIZATION_SERVERS:在引擎的服务器初始化后立即发生。 GDEXTENSION_INITIALIZATION_SCENE:在引擎的运行时类注册后立即发生。 只有在这时,包括核心类如Object、Reference和Node在内的类才会在ClassDB中注册,并且可以被扩展。 GDEXTENSION_INITIALIZATION_EDITOR:仅在编辑器中发生,在编辑器类(如EditorPlugin)注册后立即发生。 用于扩展中的仅编辑器代码。 我们有责任检查正确的初始化级别,并在适当的时候运行我们的代码。我们可以使用条件语句来实现这一点: c 复制 #include #include "include/gdextension_interface.h" void initialize(void *userdata, GDExtensionInitializationLevel p_level) { // 如果初始化级别不是“Scene”,则提前返回而不打印 if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } printf("initialize at level %d\n", p_level); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { // 如果初始化级别不是“Scene”,则提前返回而不打印 if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } printf("deinitialize at level %d\n", p_level); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { r_initialization->initialize = &initialize; r_initialization->deinitialize = &deinitialize; return 1; } 重新编译并重新运行Godot,可以看到只有初始化级别2(“Scene”)被处理: sh 复制 godot --upwards --headless --quit # Godot Engine v4.1.2.stable.official.399c9dc39 - https://godotengine.org # # initialize at level 2 # deinitialize at level 2 好了,我们的库现在可以正常初始化而不会崩溃了! 使用Godot工具函数 如果你从Godot编辑器中运行项目,你会发现printf输出只有在从终端运行Godot时才会显示。它不会出现在编辑器的输出选项卡中。让我们通过调用Godot内置的print工具函数来解决这个问题,这个函数与我们在GDScript代码中使用的相同。 如果你仔细查看gdextension_interface.h头文件,你会发现print_error、print_warning、print_script_error,但没有普通的print函数。事实上,GDExtension API非常简洁,要求我们在运行时获取几乎所有内置功能。这使得它非常轻量且动态,因此构建自己Godot分支的人可以以与内置函数相同的方式在扩展中使用他们自己的额外类/函数。这也使得手动编写扩展代码相当繁琐。GDExtension是为代码生成而设计的,这就是为什么extension_api.json文件存在的原因:以便为所有类和函数自动生成绑定,包括自定义分支中添加的函数。 调用print函数足够简单,不需要生成任何代码,所以我们现在将使用原始的GDExtension API。这将帮助我们理解GDExtension在幕后的工作原理。 要调用print的实现,我们首先需要使用variant_get_ptr_utility_function来获取它。它的参数是一个StringName,表示函数名(在本例中为“print”),以及一个表示工具函数哈希值的整数。哈希值可以在extension_api.json文件中找到,对于print,哈希值为2648703342。 创建StringName 首先,我们需要创建StringName“print”。StringName是String类型的一个版本,经过优化后可以用作唯一标识符。这种拥有唯一字符串实例的技术称为“字符串驻留”,并被多种系统使用,包括Java和Lua等编程语言。有关Godot的StringName的更多信息,请参阅其文档页面。 查看gdextension_interface.h头文件,只有从C字符串创建String类型的函数。StringName的一个构造函数接受String作为输入,因此我们需要首先创建一个String,然后从中构造一个StringName。 注意:如果我们使用godot-cpp或任何其他绑定,所有这些都将被处理,我们将能够直接从C/C++数据构造StringName。下一篇文章将使用它们,因此我们不需要这样的样板代码。 注意:Godot 4.2添加了直接从C字符串创建StringName的接口函数,例如string_name_new_with_latin1_chars。 与工具函数一样,所有内置类型的构造函数、析构函数和方法都必须在运行时使用GDExtension接口函数获取。因此,我们要做的第一件事是获取我们需要的接口函数,然后使用它们来获取String和StringName的构造函数和析构函数。 让我们打开hello-gdextension.c,并在入口点使用p_get_proc_address获取所需的扩展接口函数: c 复制 #include #include "include/gdextension_interface.h" // GDExtension API函数指针 static GDExtensionInterfaceStringNewWithUtf8Chars string_new_with_utf8_chars; static GDExtensionInterfaceVariantGetPtrConstructor variant_get_ptr_constructor; static GDExtensionInterfaceVariantGetPtrDestructor variant_get_ptr_destructor; void initialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } printf("initialize at level %d\n", p_level); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } printf("deinitialize at level %d\n", p_level); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { r_initialization->initialize = &initialize; r_initialization->deinitialize = &deinitialize; // 获取GDExtension接口函数指针 string_new_with_utf8_chars = (GDExtensionInterfaceStringNewWithUtf8Chars) p_get_proc_address("string_new_with_utf8_chars"); variant_get_ptr_constructor = (GDExtensionInterfaceVariantGetPtrConstructor) p_get_proc_address("variant_get_ptr_constructor"); variant_get_ptr_destructor = (GDExtensionInterfaceVariantGetPtrDestructor) p_get_proc_address("variant_get_ptr_destructor"); return 1; } 现在我们在initialize函数中获取构造函数和析构函数: c 复制 #include #include "include/gdextension_interface.h" // GDExtension接口函数指针 static GDExtensionInterfaceStringNewWithUtf8Chars string_new_with_utf8_chars; static GDExtensionInterfaceVariantGetPtrConstructor variant_get_ptr_constructor; static GDExtensionInterfaceVariantGetPtrDestructor variant_get_ptr_destructor; // Godot API函数指针 static GDExtensionPtrConstructor construct_StringName_from_String; static GDExtensionPtrDestructor destroy_String; static GDExtensionPtrDestructor destroy_StringName; void initialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } // StringName构造函数索引2是接收String的构造函数 // 你可以在`extension_api.json`文件中找到这个信息 construct_StringName_from_String = variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME, 2); destroy_String = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING); destroy_StringName = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME); printf("initialize at level %d\n", p_level); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } printf("deinitialize at level %d\n", p_level); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { r_initialization->initialize = &initialize; r_initialization->deinitialize = &deinitialize; // 获取GDExtension接口函数指针 string_new_with_utf8_chars = (GDExtensionInterfaceStringNewWithUtf8Chars) p_get_proc_address("string_new_with_utf8_chars"); variant_get_ptr_constructor = (GDExtensionInterfaceVariantGetPtrConstructor) p_get_proc_address("variant_get_ptr_constructor"); variant_get_ptr_destructor = (GDExtensionInterfaceVariantGetPtrDestructor) p_get_proc_address("variant_get_ptr_destructor"); return 1; } 现在我们可以实现一个从C字符串创建StringName的函数。为了使代码更易于理解,我们将为String和StringName创建结构定义,两者都占用一个指针的大小。你可以在extension_api.json中找到结构的大小。 c 复制 // ... typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } String; typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } StringName; StringName construct_StringName_from_cstring(const char *text) { // 1. 从C字符串构造一个String String string; string_new_with_utf8_chars(&string, text); // 2. 从String构造一个StringName StringName string_name; GDExtensionConstTypePtr constructor_arguments[1] = { &string }; construct_StringName_from_String(&string_name, constructor_arguments); // 3. 销毁String,因为它不再需要 destroy_String(&string); return string_name; } // ... 最后,让我们在initialize中创建我们的“print”StringName,并在deinitialize中销毁它。为了简单起见,我们也将它全局存储。最终代码如下: c 复制 #include #include "include/gdextension_interface.h" typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } String; typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } StringName; // GDExtension接口函数指针 static GDExtensionInterfaceStringNewWithUtf8Chars string_new_with_utf8_chars; static GDExtensionInterfaceVariantGetPtrConstructor variant_get_ptr_constructor; static GDExtensionInterfaceVariantGetPtrDestructor variant_get_ptr_destructor; // Godot API函数指针 static GDExtensionPtrConstructor construct_StringName_from_String; static GDExtensionPtrDestructor destroy_String; static GDExtensionPtrDestructor destroy_StringName; // 这是我们全局的“print” StringName static StringName print_StringName; StringName construct_StringName_from_cstring(const char *text) { // 1. 从C字符串构造一个String String string; string_new_with_utf8_chars(&string, text); // 2. 从String构造一个StringName StringName string_name; GDExtensionConstTypePtr constructor_arguments[1] = { &string }; construct_StringName_from_String(&string_name, constructor_arguments); // 3. 销毁String,因为它不再需要 destroy_String(&string); return string_name; } void initialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } // StringName构造函数索引2是接收String的构造函数 // 你可以在`extension_api.json`文件中找到这个信息 construct_StringName_from_String = variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME, 2); destroy_String = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING); destroy_StringName = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME); // 初始化“print” StringName print_StringName = construct_StringName_from_cstring("print"); printf("initialize at level %d\n", p_level); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } // 销毁“print” StringName destroy_StringName(&print_StringName); printf("deinitialize at level %d\n", p_level); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { r_initialization->initialize = &initialize; r_initialization->deinitialize = &deinitialize; string_new_with_utf8_chars = (GDExtensionInterfaceStringNewWithUtf8Chars) p_get_proc_address("string_new_with_utf8_chars"); variant_get_ptr_constructor = (GDExtensionInterfaceVariantGetPtrConstructor) p_get_proc_address("variant_get_ptr_constructor"); variant_get_ptr_destructor = (GDExtensionInterfaceVariantGetPtrDestructor) p_get_proc_address("variant_get_ptr_destructor"); return 1; } 调用print 现在我们有了“print”StringName,剩下的就是获取print函数的实现并调用它。 在我们深入之前,让我们获取一些额外的GDExtension接口函数指针: c 复制 // GDExtension接口函数指针 // ... static GDExtensionInterfaceGetVariantFromTypeConstructor get_variant_from_type_constructor; static GDExtensionInterfaceVariantGetPtrUtilityFunction variant_get_ptr_utility_function; static GDExtensionInterfaceVariantDestroy variant_destroy; // ... GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { // ... get_variant_from_type_constructor = (GDExtensionInterfaceGetVariantFromTypeConstructor) p_get_proc_address("get_variant_from_type_constructor"); variant_get_ptr_utility_function = (GDExtensionInterfaceVariantGetPtrUtilityFunction) p_get_proc_address("variant_get_ptr_utility_function"); variant_destroy = (GDExtensionInterfaceVariantDestroy) p_get_proc_address("variant_destroy"); return 1; } 现在我们声明全局的print函数指针: c 复制 static GDExtensionPtrUtilityFunction print_function; 并使用“print”StringName和我们在extension_api.json中找到的整数哈希值来获取它: c 复制 print_function = variant_get_ptr_utility_function(&print_StringName, 2648703342); 要调用print_function,我们首先需要将每个传递的参数转换为Variant。Variant是包含任何Godot内置类型的结构,包括数字、Array、String、StringName和Object。它是访问Godot数据的基本单元!首先全局声明构造函数: c 复制 static GDExtensionVariantFromTypeConstructorFunc construct_Variant_from_String; 然后在initialize中获取它: c 复制 construct_Variant_from_String = get_variant_from_type_constructor(GDEXTENSION_VARIANT_TYPE_STRING); 让我们也为Variant创建一个结构定义。它们在Godot的默认构建配置中占用24字节。 c 复制 typedef struct { uint8_t godot_data_dont_touch_this[24]; } Variant; 现在让我们创建一个接受C字符串的“print”工具函数的包装器。 c 复制 void print(const char *text) { // 1. 从C字符串构造一个String String string; string_new_with_utf8_chars(&string, text); // 2. 从String构造一个Variant Variant arg1; construct_Variant_from_String(&arg1, &string); // 3. 调用工具函数 // 由于它是一个void函数,使用“NULL”作为返回指针 GDExtensionConstVariantPtr args[1] = { &arg1 }; print_function(NULL, args, 1); // 4. 清理不再需要的资源 variant_destroy(&arg1); destroy_String(&string); } 最后,我们可以调用我们自己的print函数,它将调用Godot内置的“print”工具函数,因此我们的消息现在将出现在编辑器的输出选项卡中。最终代码如下: c 复制 #include "include/gdextension_interface.h" typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } String; typedef struct { uint8_t godot_data_dont_touch_this[sizeof(void *)]; } StringName; typedef struct { uint8_t godot_data_dont_touch_this[24]; } Variant; // GDExtension接口函数指针 static GDExtensionInterfaceStringNewWithUtf8Chars string_new_with_utf8_chars; static GDExtensionInterfaceVariantGetPtrConstructor variant_get_ptr_constructor; static GDExtensionInterfaceVariantGetPtrDestructor variant_get_ptr_destructor; static GDExtensionInterfaceGetVariantFromTypeConstructor get_variant_from_type_constructor; static GDExtensionInterfaceVariantGetPtrUtilityFunction variant_get_ptr_utility_function; static GDExtensionInterfaceVariantDestroy variant_destroy; // Godot API函数指针 static GDExtensionPtrConstructor construct_StringName_from_String; static GDExtensionVariantFromTypeConstructorFunc construct_Variant_from_String; static GDExtensionPtrDestructor destroy_String; static GDExtensionPtrDestructor destroy_StringName; // 这是我们全局的“print” StringName static StringName print_StringName; // 这是我们全局的“print”函数 static GDExtensionPtrUtilityFunction print_function; StringName construct_StringName_from_cstring(const char *text) { // 1. 从C字符串构造一个String String string; string_new_with_utf8_chars(&string, text); // 2. 从String构造一个StringName StringName string_name; GDExtensionConstTypePtr constructor_arguments[1] = { &string }; construct_StringName_from_String(&string_name, constructor_arguments); // 3. 销毁String,因为它不再需要 destroy_String(&string); return string_name; } void print(const char *text) { // 1. 从C字符串构造一个String String string; string_new_with_utf8_chars(&string, text); // 2. 从String构造一个Variant Variant arg1; construct_Variant_from_String(&arg1, &string); // 3. 调用工具函数 // 由于它是一个void函数,使用“NULL”作为返回指针 GDExtensionConstVariantPtr args[1] = { &arg1 }; print_function(NULL, args, 1); // 4. 清理不再需要的资源 variant_destroy(&arg1); destroy_String(&string); } void initialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } // StringName构造函数索引2是接收String的构造函数 // 你可以在`extension_api.json`文件中找到这个信息 construct_StringName_from_String = variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME, 2); construct_Variant_from_String = get_variant_from_type_constructor(GDEXTENSION_VARIANT_TYPE_STRING); destroy_String = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING); destroy_StringName = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME); // 初始化“print” StringName print_StringName = construct_StringName_from_cstring("print"); // 然后获取“print”函数指针 print_function = variant_get_ptr_utility_function(&print_StringName, 2648703342); print("Hello GDExtension from C!"); } void deinitialize(void *userdata, GDExtensionInitializationLevel p_level) { if (p_level != GDEXTENSION_INITIALIZATION_SCENE) { return; } print("Goodbye GDExtension from C!"); // 销毁“print” StringName destroy_StringName(&print_StringName); } GDExtensionBool hello_extension_entry( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization ) { r_initialization->initialize = &initialize; r_initialization->deinitialize = &deinitialize; // 将GDExtension API函数指针全局保存 string_new_with_utf8_chars = (GDExtensionInterfaceStringNewWithUtf8Chars) p_get_proc_address("string_new_with_utf8_chars"); variant_get_ptr_constructor = (GDExtensionInterfaceVariantGetPtrConstructor) p_get_proc_address("variant_get_ptr_constructor"); variant_get_ptr_destructor = (GDExtensionInterfaceVariantGetPtrDestructor) p_get_proc_address("variant_get_ptr_destructor"); get_variant_from_type_constructor = (GDExtensionInterfaceGetVariantFromTypeConstructor) p_get_proc_address("get_variant_from_type_constructor"); variant_get_ptr_utility_function = (GDExtensionInterfaceVariantGetPtrUtilityFunction) p_get_proc_address("variant_get_ptr_utility_function"); variant_destroy = (GDExtensionInterfaceVariantDestroy) p_get_proc_address("variant_destroy"); return 1; } 就是这样!重新编译,打开项目并从Godot编辑器中运行任何场景,你将在输出选项卡中看到消息“Hello GDExtension from C!”。 结论 GDExtension API非常强大且灵活,允许自定义原生代码在不重新编译引擎的情况下向Godot添加新类。另一方面,它非常简洁,访问内置类和工具函数需要大量样板代码,这部分非常适合自动代码生成。 在下一篇文章中,我们将使用godot-cpp绑定来创建一个C++扩展,它将处理所有样板代码,并让我们直接实现功能。下次见!
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.