深入研究虚幻4反射系统实现原理(一)

上一篇翻译的文章里面提到了UE4反射系统的基本原理与应用,这次我们通过代码来深入研究一下UE4的反射系统,因为反射系统在UE4中牵扯的东西较多,所以我打算分几篇文章分析。我这里假定读者对UE4有一定的了解并且有一定的C++基础,如果不了解UE4如何使用,那么请先学会如何使用UE4引擎,否则看起来可能会比较困难。

以下是我整理的一个跟反射系统相关的类图:

从上面可以看出UObject是整个反射系统核心,UE4中支持反射的类型在上一篇文章中已经说过,包括 C++类、结构体、函数 、成员变量以及枚举,也支持TArray(只支持一些如TArray和TSubclassOf的模板类型,并且它们的模板类型不能是嵌套的类型),但是TMap不支持。而这些东西的支持与上面的类是分不开的,比如UClass、UBlueprintGeneratedClass、UFunction、UEnum、以及UProperty,以及继承自它们的子类。每一个继承UObject且支持反射系统类型都有一个相对应 的UClass,或者它的子类(比如蓝图对应的课表UBlueprintGeneratedClass类,它继承自UClass),如果是特定的蓝图类型,比如动作蓝图、Widget蓝图等,如上图所示。UMetaData是元数据,它存储了一些编辑器需要的额外信息,比如它的分类(Category )、提示(Tooltip)等,最终打包的时候是不会用到这些信息的。至于我们反射系统里需要访问的float、int32等变量,则都是由继承自UProperty的子类来表示的,具体 可以根据上图所列出的对象去代码里面去找对应的类去看它具体的实现。

下面我们以一个最简单的代码示例来说明UE4中反射的实现过程,首先我创建了一个名为ReflectionStudy的工程(只有Basic Code),这样做是为了方便分析代码,一开始提到的文章中说过,如果你想让你实现的类支持反射,那么必须遵循相关的准则,比如要使用UENUM()、UCLASS()、USTRUCT()、UFUNCTION()、以及UPROPERTY()等,UHT会根据这些宏来生成对应的支持反射的代码。下面我们分别展开来分析这些代码,它生成的代码都存放在你的工程ReflectionStudy\Intermediate\Build\Win64\UE4Editor\Inc\ReflectionStudy路径下。

里面一般分为几类文件:

  1. ReflectionStudy.generated.cpp 一个工程只有一个,这个文件是用来为每个支持反射的类生成反射信息的代码,比如注册属性、添加源数据等。
  2. ReflectionStudy.generated.dep.h 这个文件里面就是包含了上面1.    ReflectionStudy.generated.cpp用到的头文件。
  3. ReflectionStudyClasses.h
  4. *.generated.h 这个就是为每个支持反射的头文件生成的对应的宏的代码。

类的定义

我们以下面的代码为例来讲解,为了查看一些用法的具体实现,我们特意加了以下几个 属性和方法。

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/GameMode.h"
#include "ReflectionStudyGameMode.generated.h"

/**
*
*/
UCLASS()
class REFLECTIONSTUDY_API AReflectionStudyGameMode : public AGameMode
{
GENERATED_BODY()

protected:
UPROPERTY(BlueprintReadWrite, Category = "AReflectionStudyGameMode")
float Score;

UFUNCTION(BlueprintCallable, Category = "AReflectionStudyGameMode")
void CallableFuncTest();

UFUNCTION(BlueprintNativeEvent, Category = "AReflectionStudyGameMode")
void NavtiveFuncTest();

UFUNCTION(BlueprintImplementableEvent, Category = "AReflectionStudyGameMode")
void ImplementableFuncTest();
};

  

UHT生成的.generated.h文件

因为对应的ReflectionStudyGameMode.generated.h头文件较长,所以我们只把关键的部分列出来讲解。

