Tekla 二次开发:Object API 完全解析 - 钢结构资源网 Tekla插件 CAD工具 犀牛GH汉化 套料 Tekla 二次开发:Object API 完全解析 - 钢结构资源网 Tekla插件 CAD工具 犀牛GH汉化 套料

Tekla 二次开发:Object API 完全解析

Tekla Structures Object API 完全解析

Tekla.Structures.Model.Object 是 Tekla Structures Open API 中绝对根抽象基类。一切模型对象——从 BeamBoltArrayWeldAssembly——都直接或间接地继承自这个仅有 37 行代码的抽象类。它定义了所有 Tekla 对象的共通身份:Identifier 属性及其与 C++ 内核的序列化桥梁。

本文基于 Tekla.Structures.Model 程序集(Version=2017.0.0.0)的反编译源码,深入解析 Object 类的完整定义、Identifier 生命周期、COM 互操作机制以及它在整个 Tekla API 体系中的根基地位。

一、继承体系

Object 是 Tekla 模型对象层级树的绝对根节点。它直接继承自 System.Object,声明为 public abstract class Object,意味着任何外部的 Tekla 对象都必定继承自此基类:

System.Object
  └── Object (abstract)
        └── ModelObject (abstract)
              ├── Part (abstract) → Beam, ContourPlate, PolyBeam, Brep, BentPlate...
              ├── BaseComponent (abstract) → Connection, Component, Detail, CustomPart...
              ├── Assembly
              ├── Brep
              ├── ReferenceModel
              └── ... 共 61 种派生类型

[图 1] Object 继承体系全景图 — 以 Object 为绝对根的全部派生类

Object 类标记了三个关键特性(Attribute):

特性 说明
[ClassInterface(ClassInterfaceType.AutoDual)] COM 互操作:自动为托管类生成 COM 接口,支持早期绑定(vtable)和后期绑定(IDispatch)
[Guid("4668C67A-...")] COM 唯一标识符:为类在 COM 注册表中分配全局唯一 ID(CLSID)
[Serializable] 序列化支持:标记该类及其所有派生类均可被 .NET 二进制序列化
[提示] 设计意图:这三个特性的组合揭示了 Object 类的核心使命——作为 .NET 托管代码与 Tekla C++ 非托管内核之间的 互操作桥梁[ClassInterface(AutoDual)] 使得 C++ 可以通过 COM 调用 .NET 对象;[Serializable] 确保对象状态可以在进程间传递。

二、构造函数

Object 的构造函数声明为 protected,这意味着外部代码不能直接实例化 Object。只有派生类可以调用此构造函数来初始化 Identifier 属性。

// Object.cs 构造函数源码
protected Object()
{
    this.Identifier = new Identifier();
}

构造函数执行以下关键初始化:

操作 说明
new Identifier() 创建一个默认的 Identifier 实例,此时 ID = 0(无效状态)
this.Identifier = ... 将标识符赋值给当前对象,完成身份准备
继承链触发 当具体子类(如 Beam)通过 new Beam() 创建时,会自动触发 Object()ModelObject()Beam() 构造链
[!] 重要:构造函数执行后,Identifier.ID = 0(无效)。这意味着对象仅存在于 .NET 内存中,尚未与 Tekla 模型数据库建立任何关联。只有调用 Insert()Select()(由子类实现)后,Identifier 才会获得有效值。

三、属性

Object 仅定义了一个公开属性——Identifier。这是整个 Tekla API 中最核心、最基础的属性。

Identifier 属性

属性名 类型 可读写 说明
Identifier Identifier 读 / 写 模型对象的唯一标识符。ID=0 表示无效(未关联数据库);ID>0 表示有效

Identifier 生命周期

[图 2] Identifier 完整生命周期:从创建到回收

Identifier 的生命周期贯穿整个 Tekla 二次开发流程:

