Dependency Injection in TwinCAT: Decoupled PLC Design

Stefan Weidner2 min read
BeckhoffOther TopicTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

What Dependency Injection Solves in TwinCAT 3

Dependency injection (DI) is a design pattern where a function block receives its dependencies from the caller instead of creating them internally.FB_init, DI lets you decouple application logic from concrete hardware-facing blocks. Instead of a control block instantiating a specific drive or I/O abstraction internally, it depends on an INTERFACE, and the concrete implementation is supplied at instantiation time. The result is function blocks that can be unit-tested with simulated implementations and reused across machines without rewriting the control logic.

Interface-Based Design as the Foundation

DI in TwinCAT 3 only works if the consumer depends on an abstraction. Define an interface that captures exactly the operations the consumer needs — no more — and have each concrete function block implement it. The consumer then holds a reference to the interface type, never to a concrete block.


Any function block implementing I_AxisDrive — a real axis wrapper, a simulated axis, or a test stub — becomes interchangeable from the consumer's perspective. Keep the interface minimal: every member you add is a member every implementation must honor, including test doubles.

Injection Mechanisms Available in TwinCAT 3

The two practical injection points are constructor injection and property/method injection:

  1. Constructor injection via FB_init: Override FB_init to accept the dependency as an interface-typed input and store it in a local reference. The dependency is fixed at instantiation, which suits dependencies that never change during runtime, such as the axis hardware abstraction assigned to a station controller.
  2. Property or method injection: Expose a setter property or an Init-style method that accepts the interface. This allows later rebinding, which suits configuration-driven designs, but requires the consumer to tolerate an unassigned state until injection occurs — guard against invalid references before first use.

Testability and Design Constraints

The primary payoff is test isolation: inject a simulated implementation to exercise control logic without physical axes or fieldbus hardware, then inject the production implementation for deployment. This requires discipline at the composition point — typically the top-level program or a dedicated composition block — where all concrete instances are declared and wired. Two constraints apply: references must remain valid for the lifetime of the consumer, so the injected instance must outlive or match the consumer's scope; and every interface member called must be implemented meaningfully in the simulation, or tests silently pass on empty stubs. Verify rebinding behavior explicitly if you use property injection, since TwinCAT will not enforce that a reference was assigned before the consumer's cyclic code runs.

FAQ

How do you implement dependency injection in TwinCAT 3?

Define an INTERFACE for the dependency, hold a REFERENCE TO that interface in the consumer, and pass the concrete implementation through an overridden FB_init or a setter property at the composition point.

Why use dependency injection in PLC code?

It decouples control logic from hardware-specific function blocks, so the same logic runs against simulated implementations during testing and production implementations on the machine without code changes.

Constructor injection vs. property injection in TwinCAT — which should I use?

Use FB_init constructor injection when the dependency is fixed for the block's lifetime; use property injection when you need to rebind implementations after instantiation, and guard against unassigned references in cyclic code.

Back to blog