#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS 	virtual void NavtiveFuncTest_Implementation();  	DECLARE_FUNCTION(execNavtiveFuncTest) 	{ 		P_FINISH; 		P_NATIVE_BEGIN; 		this->NavtiveFuncTest_Implementation(); 		P_NATIVE_END; 	}  	DECLARE_FUNCTION(execCallableFuncTest) 	{ 		P_FINISH; 		P_NATIVE_BEGIN; 		this->CallableFuncTest(); 		P_NATIVE_END; 	}

  

可以看到,我们上面定义的函数,UHT帮我们自动生成了如上代码,至于为什么会生成这样的函数,那是因为UE4蓝图调用约定,每个函数前面要加一个exec前缀,关于蓝图的实现因为我目前也了解的也不是很清楚,所以可能会在后面出一个对蓝图实现的介绍,这些函数都是由UE4虚拟机调用过来的,如果包含参数和返回值,那么还会有相应的从虚拟机栈上取参数和设置返回值的代码,读者可以自行去验证。

define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS 	private: 	static void StaticRegisterNativesAReflectionStudyGameMode(); 	friend REFLECTIONSTUDY_API class UClass* Z_Construct_UClass_AReflectionStudyGameMode(); 	public: 	DECLARE_CLASS(AReflectionStudyGameMode, AGameMode, COMPILED_IN_FLAGS(0 | CLASS_Transient | CLASS_Config), 0, TEXT("/Script/ReflectionStudy"), NO_API) 	DECLARE_SERIALIZER(AReflectionStudyGameMode) 	/** Indicates whether the class is compiled into the engine */ 	enum {IsIntrinsic=COMPILED_IN_INTRINSIC};

  

  • StaticRegisterNativesAReflectionStudyGameMode 这个函数是用来 注册C++原生函数暴露给虚拟机使用的。
  • friend REFLECTIONSTUDY_API class UClass* Z_Construct_UClass_AReflectionStudyGameMode(); 声明友元函数,这个函数是用来构建此类对应的UClass的。
  • DECLARE_CLASS 此宏比较复杂,主要是定义了StaticClass() 等,具体实现请读者打开它的定义就可以看到。
  • DECLARE_SERIALIZER 定义序列化代码。
  • enum {IsIntrinsic=COMPILED_IN_INTRINSIC}; 正如注释所说,就是用来标记这个类是否是编译到引擎中的。

  

#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS 	/** Standard constructor, called after all reflected properties have been initialized */ 	NO_API AReflectionStudyGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; private: 	/** Private copy-constructor, should never be used */ 	NO_API AReflectionStudyGameMode(const AReflectionStudyGameMode& InCopy); public: 	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AReflectionStudyGameMode); DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AReflectionStudyGameMode); 	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AReflectionStudyGameMode)

  

  • NO_API AReflectionStudyGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \定义一个标准构造函数,在所有反射属性都初始化之后调用。
  • NO_API AReflectionStudyGameMode(const AReflectionStudyGameMode& InCopy); \ 防止调用拷贝构造函数
  • DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, AReflectionStudyGameMode); \ DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(AReflectionStudyGameMode); \ 热加载相关,这是UE4里面比较牛逼的功能,我们这里也不详细讨论,这个如果以后对这块理解了也会单独开个专题进行讲解。
  • DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL 定义了一个默认构造函数,如下代码所示:

    static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

  

#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_GENERATED_BODY
PRAGMA_DISABLE_DEPRECATION_WARNINGS
public:
    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS
    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_CALLBACK_WRAPPERS
    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS
    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS
private:
PRAGMA_ENABLE_DEPRECATION_WARNINGS

  

这段代码就是对上述解释宏的引用,配合下面这个宏最终就实现了在class中定义一个GENERATED_BODY()就可以把上面所有定义的内容包含到该类中。

#undef CURRENT_FILE_ID

#define CURRENT_FILE_ID ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h

所有GENERATED_BODY()相关的宏定义如下

// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_BODY()

#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D

#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
#define GENERATED_BODY_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)

#define GENERATED_USTRUCT_BODY(...) GENERATED_BODY()
#define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()

  