阶段 状态 说明
new Beam() ID = 0(无效) 子类构造函数触发 protected Object(),创建默认 Identifier
MatchIdentifier() ID = 哈希值 通过 Model.GetModelObjectSelector() 遍历时自动匹配,产生有效 ID
Insert() ID > 0(有效) 首次写入数据库后获得系统分配的正整数 ID
Select() 成功 ID > 0(有效) 已知 ID 选择已有对象,填充属性数据
Modify() ID > 0(有效) 修改已存在对象,ID 不变
Delete() ID = 0(无效) 删除后 ID 被置零(见 DeleteInstance() 源码),对象引用失效

Identifier 源码结构

// Identifier 核心结构
public struct Identifier
{
    public long ID { get; set; }        // 对象唯一标识 (0=无效, >0=有效)
    public bool IsValid()                // 判断 ID 是否有效
    {
        return this.ID > 0L;
    }
}

四、枚举类型

Object 类本身未定义任何枚举类型。作为绝对根基类,它的设计极为精简,仅提供基础设施(Identifier 属性 + 序列化桥接),将类型的枚举和分类职责交给了派生类——ModelObject.ModelObjectEnum 定义了 61 种模型对象枚举值。

[提示] 设计原则:Object 遵循"最小接口"原则。它只包含所有 Tekla 对象必然共同需要的部分:一个身份(Identifier)和一个序列化通道。任何更多的东西(如类型枚举、CRUD 操作、属性系统)都留给子类按需添加。

五、方法

Object 类的所有方法均为 internal 访问级别,不暴露给外部二次开发代码。它们构成了 .NET 与 Tekla C++ 内核之间的序列化通道。

自身方法

方法 访问级别 返回类型 说明
ToStruct(ref dotObject_t) internal void 将当前 Object 序列化为互操作结构体 dotObject_t
FromStruct(ref dotObject_t) internal void 从互操作结构体 dotObject_t 反序列化回 Object
Object() protected 构造函数 初始化 Identifier = new Identifier()

序列化详细机制

[图 3] Object 序列化架构:从用户代码到 C++ 内核的四层数据流

序列化是 Object 类最核心的功能。整个数据流分为四层:

ToStruct — 托管→非托管序列化

// 将 .NET Object 导出为 C++ 结构体
internal void ToStruct(ref dotObject_t dotObject)
{
    dotObject.Identifier = new dotIdentifier_t(this.Identifier);
}

该方法将 .NET Identifier 转换为 COM 互操作结构体 dotIdentifier_t,存入 dotObject_tIdentifier 字段。这个转换是单向的——从托管类型到非托管类型,用于将数据传入 C++ 内核。

FromStruct — 非托管→托管反序列化

// 从 C++ 结构体恢复 .NET Object
internal void FromStruct(ref dotObject_t dotObject)
{
    this.Identifier = dotObject.Identifier.FromStruct();
}

该方法从传入的 dotObject_t 中提取 Identifier 字段,调用 dotIdentifier_t.FromStruct() 将其转换为 .NET Identifier 并回填到当前对象。这个反向转换用于从 C++ 内核读取数据到 .NET 层。

dotObject_t 互操作结构体

// 内部结构体 — Object 的 C++ 对应物
public struct dotObject_t
{
    public dotIdentifier_t Identifier;  // 仅包含一个字段
}

dotObject_t 是非常简洁的结构体——只包含一个 dotIdentifier_t Identifier 字段。这体现了 Object 的极简设计:在 C++ 层,一个 Tekla 对象的本质就是一个标识符。所有扩展属性(如 BeamStartPointProfile 等)都由子类的 dotXxx_t 结构体承载。

[图 4] Object 类成员全景:1 属性 + 2 方法 + 3 特性

六、完整代码示例

虽然 Object 是抽象类不能直接使用,但了解其工作原理对于深入理解 Tekla API 至关重要。以下是展示 Object 核心概念的实际代码示例。

示例 1:Identifier 生命周期追踪

using System;
using Tekla.Structures.Model;
using Tekla.Structures.Geometry3d;

