本主题介绍如何使用调试器数据模型C++调试器数据模型C++脚本,以支持使用脚本通过调试器引擎实现自动化。
在调试器数据模型中 脚本管理
除了数据模型管理器作为对象创建和扩展性的中央机构的角色外,它还负责管理脚本的抽象概念。 从数据模型管理器的脚本管理器部分的角度来看,脚本可以动态加载、卸载和可能由提供程序调试,以便扩展或向数据模型提供新功能。
脚本提供程序是一个组件,用于将语言(例如:NatVis、JavaScript 等)桥接至数据模型。 它注册一个或多个文件扩展名(例如:“)。NatVis、“.js”(由提供程序处理),允许调试器客户端或用户界面通过委派到提供程序加载具有该特定扩展名的脚本文件。
核心脚本管理器:IDataModelScriptManager
核心脚本管理器接口的定义如下。
DECLARE_INTERFACE_(IDataModelScriptManager, IUnknown)
{
STDMETHOD(GetDefaultNameBinder)(_COM_Outptr_ IDataModelNameBinder **ppNameBinder) PURE;
STDMETHOD(RegisterScriptProvider)(_In_ IDataModelScriptProvider *provider) PURE;
STDMETHOD(UnregisterScriptProvider)(_In_ IDataModelScriptProvider *provider) PURE;
STDMETHOD(FindProviderForScriptType)(_In_ PCWSTR scriptType, _COM_Outptr_ IDataModelScriptProvider **provider) PURE;
STDMETHOD(FindProviderForScriptExtension)(_In_ PCWSTR scriptExtension, _COM_Outptr_ IDataModelScriptProvider **provider) PURE;
STDMETHOD(EnumerateScriptProviders)(_COM_Outptr_ IDataModelScriptProviderEnumerator **enumerator) PURE;
}
GetDefaultNameBinder 方法返回数据模型的默认脚本名称绑定器。 名称绑定器是解析对象上下文中名称的组件。 例如,给定表达式“foo.bar”,调用名称绑定器解析对象 foo 上下文中的名称栏。 此处返回的绑定器遵循一组数据模型的默认规则。 脚本提供程序可以使用此绑定器跨提供程序提供名称解析的一致性。
RegisterScriptProvider 方法告知数据模型存在新的脚本提供程序,该提供程序能够将新语言桥接到数据模型。 调用此方法时,脚本管理器将立即回调给定的脚本提供程序,并查询所管理的脚本的属性。 如果已在给定脚本提供程序指示的名称或文件扩展名下注册了提供程序,则此方法将失败。 只能将单个脚本提供程序注册为特定名称或文件扩展名的处理程序。
UnregisterScriptProvider 方法撤消对 RegisterScriptProvider 方法的调用。 由传入的脚本提供程序给出的名称和文件扩展名将不再与其关联。 请务必注意,即使在取消注册后,也可能会大量对脚本提供程序的未完成 COM 引用。 此方法仅阻止加载/创建给定脚本提供程序所管理的类型的脚本。 如果该提供程序加载的脚本仍在加载或已作调试器的对象模型(或数据模型),这些作可能仍会引用回脚本。 可能有数据模型、方法或对象直接引用脚本中的构造。 脚本提供程序必须准备好处理该提供程序。
FindProviderForScriptType 方法在脚本管理器中搜索具有脚本类型字符串的提供程序,如此方法所示。 如果找不到此方法,此方法将失败;否则,此类脚本提供程序将返回到调用方。
EnumerateScriptProviders 方法将返回一个枚举器,该枚举器将枚举通过之前调用 RegisterScriptProvider 方法向脚本管理器注册的每个脚本提供程序。
脚本提供程序枚举:IDataModelScriptProviderEnumerator
EnumerateScriptProviders 方法将返回以下形式的枚举器:
DECLARE_INTERFACE_(IDataModelScriptProviderEnumerator, IUnknown)
{
STDMETHOD(Reset)() PURE;
STDMETHOD(GetNext)(_COM_Outptr_ IDataModelScriptProvider **provider) PURE;
}
Reset 方法会将枚举器移动到返回第一个元素之前所处的位置。
GetNext 方法将枚举器向前移动一个元素,并返回位于该元素的脚本提供程序。 当枚举器命中枚举的末尾时,将返回E_BOUNDS。 收到此错误后调用 GetNext 方法将继续无限期返回E_BOUNDS。
调试器数据模型C++用于脚本的主机接口
在脚本 中 主机的角色
调试主机公开了一系列非常低级别的接口,用于了解其调试目标的类型系统的性质、以调试目标的语言计算表达式等。通常,它与更高级别的构造(如脚本)无关。 这留给整体调试器应用程序或提供这些功能的扩展。 但是,有一个例外。 任何想要参与数据模型提供的总体脚本体验的调试主机都需要实现一些简单的接口,以便为脚本提供上下文。 实际上,调试主机控制其希望脚本环境在数据模型的命名空间中放置函数和其他脚本提供的功能的位置。 参与此过程允许主机允许(或不允许)使用此类函数,例如其表达式计算器。 从主机的角度来看,涉及的接口如下:
接口 | 说明 |
---|---|
IDebugHostScriptHost | 指示调试主机参与脚本环境的接口。 此接口允许创建上下文,以通知脚本引擎放置对象的位置。 |
IDataModelScriptHostContext | 脚本提供程序用作脚本内容的容器的主机接口。 除了脚本对调试器应用程序的对象模型执行的作之外,脚本的内容如何取决于特定的调试主机。 此接口允许脚本提供程序获取有关放置其内容的位置的信息。 有关详细信息,请参阅本主题后面的 数据模型C++脚本接口。 |
调试主机的脚本主机:IDebugHostScriptHost
IDebugHostScriptHost 接口是脚本提供程序用来从调试主机获取新创建的脚本的上下文的接口。 此上下文包括一个对象(由调试主机提供),其中脚本提供程序可以在数据模型和脚本环境之间放置任何桥梁。 例如,此类网桥可能是调用脚本函数的数据模型方法。 这样做允许数据模型端的调用方通过使用 IModelMethod 接口上的 Call 方法来调用脚本方法。
IDebugHostScriptHost 接口的定义如下。
DECLARE_INTERFACE_(IDebugHostScriptHost, IUnknown)
{
STDMETHOD(CreateContext)(_In_ IDataModelScript* script, _COM_Outptr_ IDataModelScriptHostContext** scriptContext) PURE;
}
CreateContext 方法由脚本提供程序调用,以创建一个新上下文,用于放置脚本的内容。 此类上下文由数据模型C++脚本接口页上详细介绍的 IDataModelScriptHostContext 接口表示。
调试器数据模型C++脚本接口
脚本和脚本接口
数据模型的整体体系结构允许第三方定义某种语言和数据模型的对象模型之间的桥梁。 通常,正在桥接的语言是一种脚本语言,因为数据模型的环境非常动态。 定义和实现数据模型的语言和对象模型之间的此桥的组件称为脚本提供程序。 初始化后,脚本提供程序会将自身注册到数据模型管理器的脚本管理器部分,以及管理扩展性的任何接口随后将允许编辑、加载、卸载和可能调试写入脚本提供程序所管理语言的脚本。
请注意,Windows 调试工具目前定义了两个脚本提供程序。
- NatVis 提供程序。 此提供程序嵌入 NatVis XML 和数据模型之间的 DbgEng.dll 和网桥内,从而允许本机/语言数据类型的可视化。
- JavaScript 提供程序。 此提供程序包含在旧版调试器扩展中:JsProvider.dll。 它桥接用 JavaScript 语言编写的脚本和数据模型,从而允许任意形式的调试器控件和扩展性。
可以将新提供程序编写为其他语言(例如:Python 等...)到数据模型。 这种情况目前封装在旧版调试器扩展中,以便进行加载。 脚本提供程序本身应最大程度地减少与旧引擎接口的依赖关系,并且应尽可能仅利用数据模型 API。 这样,提供商便能够轻松地移植到其他环境。
有两类接口与脚本提供程序相关。 第一类接口用于对脚本提供程序及其管理的脚本进行常规管理。 第二类接口用于支持脚本调试。 虽然对第一个集的支持是必需的,但对第二个集的支持是可选的,并且对于每个提供程序都没有意义。
常规管理接口包括:
接口 | 说明 |
---|---|
IDataModelScriptProvider | 脚本提供程序必须实现的核心接口。 这是向数据模型管理器的脚本管理器部分注册的接口,以便播发提供程序对特定类型的脚本的支持,并针对特定文件扩展名注册 |
IDataModelScript | 由提供程序管理的特定脚本的抽象。 加载或编辑的每个脚本都有单独的 IDataModelScript 实例 |
IDataModelScriptClient | 脚本提供程序用来将信息传达给用户界面的客户端接口。 脚本提供程序不实现此接口。 托管希望使用脚本提供程序的数据模型的应用程序。 脚本提供程序将调用脚本客户端的方法来报告状态、错误等... |
IDataModelScriptHostContext | 脚本提供程序用作脚本内容的容器的主机接口。 除了脚本对调试器应用程序的对象模型执行的作之外,脚本的内容如何取决于特定的调试主机。 此接口允许脚本提供程序获取有关放置其内容的位置的信息。 |
IDataModelScriptTemplate | 脚本提供程序可以提供一个或多个模板,这些模板充当用户创作脚本的起点。 提供内置编辑器的调试器应用程序可以使用提供程序通过此接口播发的模板内容预填充新脚本。 |
IDataModelScriptTemplateEnumerator | 脚本提供程序实现的枚举器接口,用于播发它支持的所有模板。 |
IDataModelNameBinder | 名称绑定器 - 一个对象,该对象可将上下文中的名称与值相关联。 对于给定表达式(如“foo.bar”),名称绑定器能够在对象“foo”的上下文中绑定名称“bar”,并生成值或引用。 名称绑定器通常不是由脚本提供程序实现的;相反,可以从数据模型获取默认绑定器,并由脚本提供程序使用 |
调试接口包括:
接口 | 说明 |
---|---|
IDataModelScriptDebug | 脚本提供程序必须提供的核心接口,以便使脚本可调试。 如果脚本可调试,IDataModelScript 接口的实现类必须 QueryInterface for IDataModelScriptDebug。 |
IDataModelScriptDebugClient | 希望提供脚本调试功能的用户界面实现 IDataModelScriptDebugClient 接口。 脚本提供程序利用此接口来回传递调试信息(例如:发生的事件、断点等...) |
IDataModelScriptDebugStack | 脚本提供程序实现此接口,以向脚本调试器公开调用堆栈的概念。 |
IDataModelScriptDebugStackFrame | 脚本提供程序实现此接口以公开调用堆栈中特定堆栈帧的概念。 |
IDataModelScriptDebugVariableSetEnumerator | 脚本提供程序实现此接口以公开一组变量。 此集可以表示函数的参数集、局部变量集或特定范围内变量集。 确切的含义取决于如何获取接口。 |
IDataModelScriptDebugBreakpoint | 脚本提供程序实现此接口以公开脚本中特定断点的概念和控制。 |
IDataModelScriptDebugBreakpointEnumerator | 脚本提供程序实现此作以枚举脚本中当前存在的所有断点(无论是否已启用)。 |
核心脚本提供程序:IDataModelScriptProvider
任何想要成为脚本提供程序的扩展都必须提供 IDataModelScriptProvider 接口的实现,并通过 RegisterScriptProvider 方法向数据模型管理器的脚本管理器部分注册此类扩展。 必须实现的此核心接口定义如下。
DECLARE_INTERFACE_(IDataModelScriptProvider, IUnknown)
{
STDMETHOD(GetName)(_Out_ BSTR *name) PURE;
STDMETHOD(GetExtension)(_Out_ BSTR *extension) PURE;
STDMETHOD(CreateScript)(_COM_Outptr_ IDataModelScript **script) PURE;
STDMETHOD(GetDefaultTemplateContent)(_COM_Outptr_ IDataModelScriptTemplate **templateContent) PURE;
STDMETHOD(EnumerateTemplates)(_COM_Outptr_ IDataModelScriptTemplateEnumerator **enumerator) PURE;
}
GetName 方法返回提供程序作为通过 SysAllocString 方法分配的字符串所管理的脚本类型(或语言)的名称。 调用方负责通过 SysFreeString 释放返回的字符串。 可能从此方法返回的字符串示例是“JavaScript”或“NatVis”。 返回的字符串很可能显示在承载数据模型的调试器应用程序的用户界面中。 没有两个脚本提供程序可以返回相同的名称(不区分大小写)。
GetExtension 方法返回此提供程序管理的脚本的文件扩展名(不含点),作为通过 SysAllocString 方法分配的字符串。 承载数据模型的调试器应用程序(带有脚本支持)会将使用此扩展的脚本文件打开委托给脚本提供程序。 调用方负责通过 SysFreeString 释放返回的字符串。 此方法可能返回的字符串示例为“js”或“NatVis”。
调用 CreateScript 方法以创建新脚本。 每当调用此方法时,脚本提供程序都必须返回由返回的 IDataModelScript 接口表示的新脚本和空脚本。 请注意,无论用户界面是创建一个新的空白脚本以供用户编辑,还是调试器应用程序正在从磁盘加载脚本,都会调用此方法。 提供程序不涉及文件 I/O。 它仅通过传递给 IDataModelScript 上方法的流处理来自托管应用程序的请求。
GetDefaultTemplateContent 方法返回提供程序的默认模板内容的接口。 这是脚本提供程序希望在新创建的脚本的编辑窗口中预先填充的内容。 如果脚本提供程序没有模板(或者没有指定为默认内容的模板内容),则脚本提供程序可能会从此方法返回E_NOTIMPL。
EnumerateTemplates 方法返回一个枚举器,该枚举器能够枚举脚本提供程序提供的各种模板。 模板内容是脚本提供程序希望在创建新脚本时“预填充”到编辑窗口中的内容。 如果支持多个不同的模板,则可以命名这些模板(例如:“命令性脚本”、“扩展脚本”)和托管数据模型的调试器应用程序可以选择如何将“模板”呈现给用户。
核心脚本接口:IDataModelScript
管理提供程序实现的单个脚本的主接口是 IDataModelScript 接口。 当客户端希望创建新的空白脚本并在 IDataModelScriptProvider 上调用 CreateScript 方法时,将返回实现此接口的组件。
提供程序创建的每个脚本都应位于独立的孤岛中。 除了通过数据模型与外部对象的显式交互外,一个脚本不应影响另一个脚本。 例如,两个脚本都可以扩展某种类型或概念(例如:调试器的流程概念)。 然后,任一脚本都可以通过外部进程对象访问彼此的字段。
接口的定义如下。
DECLARE_INTERFACE_(IDataModelScript, IUnknown)
{
STDMETHOD(GetName)(_Out_ BSTR *scriptName) PURE;
STDMETHOD(Rename)(_In_ PCWSTR scriptName) PURE;
STDMETHOD(Populate)(_In_ IStream *contentStream) PURE;
STDMETHOD(Execute)(_In_ IDataModelScriptClient *client) PURE;
STDMETHOD(Unlink)() PURE;
STDMETHOD(IsInvocable)(_Out_ bool *isInvocable) PURE;
STDMETHOD(InvokeMain)(_In_ IDataModelScriptClient *client) PURE;
}
GetName 方法通过 SysAllocString 函数将脚本的名称作为分配的字符串返回。 如果脚本尚没有名称,该方法应返回 null BSTR。 在这种情况下,它不应失败。 如果通过对 Rename 方法的调用显式重命名脚本,GetName 方法应返回新分配的名称。
Rename 方法将新名称分配给脚本。 脚本实现负责保存此名称,并在对 GetName 方法的任何调用时返回它。 当用户界面选择将脚本另存为新名称时,通常会调用此名称。 请注意,重命名脚本可能会影响宿主应用程序选择投影脚本内容的位置。
客户端调用 Populate 方法以更改或同步脚本的“内容”。 它是向脚本提供程序发出的通知,脚本的代码已更改。 请务必注意,此方法不会导致执行脚本或更改脚本作的任何对象。 这只是脚本提供程序的通知,即脚本的内容已更改,以便它可以同步其自己的内部状态。
Execute 方法根据上次成功填充调用的要求执行脚本的内容,并根据该内容修改调试器的对象模型。 如果语言(或脚本提供程序)定义了一个“main 函数”(作者希望在单击用户界面中的虚构“执行脚本”按钮时调用的函数),则不会在执行操作期间调用此类“main 函数”。 可以将执行操作视为仅执行初始化和对象模型操作(例如:执行根代码并设置扩展点)。
Unlink 方法撤消 Execute 操作。 撤消在执行脚本期间建立的任何对象模型操作或扩展点。 取消链接作后,可以通过调用“执行”重新执行脚本,也可以释放该脚本。
IsInvocable 方法返回脚本是否可调用 -- 也就是说,它是否具有由其语言或提供程序定义的“main 函数”。 在概念上,如果用户界面中按下了虚构的“执行脚本”按钮,脚本作者会希望调用这种“主函数”。
如果脚本有一个“main 函数”,该函数旨在从 UI 调用执行,则它通过 IsInvocable 方法的真实返回来指示此类函数。 然后,用户界面可以调用 InvokeMain 方法以实际“调用”脚本。 请注意,这不同于
**脚本客户端:IDataModelScriptClient **
托管要管理脚本并围绕此概念的用户界面(无论是图形还是控制台)的数据模型的应用程序实现了 IDataModelScriptClient 接口。 在执行或调用或脚本期间,此接口将传递给任何脚本提供程序,以便将错误和事件信息传回用户界面。
IDataModelScriptClient 接口的定义如下。
DECLARE_INTERFACE_(IDataModelScriptClient, IUnknown)
{
STDMETHOD(ReportError)(_In_ ErrorClass errClass, _In_ HRESULT hrFail, _In_opt_ PCWSTR message, _In_ ULONG line, _In_ ULONG position) PURE;
}
如果在执行或调用脚本期间发生错误,脚本提供程序将调用 ReportError 方法以通知用户界面错误。
脚本的主机上下文:IDataModelScriptHostContext
调试主机对数据模型脚本内容的方式和位置有一定的影响。 预期每个脚本都要求主机获取一个上下文,以便将桥放置在脚本(例如,可以调用的函数对象等...)中。通过调用 IDebugHostScriptHostHost 上的 CreateContext 方法并获取 IDataModelScriptHostContext 来检索此上下文。
IDataModelScriptHostContext 接口的定义如下。
DECLARE_INTERFACE_(IDataModelScriptHostContext, IUnknown)
{
STDMETHOD(NotifyScriptChange)(_In_ IDataModelScript* script, _In_ ScriptChangeKind changeKind) PURE;
STDMETHOD(GetNamespaceObject)(_COM_Outptr_ IModelObject** namespaceObject) PURE;
}
脚本提供程序在对关联上下文上的 NotifyScriptChange 方法进行方法调用时,需要向调试主机通知调试主机。 此类作定义为 ScriptChangeKind 枚举的成员
GetNamespaceObject 方法返回一个对象,脚本提供程序可以在数据模型和脚本之间放置任何桥。 例如,在这里,脚本提供程序可以将数据模型方法对象(IModelMethod 接口装箱到 IModelObject 中)放置其实现调用到脚本中相应命名的函数。
新创建的脚本的 模板:IDataModelScriptTemplate
想要为新脚本提供预填充内容的脚本提供程序(例如:帮助用户在调试器用户界面中编写脚本)可以通过提供一个或多个脚本模板来执行此作。 此类模板是实现 IDataModelScriptTemplate 接口的组件,并通过脚本提供程序上的 GetDefaultTemplate 方法或 EnumerateTemplates 方法返回。
IDataModelScriptTemplate 接口的定义如下。
DECLARE_INTERFACE_(IDataModelScriptTemplate, IUnknown)
{
STDMETHOD(GetName)(_Out_ BSTR *templateName) PURE;
STDMETHOD(GetDescription)(_Out_ BSTR *templateDescription) PURE;
STDMETHOD(GetContent)(_COM_Outptr_ IStream **contentStream) PURE;
}
GetName 方法返回模板的名称。 如果模板没有名称,则E_NOTIMPL可能会失败。 单个默认模板(如果存在)不需要具有名称。 所有其他模板都是。 这些名称可能作为菜单的一部分显示在用户界面中,以选择要创建的模板。
GetDescription 方法返回模板的说明。 此类说明将在更具描述性的界面中向用户呈现,以帮助用户了解模板旨在执行的作。 如果没有说明,模板可能会从此方法返回E_NOTIMPL。
GetContent 方法返回模板的内容(或代码)。 如果用户选择从此模板创建新脚本,则会预先填充到编辑窗口中。 该模板负责创建(并返回)客户端可以拉取的内容的标准流。
提供程序模板内容的枚举:IDataModelScriptTemplateEnumerator
脚本提供程序可以提供一个或多个模板,这些模板可在某些用户界面中将内容预填充到新创建的脚本中。 如果提供了这些模板中的任何一个,则脚本提供程序必须对其实现枚举器,该枚举器是在调用 EnumerateTemplates 方法时返回的。
此类枚举器是 IDataModelScriptTemplateEnumerator 接口的实现,定义如下。
DECLARE_INTERFACE_(IDataModelScriptTemplateEnumerator, IUnknown)
{
STDMETHOD(Reset)() PURE;
STDMETHOD(GetNext)(_COM_Outptr_ IDataModelScriptTemplate **templateContent) PURE;
}
Reset 方法将枚举器重置为首次创建枚举器时所处的位置 -- 在生成第一个模板之前。
GetNext 方法将枚举器移动到下一个模板并返回它。 枚举器在枚举结束时返回E_BOUNDS。 命中E_BOUNDS标记后,枚举器将继续无限期生成E_BOUNDS错误,直到进行重置调用。
解析名称的含义:IDataModelNameBinder
数据模型为脚本提供程序提供了一种标准方法,用于确定给定上下文中给定名称的含义(例如,确定将跨各种脚本提供程序运行的 foo.bar 的条形图的含义)。 此机制称为名称绑定器,由 IDataModelNameBinder 接口表示。 此类绑定器封装了一组规则,这些规则介绍了名称的解析方式,以及如何处理在对象上多次定义名称的冲突解决。 这些规则的一部分包括诸如投影名称(由数据模型添加的名称)如何根据本机名称(正在调试的语言的类型系统中的一个)解析。
为了在脚本提供程序之间提供一定程度的一致性,数据模型的脚本管理器提供默认名称绑定器。 可以通过调用 IDataModelScriptManager 接口上的 GetDefaultNameBinder 方法获取此默认名称绑定器。 名称绑定器接口的定义如下。
DECLARE_INTERFACE_(IDataModelNameBinder, IUnknown)
{
STDMETHOD(BindValue)(_In_ IModelObject* contextObject, _In_ PCWSTR name, _COM_Errorptr_ IModelObject** value, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(BindReference)(_In_ IModelObject* contextObject, _In_ PCWSTR name, _COM_Errorptr_ IModelObject** reference, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(EnumerateValues)(_In_ IModelObject* contextObject, _COM_Outptr_ IKeyEnumerator** enumerator) PURE;
STDMETHOD(EnumerateReferences)(_In_ IModelObject* contextObject, _COM_Outptr_ IKeyEnumerator** enumerator) PURE;
}
BindValue 方法根据一组绑定规则对给定对象执行等效 contextObject.name。 此绑定的结果是一个值。 作为值,基础脚本提供程序不能使用该值对名称执行分配。
BindReference 方法类似于 BindValue,因为它也根据一组绑定规则对给定对象执行等效 contextObject.name。 但是,此方法中的绑定的结果是引用而不是值。 作为引用,脚本提供程序可以利用该引用对名称执行分配。
EnumerateValues 方法根据 BindValue 方法的规则枚举将针对对象绑定的名称和值集。 与 IModelObject 上的 EnumerateKeys、EnumerateValues 和类似方法不同,这些方法可能返回具有相同值的多个名称(对于基类、父模型和类似名称),此枚举器将仅返回将与 BindValue 和 BindReference 绑定的特定名称集。 名称永远不会重复。 请注意,通过名称绑定器枚举对象的成本比调用 IModelObject 方法要高得多。
EnumerateReferences 方法枚举名称和对它们的引用集,这些名称将根据 BindReference 方法的规则绑定到对象。 与 IModelObject 上的 EnumerateKeys、EnumerateValues 和类似方法不同,这些方法可能返回具有相同值的多个名称(对于基类、父模型和类似名称),此枚举器将仅返回将与 BindValue 和 BindReference 绑定的特定名称集。 名称永远不会重复。 请注意,通过名称绑定器枚举对象的成本比调用 IModelObject 方法要高得多。
调试器数据模型C++脚本调试接口
数据模型中脚本提供程序的基础结构还提供有关调试脚本的概念。 任何希望向调试主机公开调试功能的脚本以及承载数据模型的调试程序应用程序都可以通过让可调试脚本实现 IDataModelScriptDebug 接口以及 IDataModelScript 接口来实现。 脚本中存在此接口,指示其可调试的基础结构。
虽然 IDataModelScriptDebug 接口是访问脚本提供程序的调试功能的起点,但它由一组其他接口联接,以提供整体调试功能。
调试接口包括:
接口 | 说明 |
---|---|
IDataModelScriptDebug | 脚本提供程序必须提供的核心接口,以便使脚本可调试。 如果脚本可调试,IDataModelScript 接口的实现类必须 QueryInterface for IDataModelScriptDebug。 |
IDataModelScriptDebugClient | 希望提供脚本调试功能的用户界面实现 IDataModelScriptDebugClient 接口。 脚本提供程序利用此接口来回传递调试信息(例如:发生的事件、断点等...) |
IDataModelScriptDebugStack | 脚本提供程序实现此接口,以向脚本调试器公开调用堆栈的概念。 |
IDataModelScriptDebugStackFrame | 脚本提供程序实现此接口以公开调用堆栈中特定堆栈帧的概念。 |
IDataModelScriptDebugVariableSetEnumerator | 脚本提供程序实现此接口以公开一组变量。 此集可以表示函数的参数集、局部变量集或特定范围内变量集。 确切的含义取决于如何获取接口。 |
IDataModelScriptDebugBreakpoint | 脚本提供程序实现此接口以公开脚本中特定断点的概念和控制。 |
IDataModelScriptDebugBreakpointEnumerator | 脚本提供程序实现此作以枚举脚本中当前存在的所有断点(无论是否已启用)。 |
常规管理接口包括:
接口 | 说明 |
---|---|
IDataModelScriptProvider | 脚本提供程序必须实现的核心接口。 这是向数据模型管理器的脚本管理器部分注册的接口,以便播发提供程序对特定类型的脚本的支持,并针对特定文件扩展名注册 |
IDataModelScript | 由提供程序管理的特定脚本的抽象。 加载或编辑的每个脚本都有单独的 IDataModelScript 实例 |
IDataModelScriptClient | 脚本提供程序用来将信息传达给用户界面的客户端接口。 脚本提供程序不实现此接口。 托管希望使用脚本提供程序的数据模型的应用程序。 脚本提供程序将调用脚本客户端的方法来报告状态、错误等... |
IDataModelScriptHostContext | 脚本提供程序用作脚本内容的容器的主机接口。 除了脚本对调试器应用程序的对象模型执行的作之外,脚本的内容如何取决于特定的调试主机。 此接口允许脚本提供程序获取有关放置其内容的位置的信息。 |
IDataModelScriptTemplate | 脚本提供程序可以提供一个或多个模板,这些模板充当用户创作脚本的起点。 提供内置编辑器的调试器应用程序可以使用提供程序通过此接口播发的模板内容预填充新脚本。 |
IDataModelScriptTemplateEnumerator | 脚本提供程序实现的枚举器接口,用于播发它支持的所有模板。 |
IDataModelNameBinder | 名称绑定器 - 一个对象,该对象可将上下文中的名称与值相关联。 对于给定表达式(如“foo.bar”),名称绑定器能够在对象“foo”的上下文中绑定名称“bar”,并生成值或引用。 名称绑定器通常不是由脚本提供程序实现的;相反,可以从数据模型获取默认绑定器,并由脚本提供程序使用。 |
使脚本可调试:IDataModelScriptDebug
任何可调试的脚本都通过实现 IDataModelScriptScript 的同一组件上存在 IDataModelScriptDebug 接口来指示此功能。 调试主机或托管数据模型的调试器应用程序对此接口的查询指示调试功能的存在。
IDataModelScriptDebug 接口的定义如下。
DECLARE_INTERFACE_(IDataModelScriptDebug, IUnknown)
{
STDMETHOD_(ScriptDebugState, GetDebugState)() PURE;
STDMETHOD(GetCurrentPosition)(_Out_ ScriptDebugPosition *currentPosition, _Out_opt_ ScriptDebugPosition *positionSpanEnd, _Out_opt_ BSTR *lineText) PURE;
STDMETHOD(GetStack)(_COM_Outptr_ IDataModelScriptDebugStack **stack) PURE;
STDMETHOD(SetBreakpoint)(_In_ ULONG linePosition, _In_ ULONG columnPosition, _COM_Outptr_ IDataModelScriptDebugBreakpoint **breakpoint) PURE;
STDMETHOD(FindBreakpointById)(_In_ ULONG64 breakpointId, _COM_Outptr_ IDataModelScriptDebugBreakpoint **breakpoint) PURE;
STDMETHOD(EnumerateBreakpoints)(_COM_Outptr_ IDataModelScriptDebugBreakpointEnumerator **breakpointEnum) PURE;
STDMETHOD(GetEventFilter)(_In_ ScriptDebugEventFilter eventFilter, _Out_ bool *isBreakEnabled) PURE;
STDMETHOD(SetEventFilter)(_In_ ScriptDebugEventFilter eventFilter, _In_ bool isBreakEnabled) PURE;
STDMETHOD(StartDebugging)(_In_ IDataModelScriptDebugClient *debugClient) PURE;
STDMETHOD(StopDebugging)(_In_ IDataModelScriptDebugClient *debugClient) PURE;
}
GetDebugState 方法返回脚本的当前状态(例如:是否正在执行)。 状态由 ScriptDebugState 枚举中的值定义。
GetCurrentPosition 的方法返回脚本中的当前位置。 仅当脚本被分解为调试器时,才会调用 GetScriptState 将返回 ScriptDebugBreak。 对此方法的任何其他调用都无效,并且将失败。
GetStack 方法获取中断位置处的当前调用堆栈。 仅当脚本被分解到调试器中时,才能调用此方法。
SetBreakpoint 方法在脚本中设置断点。 请注意,实现可以自由调整传入的行和列位置,以前进到适当的代码位置。 通过对返回的 IDataModelScriptDebugBreakpoint 接口的方法调用,可以检索放置断点的实际行号和列号。
通过 SetBreakpoint 方法在脚本中创建的每个断点都由实现分配唯一标识符(64 位无符号整数)。 FindBreakpointById 方法用于从给定标识符获取断点的接口。
EnumerateBreakpoints 方法返回一个枚举器,该枚举器能够枚举特定脚本中设置的每个断点。
GetEventFilter 方法返回是否为特定事件启用“中断事件”。 ScriptDebugEventFilter 枚举的成员描述了可能导致“事件中断”的事件。
SetEventFilter 方法根据 ScriptDebugEventFilter 枚举的成员定义的特定事件更改“事件中断”行为。 可以在 GetEventFilter 方法的文档中找到可用事件的完整列表(以及此枚举的说明)。
StartDebugging 方法“打开”特定脚本的调试器。 启动调试的行为不会主动导致任何执行中断或单步执行。 它只是使脚本可调试,并提供一组接口供客户端与调试接口通信。
StopDebugging 方法由想要停止调试的客户端调用。 成功执行 StartDebugging 后(例如:在中断期间,同时执行脚本等)后,可以随时调用此方法。调用会立即停止所有调试活动,并在调用 StartDebugging 之前重置状态。
调试接口:IDataModelScriptDebugClient
希望围绕脚本调试提供接口的调试主机或调试程序应用程序必须通过脚本调试接口上的 StartDebugging 方法向脚本调试器提供 IDataModelScriptDebugClient 接口的实现。
IDataModelScriptDebugClient 是传递调试事件的通信通道,控制从脚本执行引擎传递到调试器接口。 它的定义如下。
DECLARE_INTERFACE_(IDataModelScriptDebugClient, IUnknown)
{
STDMETHOD(NotifyDebugEvent)(_In_ ScriptDebugEventInformation *pEventInfo, _In_ IDataModelScript *pScript, _In_opt_ IModelObject *pEventDataObject, _Inout_ ScriptExecutionKind *resumeEventKind) PURE;
}
每当发生任何中断脚本调试器的事件时,调试代码本身就会通过 NotifyDebugEvent 方法调用接口。 此方法是同步的。 在接口从事件返回之前,不会继续执行脚本。 脚本调试器的定义很简单:绝对没有需要处理的嵌套事件。 调试事件由称为 ScriptDebugEventInformation 的变体记录定义。 事件信息中的哪些字段有效主要由 DebugEvent 成员定义。 它定义由 ScriptDebugEvent 枚举的成员描述发生的事件类型。
调用堆栈:IDataModelScriptDebugStack
当发生中断脚本调试器的事件时,调试接口将想要检索中断位置的调用堆栈。 这是通过 GetStack 方法完成的。 此类堆栈通过定义如下的 IDataModelScriptDebugStack 来表示。
请注意,整个堆栈可以跨越多个脚本和/或多个脚本提供程序。 从对特定脚本调试接口上的 GetStack 方法的单个调用返回的调用堆栈应仅返回该脚本边界内的调用堆栈段。 如果同一提供程序的两个脚本进行交互,脚本调试引擎可以检索调用堆栈,这完全可以跨多个脚本上下文。 GetStack 方法不应返回另一个脚本中的堆栈部分。 相反,如果检测到这种情况,则脚本中边界帧的堆栈帧应通过该堆栈帧上的 IsTransitionPoint 和 GetTransition 方法的实现将自身标记为转换帧。 预计调试器接口将从存在的多个堆栈段将整体堆栈拼凑在一起。
必须以这种方式实现转换,或者调试接口可能会将有关局部变量、参数、断点和其他特定于脚本的构造的查询定向到错误的脚本上下文! 这将导致调试器接口中未定义的行为。
DECLARE_INTERFACE_(IDataModelScriptDebugStack, IUnknown)
{
STDMETHOD_(ULONG64, GetFrameCount)() PURE;
STDMETHOD(GetStackFrame)(_In_ ULONG64 frameNumber, _COM_Outptr_ IDataModelScriptDebugStackFrame **stackFrame) PURE;
}
GetFrameCount 方法返回此调用堆栈段中的堆栈帧数。 如果提供程序可以检测不同脚本上下文或不同提供程序中的帧,则它应通过实现 IsTransitionPoint 和入口帧上的 GetTransition 方法向调用方指示此堆栈段。
GetStackFrame 从堆栈段获取特定的堆栈帧。 调用堆栈具有从零开始的索引系统:发生中断事件的当前堆栈帧是帧 0。 当前方法的调用方为帧 1(依此类推)。
在损坏时 检查状态:IDataModelScriptDebugStackFrame
当闯入脚本调试器时,可以通过对 IDataModelScriptDebugStack 接口上的 GetStackFrame 方法的调用来检索调用堆栈的特定帧,该接口表示发生中断的堆栈段。 返回表示此帧的 IDataModelScriptDebugStackFrame 接口定义如下。
DECLARE_INTERFACE_(IDataModelScriptDebugStackFrame, IUnknown)
{
STDMETHOD(GetName)(_Out_ BSTR *name) PURE;
STDMETHOD(GetPosition)(_Out_ ScriptDebugPosition *position, _Out_opt_ ScriptDebugPosition *positionSpanEnd, _Out_opt_ BSTR *lineText) PURE;
STDMETHOD(IsTransitionPoint)(_Out_ bool *isTransitionPoint) PURE;
STDMETHOD(GetTransition)(_COM_Outptr_ IDataModelScript **transitionScript, _Out_ bool *isTransitionContiguous) PURE;
STDMETHOD(Evaluate)(_In_ PCWSTR pwszExpression, _COM_Outptr_ IModelObject **ppResult) PURE;
STDMETHOD(EnumerateLocals)(_COM_Outptr_ IDataModelScriptDebugVariableSetEnumerator **variablesEnum) PURE;
STDMETHOD(EnumerateArguments)(_COM_Outptr_ IDataModelScriptDebugVariableSetEnumerator **variablesEnum) PURE;
}
GetName 方法返回此帧的显示名称(例如:函数名称)。 此类名称将显示在调试器界面中向用户显示的堆栈回溯中。
GetPosition 方法返回堆栈帧所表示的脚本中的位置。 仅当脚本位于包含此帧的堆栈所表示的中断中时,才能调用此方法。 始终返回此帧中的行和列位置。 如果调试器能够返回脚本中“执行位置”的跨度,则可以在 positionSpanEnd 参数中返回结束位置。 如果调试器无法执行此作,范围末尾的行和列值(如果请求)应设置为零。
IDataModelScriptDebugStack 接口表示调用堆栈的段 -- 调用堆栈的该部分包含在一个脚本的上下文中。 如果调试器能够检测从一个脚本到另一个脚本(或一个脚本提供程序到另一个脚本提供程序)的转换,则可以通过实现 IsTransitionPoint 方法并根据需要返回 true 或 false 来指示这一点。 进入应用段的脚本的调用堆栈帧应被视为转换点。 所有其他帧都不是。
如果给定的堆栈帧是 IsTransition 方法确定的转换点(请参阅该位置的转换点定义的文档),则 GetTransition 方法返回有关转换的信息。 具体而言,此方法返回上一个脚本 ,即调用包含此 IDataModelScriptDebugStackFrame 的堆栈段所表示的脚本。
Evaluate 方法在调用此方法的 IDataModelScriptDebugStackFrame 接口所表示的堆栈帧上下文中计算表达式(脚本提供程序的语言)。 表达式计算的结果必须作为 IModelObject 从脚本提供程序中封送出来。 生成的 IModelObject 上的属性和其他构造必须在调试器处于中断状态时获取。
EnumerateLocals 方法返回一个变量集(由 IDataModelScriptDebugVariableSetEnumerator 接口表示),这些变量位于调用此方法的 IDataModelScriptDebugStackFrame 接口所表示的堆栈帧上下文中。
EnumerateArguments 方法为调用此方法的 IDataModelScriptDebugStackFrame 接口所表示的堆栈帧中调用的函数的所有函数参数返回一个变量集(由 IDataModelScriptDebugVariableSetEnumerator 接口表示)。
查看变量:IDataModelScriptDebugVariableSetEnumerator
正在调试的脚本中的一组变量(是否位于特定范围、函数的局部变量、函数的参数等...)由通过 IDataModelScriptDebugVariableSetEnumerator 接口定义的变量集表示:
DECLARE_INTERFACE_(IDataModelScriptDebugVariableSetEnumerator, IUnknown)
{
STDMETHOD(Reset)() PURE;
STDMETHOD(GetNext)(_Out_ BSTR *variableName, _COM_Outptr_opt_ IModelObject **variableValue, _COM_Outptr_opt_result_maybenull_ IKeyStore **variableMetadata) PURE;
}
Reset 方法将枚举器的位置重置为创建后紧邻的位置,即在集的第一个元素之前。
GetNext 方法将枚举器移动到集中的下一个变量,并返回变量的名称、值和与之关联的任何元数据。 如果枚举器已命中集的末尾,则返回错误E_BOUNDS。 从 GetNext 方法返回E_BOUNDS标记后,除非进行干预重置调用,否则它将在再次调用时继续生成E_BOUNDS。
断点:IDataModelScriptDebugBreakpoint
脚本断点是通过给定脚本的调试接口上的 SetBreakpoint 方法设置的。 此类断点由唯一 ID 和 IDataModelScriptDebugBreakpoint 接口的实现表示,如下所示。
DECLARE_INTERFACE_(IDataModelScriptDebugBreakpoint, IUnknown)
{
STDMETHOD_(ULONG64, GetId)() PURE;
STDMETHOD_(bool, IsEnabled)() PURE;
STDMETHOD_(void, Enable)() PURE;
STDMETHOD_(void, Disable)() PURE;
STDMETHOD_(void, Remove)() PURE;
STDMETHOD(GetPosition)(_Out_ ScriptDebugPosition *position, _Out_opt_ ScriptDebugPosition *positionSpanEnd, _Out_opt_ BSTR *lineText) PURE;
}
GetId 方法将脚本提供程序的调试引擎分配给断点的唯一标识符。 此标识符在包含脚本的上下文中必须是唯一的。 断点标识符可能对提供程序是唯一的;但是,这不是必需的。
IsEnabled 方法返回是否启用断点。 禁用的断点仍然存在,仍在脚本的断点列表中,它只是暂时“关闭”。 应以启用状态创建所有断点。
Enable 方法启用断点。 如果断点被禁用,调用此方法后“命中断点”将导致调试器中断。
Disable 方法禁用断点。 在此调用后,调用此方法后“命中断点”不会中断调试器。 断点(仍存在)被视为“已关闭”。
Remove 方法从其包含列表中删除断点。 此方法返回后,断点不再以语义方式存在。 表示断点的 IDataModelScriptDebugBreakpoint 接口在调用后被视为孤立的。 除了发布它之外,其他任何作都无法(在法律上)完成。
GetPosition 方法返回脚本中断点的位置。 脚本调试器必须返回断点所在的源代码中的行和列。 如果它能够执行此作,则它还可以通过填写 positionSpanEnd 参数定义的结束位置来返回由断点表示的源范围。 如果调试器无法生成此范围,并且调用方请求它,则范围结束位置的“行”和“列”字段应填充为零,指示无法提供值。
断点枚举:IDataModelScriptDebugBreakpointEnumerator
如果脚本提供程序支持调试,则它还必须跟踪与每个脚本关联的所有断点,并且能够将这些断点枚举到调试接口。 断点的枚举器是通过给定脚本调试接口上的 EnumerateBreakpoints 方法获取的,定义如下。
DECLARE_INTERFACE_(IDataModelScriptDebugBreakpointEnumerator, IUnknown)
{
STDMETHOD(Reset)() PURE;
STDMETHOD(GetNext)(_COM_Outptr_ IDataModelScriptDebugBreakpoint **breakpoint) PURE;
}
Reset 方法将枚举器的位置重置为在创建枚举器之后的位置,即在第一个枚举断点之前。
GetNext 方法将枚举器向前移动到要枚举的下一个断点,并返回该断点的 IDataModelScriptDebugBreakpoint 接口。 如果枚举器已到达枚举的末尾,它将返回E_BOUNDS。 生成E_BOUNDS错误后,除非对 Reset 方法进行干预调用,否则对 GetNext 方法的后续调用将继续生成E_BOUNDS。
另请参阅
本主题是一系列教程的一部分,其中介绍了可从C++访问的接口、如何使用它们生成基于C++的调试器扩展,以及如何从C++数据模型扩展中使用其他数据模型构造(例如:JavaScript 或 NatVis)。