至此ReflectionStudyGameMode.generated.h文件里面的内容就基本分析完了,下面我们来看ReflectionStudy.generated.cpp里面对应的代码,结合前面的解释,相信你对整个UE4的反射系统就有一个大体的了解了。

.generated.cpp文件中相关内容

FName REFLECTIONSTUDY_ImplementableFuncTest = FName(TEXT("ImplementableFuncTest"));
FName REFLECTIONSTUDY_NavtiveFuncTest = FName(TEXT("NavtiveFuncTest"));
    void AReflectionStudyGameMode::ImplementableFuncTest()
    {
        ProcessEvent(FindFunctionChecked(REFLECTIONSTUDY_ImplementableFuncTest),NULL);
    }
    void AReflectionStudyGameMode::NavtiveFuncTest()
    {
        ProcessEvent(FindFunctionChecked(REFLECTIONSTUDY_NavtiveFuncTest),NULL);
    }
    void AReflectionStudyGameMode::StaticRegisterNativesAReflectionStudyGameMode()
    {
        FNativeFunctionRegistrar::RegisterFunction(AReflectionStudyGameMode::StaticClass(), "CallableFuncTest",(Native)&AReflectionStudyGameMode::execCallableFuncTest);
        FNativeFunctionRegistrar::RegisterFunction(AReflectionStudyGameMode::StaticClass(), "NavtiveFuncTest",(Native)&AReflectionStudyGameMode::execNavtiveFuncTest);
    }
    IMPLEMENT_CLASS(AReflectionStudyGameMode, 3618622309);

  

  • 刚接触UE4的时候,如果是BlueprintImplementabeEvent的函数,是不是发现不需要自己去实现,那么当时有没有觉得怪异呢,上面的代码就解释清楚了,那是UE4帮我们实现了,可以看到它调用了ProcessEvent方法,这个方法在UObject中实现的。
  • StaticRegisterNativesAReflectionStudyGameMode 向AReflectionStudyGameMode::StaticClass()返回的UClass里面添加原生的C++函数。
  • IMPLEMENT_CLASS 定义了一个静态全局变量,用于在程序启动的时候注册UClass。

  

	UFunction* Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest()
	{
		UObject* Outer=Z_Construct_UClass_AReflectionStudyGameMode();
		static UFunction* ReturnFunction = NULL;
		if (!ReturnFunction)
		{
			ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("CallableFuncTest"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x04080401, 65535);
			ReturnFunction->Bind();
			ReturnFunction->StaticLink();
#if WITH_METADATA
			UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnFunction, TEXT("Category"), TEXT("AReflectionStudyGameMode"));
			MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h"));
#endif
		}
		return ReturnFunction;
	}

  

  • 这个函数向AReflectionStudyGameMode返回的UClass类里面注册名为CallableFuncTest的函数,而#if WITH_METADATA里面就是我们前面提到的元数据,可以注意其中我们类中指定的Category分类就在这里指定的,放在了它的(UPackage中)UMetaData中。Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest()和Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest()实现方式和上面基本一样,这里我就不写出来了。

  

	UClass* Z_Construct_UClass_AReflectionStudyGameMode()
	{
		static UClass* OuterClass = NULL;
		if (!OuterClass)
		{
			Z_Construct_UClass_AGameMode();
			Z_Construct_UPackage__Script_ReflectionStudy();
			OuterClass = AReflectionStudyGameMode::StaticClass();
			if (!(OuterClass->ClassFlags & CLASS_Constructed))
			{
				UObjectForceRegistration(OuterClass);
				OuterClass->ClassFlags |= 0x2090028C;

				OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest());
				OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest());
				OuterClass->LinkChild(Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest());

PRAGMA_DISABLE_DEPRECATION_WARNINGS
				UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, OuterClass, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, AReflectionStudyGameMode), 0x0020080000000004);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest(), "CallableFuncTest"); // 3059784748
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest(), "ImplementableFuncTest"); // 4773450
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest(), "NavtiveFuncTest"); // 2500148308
				OuterClass->ClassConfigName = FName(TEXT("Game"));
				OuterClass->StaticLink();