public void TraceIdentifierLifecycle()
{
    // ===== 阶段 1:构造 (ID = 0,无效) =====
    Beam myBeam = new Beam();
    Console.WriteLine($"1. After new Beam(): ID={myBeam.Identifier.ID}, " +
        $"Valid={myBeam.Identifier.IsValid()}");
    // 输出: ID=0, Valid=False
    // 此时仅存在于 .NET 内存,未关联 Tekla 模型

    // ===== 阶段 2:设置属性 =====
    myBeam.StartPoint = new Point(0, 0, 0);
    myBeam.EndPoint = new Point(0, 0, 3000);
    myBeam.Profile.ProfileString = "HEA200";
    myBeam.Material.MaterialString = "S235JR";
    myBeam.Class = "2";

    Console.WriteLine($"2. After property setup: ID={myBeam.Identifier.ID}");
    // 输出: ID=0 (属性设置不改变 ID)

    // ===== 阶段 3:插入数据库 =====
    bool insertOk = myBeam.Insert();
    Console.WriteLine($"3. After Insert(): ID={myBeam.Identifier.ID}, " +
        $"Valid={myBeam.Identifier.IsValid()}, Result={insertOk}");
    // 输出: ID=12345 (系统分配), Valid=True, Result=True

    // ===== 阶段 4:通过 Select 获取已有对象 =====
    Beam selectedBeam = new Beam();
    selectedBeam.Identifier = new Identifier(myBeam.Identifier.ID);
    bool selectOk = selectedBeam.Select();
    Console.WriteLine($"4. After Select({myBeam.Identifier.ID}): " +
        $"Valid={selectedBeam.Identifier.IsValid()}, Result={selectOk}");
    // 输出: Valid=True, Result=True
    // 从数据库加载了完整属性数据

    // ===== 阶段 5:修改 =====
    selectedBeam.Class = "3";
    bool modifyOk = selectedBeam.Modify();
    Console.WriteLine($"5. After Modify(): ID={selectedBeam.Identifier.ID}, " +
        $"Result={modifyOk}");
    // 输出: ID=12345 (不变), Result=True

    // ===== 阶段 6:删除 =====
    bool deleteOk = selectedBeam.Delete();
    Console.WriteLine($"6. After Delete(): ID={selectedBeam.Identifier.ID}, " +
        $"Result={deleteOk}");
    // 输出: ID=0 (归零), Valid=False, Result=True
    // 对象引用已失效,不可再用于其他操作
}

示例 2:理解 Object 作为类型基类

using System;
using System.Collections.Generic;
using Tekla.Structures.Model;

public void DemonstrateObjectAsBaseClass()
{
    // ===== Object 是 Tekla 类型系统的根基 =====
    // 所有 Tekla 模型对象都是 Object 的后代

    // 创建各种类型的对象
    Beam beam = new Beam();
    ContourPlate plate = new ContourPlate();
    BoltArray bolts = new BoltArray();
    Weld weld = new Weld();
    Assembly assembly = new Assembly();

    // 验证它们都可以向上转型为 Object
    Object obj1 = beam;      // Beam → ModelObject → Object
    Object obj2 = plate;     // ContourPlate → Part → ModelObject → Object
    Object obj3 = bolts;     // BoltArray → ModelObject → Object
    Object obj4 = weld;      // Weld → ModelObject → Object
    Object obj5 = assembly;  // Assembly → ModelObject → Object

    Console.WriteLine("=== 所有 Tekla 对象都继承自 Object ===");
    Console.WriteLine($"Beam is Object:         {beam is Object}");
    Console.WriteLine($"ContourPlate is Object: {plate is Object}");
    Console.WriteLine($"BoltArray is Object:    {bolts is Object}");
    Console.WriteLine($"Weld is Object:         {weld is Object}");
    Console.WriteLine($"Assembly is Object:     {assembly is Object}");
    // 全部输出 True

    // ===== Identifier 是 Object 定义的唯一公共属性 =====
    // 所有子类都能通过 Identifier 进行身份管理
    Console.WriteLine($"\n=== Identifier 多态访问 ===");
    List<Object> objects = new List<Object> {
        beam, plate, bolts, weld, assembly
    };
    foreach (Object obj in objects)
    {
        Console.WriteLine($"{obj.GetType().Name,-20} " +
            $"Identifier.ID={obj.Identifier.ID}, " +
            $"Valid={obj.Identifier.IsValid()}");
    }
    // 输出: 所有对象的 ID 都是 0 (尚未插入数据库)
}

