Inside OCCT TKernel: Plugin Loading, Message Dispatch, and Runtime Type Registration

TKernel forms the lowest layer of the OpenCASCADE Technology (OCCT) stack, providing portable abstractions for primitive data types, container templates, exception hierarchies, file systems, encoding, and OS-level services. Most of its directories wrap platform differences in ways familiar to anyone who has read a large cross-platform C++ codebase; however, a few subsystems introduce distinctive architectural patterns worth examining in detail.

Module Overview

Directory Purpose
FlexLexer Interface layer for flex-generated tokenizers
FSD Low-level file-system data serialization
Message Diagnostic routing and alert dispatch
NCollection Generic template containers: arrays, lists, graphs
OSD Operating-system portability: paths, timers, threads
Plugin Dynamic library discovery and factory binding
Quantity Physical and geometric units: color, angle, distance
Resource Application assets and localized string tables
Standard Fundamental typedefs, allocators, and atomic reference counting
StdFail Common exception types
Storage Object persistence and schema migration
TCollection / TColStd Concrete container specializations
TShort Compact integer sequences
Units / UnitsAPI / UnitsMethods Dimensional conversion and unit arithmetic

Rather than analyzing every wrapper in depth, the following sections focus on three mechanisms that shape how the rest of OCCT interacts with TKernel: the plugin resolver, the message bus, and the custom RTTI registry.

Plugin Resolver

OCCT defers plugin instantiation to runtime. A plugin shared library must export a single C-linkage factory function. The host framework loads the module dynamically, retrieves the factory address, and requests a service instance by GUID.

Core loader interface:

class Plugin_Entry
{
public:
  Standard_EXPORT static Handle(Standard_Transient) Resolve(
    const Standard_GUID& theUUID,
    const Standard_Boolean theWarnIfMissing = Standard_True);
};

Plugin authors satisfy this contract through a macro that generates the exported factory:

#define OCCT_PLUGIN_FACTORY(Impl) \
extern "C" Standard_EXPORT Standard_Transient* PluginFactory( \
  const Standard_GUID& theKey) { \
  return const_cast<Standard_Transient*>(Impl::GetFactory(theKey).get()); \
}

Each plugin module implements a static GetFactory method that maps GUIDs to concrete services. In interactive test harnesses such as Draw, plugins additionally register interpreter commands, making new algorithms available from the scripting console without recompiling the host.

Message Bus

The diagnostic layer decouples message producers from consumers. Code that detects an exceptional condition emits an alert tagged with a priority; independent drivers decide whether to print to a terminal, append to a log, or write to the system event journal.

Alert API:

class Msg_Alert
{
  static Msg_Messenger::Buffer Emit(Msg_Priority thePriority)
  {
    return DefaultBus()->Emit(thePriority);
  }

  static void Emit(const TCollection_AsciiString& theText, Msg_Priority thePriority)
  {
    DefaultBus()->Emit(theText, thePriority);
  }
};

The central messenger multiplexes output to any number of attached drivers:

class Msg_Messenger
{
  Standard_EXPORT Standard_Boolean AddDriver(const Handle(Msg_Driver)& theDriver);
  Standard_EXPORT Standard_Boolean RemoveDriver(const Handle(Msg_Driver)& theDriver);
  
  Standard_EXPORT void Emit(const Standard_CString theText,
                            const Msg_Priority thePriority = Msg_Warning) const;
};

Concrete drivers inherit from a common base and filter by severity:

class Msg_Driver
{
  Msg_Priority FilterLevel() const { return myLevel; }
  Standard_EXPORT virtual void Emit(const Standard_CString theText,
                                    const Msg_Priority thePriority) const;
};

class Msg_DriverConsole : public Msg_Driver;
class Msg_DriverFile : public Msg_Driver;
class Msg_DriverSyslog : public Msg_Driver;
class Msg_DriverReport : public Msg_Driver;

Because the bus supports concurrent drivers, a single call site can stream diagnostics to a regression report, a developer console, and a production log simultaneously without explicit branching.

Run-Time Type Registry

OCCT implements its own reflection layer rather than relying exclusively on compiler RTTI. Every reflectable type derives from Standard_Transient, which carries an atomic reference counter. Type identity is represented by Standard_Type descriptors maintained in a global, lazily initialized, thread-safe map.

Root class and handle template:

template <class T> class Handle;

class Standard_Transient
{
  Standard_EXPORT Standard_Boolean TypeMatch(
    const opencascade::Handle<Standard_Type>& theKind) const;
  Standard_EXPORT Standard_Boolean TypeDerived(
    const opencascade::Handle<Standard_Type>& theKind) const;

private:
  std::atomic_int myCounter;
};

Subclasses declare reflective metadata through header macros:

class Adaptor2d_Curve2d : public Standard_Transient
{
  OCCT_RTTI_DECL(Adaptor2d_Curve2d, Standard_Transient)
};

Macros split declaration and implementation:

#define OCCT_RTTI_DECL(Class, Base) \
public: \
  using Super = Base; \
  static const char* StaticName() { return #Class; } \
  Standard_EXPORT static const Handle(Standard_Type)& StaticType(); \
  Standard_EXPORT virtual const Handle(Standard_Type)& RuntimeType() const Standard_OVERRIDE;

#define OCCT_RTTI_IMPL(Class, Base) \
  const Handle(Standard_Type)& Class::StaticType() { \
    return Standard_Type::Fetch<Class>(); \
  } \
  const Handle(Standard_Type)& Class::RuntimeType() const { \
    return Class::StaticType(); \
  }

The regisrty uses recursive template instantiation to capture inheritance chains:

class Standard_Type : public Standard_Transient
{
public:
  template <class T>
  static const Handle(Standard_Type)& Fetch()
  {
    return opencascade::type_slot<T>::Value();
  }
};

template <class T>
class type_slot
{
public:
  static const Handle(Standard_Type)& Value()
  {
    static Handle(Standard_Type) aSlot =
      Standard_Type::Register(typeid(T), T::StaticName(), sizeof(T),
                              type_slot<typename T::Super>::Value());
    return aSlot;
  }
};

Registration deduplicates under a static mutex:

Standard_Type* Standard_Type::Register(const std::type_info& theInfo,
                                       const char* theLabel,
                                       Standard_Size theBytes,
                                       const Handle(Standard_Type)& theBase)
{
  static Standard_Mutex theGate;
  Standard_Mutex::Sentry aLock(theGate);

  registry_type& aCatalog = Registry();
  auto aHit = aCatalog.find(theInfo);
  if (aHit != aCatalog.end())
    return aHit->second;

  Standard_Type* aResult = new Standard_Type(theInfo, theLabel, theBytes, theBase);
  aCatalog.emplace(theInfo, aResult);
  return aResult;
}

Each template specialization of type_slot stores exactly one static handle. The first request for a given type triggers insertion into the catalog; later requests return the existing descriptor. This mechanism gives OCCT deterministic type queries, sized allocation metadata, and explicit parent links without depending solely on compiler-generated type_info.

Tags: OpenCASCADE OCCT TKernel C++ rtti

Posted on Sat, 22 Aug 2026 16:12:35 +0000 by Talon