#if WITH_METADATA
				UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();
				MetaData->SetValue(OuterClass, TEXT("HideCategories"), TEXT("Info Rendering MovementReplication Replication Actor Input Movement Collision Rendering Utilities|Transformation"));
				MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("ReflectionStudyGameMode.h"));
				MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h"));
				MetaData->SetValue(OuterClass, TEXT("ShowCategories"), TEXT("Input|MouseInput Input|TouchInput"));
				MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("AReflectionStudyGameMode"));
				MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("ReflectionStudyGameMode.h"));
#endif
			}
		}
		check(OuterClass->GetClass());
		return OuterClass;
	}

  

  • 这个函数的作用就是来生成AReflectionStudyGameMode的UClass对象,并注册所有的UFunction 和UProperty
  • Z_Construct_UClass_AGameMode(); 因为它继承自AGameMode所以AGameMode的UClass必须有效。
  • Z_Construct_UPackage__Script_ReflectionStudy(); 确保UPackage已经创建。
  • #if WITH_METADATA 宏中代码也是用于创建元数据。
	static FCompiledInDefer Z_CompiledInDefer_UClass_AReflectionStudyGameMode(Z_Construct_UClass_AReflectionStudyGameMode, &AReflectionStudyGameMode::StaticClass, TEXT("AReflectionStudyGameMode"), false, nullptr, nullptr);
	DEFINE_VTABLE_PTR_HELPER_CTOR(AReflectionStudyGameMode);
  • 第一行代码用于存放创建UClass的一个静态函数,之后将会执行这个静态生成UClass函数
  • DEFINE_VTABLE_PTR_HELPER_CTOR 定义一个参数为FVTableHelper构造函数。

  

	UPackage* Z_Construct_UPackage__Script_ReflectionStudy()
	{
		static UPackage* ReturnPackage = NULL;
		if (!ReturnPackage)
		{
			ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT("/Script/ReflectionStudy")), false, false));
			ReturnPackage->SetPackageFlags(PKG_CompiledIn | 0x00000000);
			FGuid Guid;
			Guid.A = 0x00B770A5;
			Guid.B = 0x8BECE3AF;
			Guid.C = 0x00000000;
			Guid.D = 0x00000000;
			ReturnPackage->SetGuid(Guid);

		}
		return ReturnPackage;
	}

  

用于返当前模块的UPackage,上面的代码中会用到这个参数GetOuterMost()函数,返回的就是这个UPackage。

总结

至此我们对UE4中反射系统对类的支持做了一个简单的介绍,相信大家也有了一定的了解,限于篇幅,我们这篇到此为止,后面会继续讨论其它USTRUCT、UENUM、等的实现,以及它们整个反射系统的运行流程。由于我对UE4也不是特别熟悉,所以其中可能有说的不准确的地方,如果有错误的地方,还请指正,也希望大家能一起讨论。当然后面我也会讲一下UE4中其它模块的实现,比如整个蓝图的实现、多线程渲染、以及基于物理的渲染等内容。

由于最上面的类图尺寸过大,上传后的图片并不是特别清晰,高清原图可以在这里下载

时间: 2024-12-18 21:17:04

深入研究虚幻4反射系统实现原理(一)的相关文章

R语言统计分析技术研究——岭回归技术的原理和应用

岭回归技术的原理和应用 作者马文敏 岭回归分析是一种专用于共线性分析的有偏估计回归方法,实质上是一种改良的最小二乘估计法,通过放弃最小二乘法的无偏性,以损失部分信息,降低精度为代价获得回归系数更为符合实际,更可靠的回归方法,对病态数据的耐受性远远强于最小二乘法. 回归分析:他是确立两种或两种以上变量间相互依赖的定量关系的一种统计分析法.运用十分广泛,回归分析按照设计量的多少,分为一元回归和多元回归分析,按照因变量的多少,可分为简单回归分析和多重回归分析,按照自变量和因变量的多少类型可分为线性回归

