通过 DynamicObject 的 TryInvokeMember 重写使用扩展方法

发布于 2024-11-05 23:52:19 字数 3759 浏览 1 评论 0 原文

我觉得我需要提供一些背景信息来说明我为什么要做我正在做的事情,因为我想接受建议和批评,并给可能阅读本文的使用 Blender 的 XNA 程序员带来希望。考虑到这篇文章的长度,如果您不关心我的问题的具体细节,请随时跳到最后一段。

我正在开发一个 XNA Content Pipeline 扩展项目,该项目读取 .blend 文件(由 Blender 创建)并进行转换它们可以从 XNA 游戏加载数据,以避免每次我进行任何小调整时都必须从 Blender 导出新的 .FBX 或 .OBJ 模型,以及(希望)为Blender 令人惊叹的粒子和物理功能。

在不深入了解 Blender 的内部工作原理的情况下,我想描述一下我的理解.blend 文件如何工作。如果您对这个主题更了解,请纠正我。

Blender 将文件保存在字节“块”中。这些块中的大多数包含表示 3D 场景中的对象和设置的数据,文件中的最后一个块(称为 SDNA 块)包含可以视为非常简单的 C 样式结构的内容,每个块都有一个唯一的标识符,以及多个不同类型的字段。这些结构体的字段可以是简单类型,例如intfloat,也可以是SDNA块中定义的类型。

例如,下面是 ID SDNA 结构的半伪代码表示:

structure IDPropertyData
{
    void *pointer;
    ListBase group;
    int val;
    int val2;
}

如您所见,字段 *pointerval、和 val2 可以在运行时用 void*int 类型的简单值表示。然而,group 字段的类型为 ListBase,它是在文件的 SDNA 块中的其他位置定义的。

这就是 C# 的新动态功能发挥作用的地方:我创建了一个类(称为 BlenderObject),给定 SDNA 结构(它是“SDNA 类型”)和一大块字节,其行为如下通过存储其自身的简单类型值或其他 BlenderObject 实例的集合(每个实例代表其“字段”之一)来创建该结构的实例。这允许我的库的用户编写如下代码:

//Get a BlendContent instance that contains the file's information.
BlendContent content = BlendContent.Read(filePath);
//Get the 0th (and only) block containing data for the scene (code "SC")
BlendFileBlock sceneBlock = content.FileBlocks["SC", 0];
//Get the BlenderObject that represents the scene
dynamic scene = sceneBlock.Object;
//Get the scene's "r" field, whose SDNA type is RenderData.
dynamic renderData = scene.r;
//Get the x and y resolution of the rendered scene
float
    xParts = renderData.xparts,
    yParts = renderData.yparts;

scenerenderData 都是“复杂”的 BlenderObject 实例(每个都有一个集合字段,而不是直接值),并且 xpartsyparts 都是“简单”BlenderObject 实例(每个实例都有一个直接的、简单的- 为其本身键入值,而不是集合字段)。如果每个 BlenderObject 的 SDNA 类型是程序集中的具体编译类型,则它的行为方式与您期望的完全一致,这是我使用动态来表示 Blender 对象的目标。

为了简化我的库的使用,我正在努力重载 DynamicObject 的方法,使“简单”BlenderObject 实例的行为与其直接值相同。例如,让 foo 是一个 BlenderObject,其直接 int 类型值为 4。我希望能够做到以下为 foo

string s = foo.ToString();
Console.WriteLine(s);

第一行的目的是调用 foo 内部值的 ToString 方法,而不是 foo 本身,所以 fooTryInvokeMember 重写使用反射在其值 4 上调用 Int32.ToString(),而不是对其自身调用 BlenderObject.ToString()。这种反射方法调用效果很好(同样的概念也适用于数组类型直接值的索引器),除非我尝试如下操作:

string s = foo.Bar();
Console.WriteLine(s);

Bar 是我的程序集中定义的扩展方法,因此反映 foo 的值 4 显然无法找到它,当然,还会抛出异常。最后,我的问题是:

如何找到和/或缓存可应用于对象的扩展方法?我知道如何通过反射查找扩展方法,但每次都这样做在动态 BlenderObject 实例上调用扩展方法会非常很慢。除了该问题中描述的方法之外,是否有更快的方法来查找扩展方法?如果没有,我应该如何实习扩展方法,这样我只需要找到它们一次就可以快速再次访问它们?抱歉这么久了,并提前感谢任何有用的答案/评论。

编辑:

正如@spender回答的那样,解决我的问题的一个简单方法是使用字典(尽管我使用的是Dictionary> 轻松使用传递给 DynamicObject.TryInvokeMemberInvokeMemberBinder 提供的 CallInfo)。不过,我的实现给我带来了另一个问题:

如何获取调用程序集引用的程序集中定义的类型? 例如,考虑以下代码: 对象 Foo(动态 BlenderObject) { 返回 BlenderObject.x.Baz(); } 如果此代码位于引用我的 Blender 库的项目中,并且扩展方法 Baz() 是在该项目引用的另一个程序集中定义的,但不是由我的 Blender 管道引用的,那么我将如何查找 < code>Baz() 来自我的 Blender 管道?这可能吗?