示例 3:Object 作为容器中的多态元素

using System.Collections;
using Tekla.Structures.Model;

public void UseObjectPolymorphism()
{
    // ===== Object 是实现多态容器的基础 =====
    // 通过 Identifier 统一管理不同类型的对象

    Model model = new Model();
    ArrayList allObjects = new ArrayList();

    // 获取所有模型对象(利用 Object 基类统一存储)
    ModelObjectEnumerator enumerator =
        model.GetModelObjectSelector().GetAllObjects();

    while (enumerator.MoveNext())
    {
        // Current 返回 ModelObject(继承自 Object)
        ModelObject current = enumerator.Current;

        // 通过 Object 基类访问 Identifier
        if (current.Identifier.IsValid())
        {
            allObjects.Add(current);
        }
    }

    // ===== 利用 IComparable 按 ID 排序 =====
    // ModelObject 实现的 IComparable 基于 Identifier.ID
    ArrayList sorted = new ArrayList(allObjects);
    sorted.Sort();

    Console.WriteLine($"=== 共找到 {sorted.Count} 个对象,按 ID 排序 ===");
    Console.WriteLine($"{"Type",-25} {"ID",-12} {"Valid"}");
    Console.WriteLine(new string('-', 45));

    foreach (ModelObject obj in sorted)
    {
        Console.WriteLine($"{obj.GetType().Name,-25} " +
            $"{obj.Identifier.ID,-12} {obj.Identifier.IsValid()}");
    }

    // 示例输出:
    // Type                     ID           Valid
    // ---------------------------------------------
    // Beam                     1001         True
    // ContourPlate             1002         True
    // Weld                     1003         True
    // ...
}

示例 4:演示 Object 的反编译完整源码

using System;
using System.Runtime.InteropServices;
using Tekla.Structures.Internal;
using Tekla.Structures.ModelInternal;

namespace Tekla.Structures.Model
{
    // COM 接口:自动生成双重接口(vtable + IDispatch)
    [ClassInterface(ClassInterfaceType.AutoDual)]
    // COM 全局唯一标识符
    [Guid("4668C67A-0E90-4C49-89B6-DA07FFF9A6C0")]
    // 支持 .NET 二进制序列化
    [Serializable]
    public abstract class Object
    {
        // Object 定义的唯一公共属性:模型对象标识符
        public Identifier Identifier { get; set; }

        // protected 构造函数 — 外部不能直接实例化 Object
        // 初始化为无效 ID (0) 的默认 Identifier
        protected Object()
        {
            this.Identifier = new Identifier();
        }

        // 托管 .NET → 非托管 C++ 序列化
        // 将 Identifier 转换为 COM 互操作结构体
        internal void ToStruct(ref dotObject_t dotObject)
        {
            dotObject.Identifier = new dotIdentifier_t(this.Identifier);
        }

        // 非托管 C++ → 托管 .NET 反序列化
        // 从 COM 互操作结构体恢复 Identifier
        internal void FromStruct(ref dotObject_t dotObject)
        {
            this.Identifier = dotObject.Identifier.FromStruct();
        }
    }
}