要精通Java,先研究它的执行原理

对于任何一门语言,要想达到精通的水平,研究它的执行原理(或者叫底层机制)不失为一种良好的方式. 在本篇文章中,将重点研究java源代码的执行原理,即从程序员编写JAVA源代码,到最终形成产品,在整个过程中,都经历了什么?每一步又是怎么执行的?执行原理又是什么? 一.编写java源程序 java源文件:指存储java源码的文件. 先来看看如下代码: //MyTest被public修饰,故存储该java源码的文件名为MyTest public class MyTest { public static

ReentrantLock实现原理深入探究

前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLock并且能讲清楚ReentrantLock的原理的文章很少,本文就来研究一下ReentrantLock的实现原理.研究ReentrantLock的实现原理需要比较好的Java基础以及阅读代码的能力,有些朋友看不懂没关系,可以以后看,相信你一定会有所收获. 最后说一句,ReentrantLock是基于A

0909编译原理

1.编译原理学什么? “编译原理”是一门研究设计和构造编译程序原理和方法的课程,是计算机各专业的一门重要专业基础课. 2.为什么学编译原理? 通过学习该课程,掌握编译的基本理论.常用的编译技术,了解编译过程及编译系统结构和机理,更好的理解程序. 3.怎么学编译原理? 实践中学习,当然,需要掌握一些基本知识,通过在课堂中认真听课,运用已有的编程基础多加实践. 4.思考:在没有学习本书理论之前,如果让你写一个编译器,你是什么思路? 之前了解过编译器的原理就是将高级语言翻译成机器语言,但是如何写一个编

0909对编译原理的初了解

1.编译原理学什么? "编译原理":研究设计和构造编译程序原理和方法以及主要实现技术.其中蕴含着计算机科学中解决问题的思路.形式化问题和解决问题的方法.通过本课程的学习,使学生掌握编译理论和方法方面的基本知识,同时也获得设计.实现.分析和移植编译程序方面的初步能力. 编译原理(第3版)共10章,内容包括语言及文法的基本知识.词法分析.语法分析.语义分析及中间代码生成.符号表组织.运行时的存储组织与分配.代码优化及目标代码生成等.此外编译原理是一门实践性较强的课程,要联系实际,多看实验参

Java NIO原理 图文分析及代码实现

Java NIO原理 图文分析及代码实现 博客分类: java底层 java NIO原理阻塞I/O非阻塞I/O Java NIO原理图文分析及代码实现 前言:  最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术

Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

概述 类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode.在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果.当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermo

0909 初遇编译原理

编译原理学什么?      编译原理是一门研究设计和构造编译程序原理和方法的课程,是计算机各专业的一门重要专业基础课.编译原理主要学习的是编译程序结构及各部分功能.文法和语言         的基本概念和表示.词法分析.语法分析.属性文法与语法制导翻译技术.符号表.运算时存储空间的组织.代码优化与目标代码生成.并行编译技术概述等. 为什么学编译原理?      编译原理这门课程实际蕴含蕴含着计算机学科中解决问题的思路.形式化问题和解决问题的方法,这些思路和方法除了对应用软件和系统软件的设计与开发

数据库SQL SELECT查询的工作原理

作为B/S架构的开发人员,总是离不开数据库.一般开发员只会应用SQL的四条经典语句:select,insert,delete,update.但是我从来没有研究过它们的工作原理,这篇我想说一说select在数据库中的工作原理. B/S架构中最经典的话题无非于三层架构,可以大概分为数据层,业务逻辑层和表示层,而数据层的作用一般都是和数据库交互,例如查询记录.我们经常是写好查询SQL,然后调用程序执行SQL.但是它内部的工作流程是怎样的呢?先做哪一步,然后做哪一步等,我想还有大部分朋友和我一样都不一定