I feel like I need to give a little background information on why I'm doing what I'm doing, because I want to open it to suggestions and criticism, and give hope to Blender-using XNA programmers that might read this. Considering the length of this post, please feel free to skip to the last paragraph if you don't care about the nitty-gritty details of my problem.

I'm working on an XNA Content Pipeline extension project that reads .blend files (created by Blender) and converts them to data loadable from an XNA game, to avoid having to export a new .FBX or .OBJ model from Blender every time I make any little tweak, as well as (hopefully) make some XNA-compatible support for Blender's awesome particle and physics capabilities.

Without diving too deep into Blender's inner workings, I'd like to describe my understanding of how .blend files work. Please correct me if you're more knowledgeable on the subject.

Blender saves files in "blocks" of bytes. Most of these blocks contain data representing objects and settings in the 3D scene, and the last block in the file (called the SDNA block), contains what can be thought of as very simple C-style structures, each of which has a unique identifier, as well as several fields of various types. Fields of these structures may be of simple types, such as int or float, or they may be of the types defined in the SDNA block.

For example, here's a semi-pseudo-code representation of the ID SDNA structure:

structure IDPropertyData
{
    void *pointer;
    ListBase group;
    int val;
    int val2;
}

As you can see, the fields *pointer, val, and val2 can be represented at runtime by simple values of type void* or int. The group field, however, is of type ListBase, which is defined elsewhere in the file's SDNA block.

This is where C#'s new dynamic features come to play: I've made a class (called BlenderObject) that, given an SDNA structure (it's "SDNA type"), and a chunk of bytes, behaves as an instance of that structure by storing either a simple-type value for itself, or a collection of other BlenderObject instances, each representing one of its "fields". This allows the user of my library to write code like the following:

//Get a BlendContent instance that contains the file's information.
BlendContent content = BlendContent.Read(filePath);
//Get the 0th (and only) block containing data for the scene (code "SC")
BlendFileBlock sceneBlock = content.FileBlocks["SC", 0];
//Get the BlenderObject that represents the scene
dynamic scene = sceneBlock.Object;
//Get the scene's "r" field, whose SDNA type is RenderData.
dynamic renderData = scene.r;
//Get the x and y resolution of the rendered scene
float
    xParts = renderData.xparts,
    yParts = renderData.yparts;

scene and renderData are both "complex" BlenderObject instances (each having a collection of fields, rather than a direct value), and xparts, and yparts are both "simple" BlenderObject instances (each having a direct, simple-type value for itself, rather than a collection of fields). Each BlenderObject behaves exactly the way you'd expect it to if its SDNA type was a concretely compiled type in the assembly, which is my goal in using dynamics to represent Blender objects.

To simplify using my library, I'm working on overloading DynamicObject's methods in a way that makes "simple" BlenderObject instances behave as their direct values. For example, let foo be a BlenderObject with a direct int-type value of, let's say, 4. I want to be able to do the following with foo:

string s = foo.ToString();
Console.WriteLine(s);

The intention of the first line is to invoke the ToString method of foo's inner value, not of foo itself, so the foo's TryInvokeMember override uses reflection to invoke Int32.ToString() on its value of 4, rather than BlenderObject.ToString() on itself. This reflective method-calling works great (and so does the same concept applied to indexers for array-type direct values), except when I try something like the following:

string s = foo.Bar();
Console.WriteLine(s);

Bar is an extension method defined in my assembly, and so reflecting foo's value of 4 obviously fails to find it, and, of course, exceptions are thrown. My question, finally, is this:

How can I find and/or cache extension methods that can be applied to an object? I know how to find extension methods with reflection, but doing this every time an extension method is called on a dynamic BlenderObject instance would be really slow. Is there a faster way to find extension methods, other than the one described in that question? If not, how should I go about interning extension methods so I only need to find them once and can quickly access them again? Sorry for the longevity, and thanks in advance for any helpful answers/comments.

EDIT:

As @spender answered, an easy way to solve my problem is with a dictionary (although I'm using a Dictionary<Type, Dictionary<CallInfo, MethodInfo>> to easily use the CallInfo provided by the InvokeMemberBinder passed to DynamicObject.TryInvokeMember). My implementation has brought me to another question, though:

How can I get the types defined in assemblies referenced by the calling assembly?
For example, consider this code:
object Foo(dynamic blenderObject)
{
return blenderObject.x.Baz();
}
If this code's in a project that references my Blender library, and the extension method Baz() is defined in another assembly referenced by that project, but not by my blender pipeline, how would I go about finding Baz() from within my Blender pipeline? Is this even possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

君勿笑 2024-11-12 23:52:19

我只需将发现的方法缓存在前面的 Dictionary> 中。查找速度会很快。

I'd simply cache the discovered methods in a Dictionary<Type,ICollection<MethodInfo>> up front. The lookup will be fast.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文