七、常见问题与注意事项

  • Object 不能直接实例化:Object 声明为 public abstract class Object,构造函数为 protected,这意味着永远不能 new Object()。所有使用必须通过具体子类(BeamContourPlate 等)进行。
  • 所有 Tekla 对象都有 Identifier:由于 Object 是所有模型对象的绝对根基类,Identifier 属性在每一个 Tekla 对象上都可用。这是 Tekla API 最基本的类型约束——没有 Identifier 的对象不是 Tekla 对象。
  • Identifier.ID=0 即无效:Identifier.IsValid() 通过 ID > 0L 判断。ID=0 表示对象未与模型数据库关联。许多子类方法(Select()Modify()Delete())都要求 IsValid()==true
  • ToStruct 和 FromStruct 是 internal 的:ToStruct(ref dotObject_t)FromStruct(ref dotObject_t) 声明为 internal,普通二次开发代码无法直接调用。它们通过 DelegateProxy 在程序集内部被调用,用于 .NET↔C++ 数据交换。
  • 三个特性都服务于 COM 互操作:[ClassInterface(AutoDual)] 使类对 COM 可见;[Guid] 提供 COM 身份注册;[Serializable] 支持跨应用程序域传输对象状态。这三个特性的组合确保 Object 能作为托管/非托管代码之间的桥梁。
  • Object 只应承担"最小公共接口":Object 仅定义 1 个属性和 2 个内部方法——这是经过精心设计的。向此类添加任何新功能前应慎重考虑:它会影响整个 Tekla API 的上百个派生类
  • 序列化是单向映射的:ToStruct() 将 .NET 对象写入 COM 结构体,FromStruct() 将 COM 结构体读回 .NET 对象。每次调用都会创建新的 dotIdentifier_t 实例,不存在引用共享。
  • dotObject_t 仅一个字段:dotObject_t 结构体只有 public dotIdentifier_t Identifier 一个字段。这是因为在 C++ 内核层面,Object 的"本质"仅是一个标识符。所有子类的额外数据由各自的 dotXxx_t 承载(如 dotModelObject_t 有 80+ 字段)。
[提示] 架构启示:理解 Object 的设计是理解整个 Tekla API 架构的关键。它展示了 Tekla 对"模型对象"本质的定义——一个拥有唯一标识符、能跨越托管/非托管边界序列化的实体。在此基础上的 ModelObject 添加了 CRUD、属性系统、枚举分类等能力,而具体子类则实现最终的领域模型行为。

八、总结

  • Object 是 Tekla Open API 中所有模型对象的绝对根抽象基类,直接继承自 System.Object
  • 仅定义 1 个公开属性:IdentifierID 唯一标识 + IsValid() 状态判断),是所有 Tekla 对象的身份基础。
  • protected 构造函数初始化 Identifier = new Identifier()(ID=0 无效),通过子类构造链自动执行。
  • ToStruct(ref dotObject_t)FromStruct(ref dotObject_t) 构成 .NET↔C++ 序列化桥梁,对二次开发代码不可见。
  • 标记 [ClassInterface(AutoDual)][Guid][Serializable] 三个特性,确保 COM 互操作和跨进程序列化能力。
  • 对应的互操作结构体 dotObject_t 仅含一个 dotIdentifier_t 字段,体现"Object = 标识符"的设计哲学。
  • 不定义任何枚举类型或公共方法——遵循"最小公共接口"原则,将扩展职责完全交给派生类。
  • 作为整个 Tekla API 类型体系的地基,Object 的每个设计决策都影响着上方 61 种派生类型的行为。

[图 5] Object 派生类分布统计:按类别汇总的 61 种模型对象类型

尽管只有 37 行代码,Object 却是整个 Tekla Open API 体系中最被低估的类。它定义的不是"能做什么",而是"是什么"——一个跨越 .NET 和 C++ 边界的、拥有唯一身份的可序列化实体。理解 Object,就理解了 Tekla API 设计的根基。

 

本文适用于 Tekla Structures 2017 及以上版本。

— 关注 Tekla 开发 · 扫码交流 —

Tekla开发公众号

Tekla 结构开发

公众号

个人微信

微信交流

扫码加好友

评论 0

sitemap