Software development on the platform Delphi often confronts engineers with cryptic compiler messages that can seem intimidating to newbies and even distract experienced developers. One of the most common and often misunderstood problems is the error reporting unsatisfied forward or external declaration. This failure occurs during the linking phase when the compiler detects the declaration of a procedure or function, but cannot find its actual implementation in the compiled code. Understanding how the compiler and linker work is critical to quickly troubleshooting such failures.

The essence of the problem lies in the desynchronization between the interface part of the module and its implementation. When you declare a method in a section interface, you are promising the compiler that the code for that method will be provided. If in section implementation this code does not exist, or it is written with syntax errors, the linker issues an alarm. This is a fundamental principle of how the language works, requiring strict correspondence between declaration and definition. In this article we will analyze in detail all the possible causes of this error and how to eliminate it.

⚠️ Warning: Ignoring this error in the early stages of development may result in the project becoming unbuildable, blocking the entire team from working on new features.

Compiler and linker basics

To effectively deal with compilation errors, you need to understand what happens β€œunder the hood” when you press the build button for a project. Compiler Delphi works in two main steps: first it processes the source code, checking syntax and data types, and then the linker assembles all the object files into a single executable file. It is at the second stage, when external links are checked, that the message about unsatisfied forward declaration pops up. This means that there is an entry for the function in the symbol table, but the address of its code is zero.

Developers often forget that code order matters, especially when using directives forward. If you declare a procedure with this keyword, you are explicitly telling the compiler to defer implementation checking until the end of the module. However, if the implementation is still not found by the time the module is compiled, a fatal error will occur. Strong typing The language requires that the declaration and implementation signatures match on a byte-by-byte basis, including default parameters and return types.

πŸ“Š How often do you encounter linking errors?
Daily
Once a week
Rarely
Only when working with someone else's code

It is important to note that this problem is not limited to standard procedures. It fully concerns class methods, especially virtual and dynamic ones. When a class declares a virtual method but does not provide its implementation (or its parent's implementation), this is also classified as an unsatisfied declaration. Object-oriented programming in Delphi imposes additional requirements on inheritance, ignoring which leads to difficult debugging situations.

Typical causes of the error

There are several classic scenarios that are guaranteed to cause this error to appear in the compiler log. Most often the problem is trivial: the developer declared a method in the header part, but forgot to write the function body in the implementation section. This can happen when refactoring code, when the method signature has been changed and the old name has been removed, or when copying code from other projects. In such cases, the compiler sees a "hole" in the program logic.

Another common cause is mismatched parameter lists. Even if the parameter names are different, their types and order must strictly correspond to the declaration. If in the interface you specified the parameter as const String, but in the implementation they simply wrote String, the compiler will consider these two different functions. The implementation will be considered a new, local procedure, and the one declared in the interface will remain without a body. Parsing does not forgive such inconsistencies.

  • πŸ”΄ Forgotten implementation: the method is declared in the interface, but is missing in the implementation.
  • πŸ”΄ Signature mismatch: The parameter types or return value differ in the declaration and body.
  • πŸ”΄ Directive errors: incorrect use of the virtual, override or forward keywords.
  • πŸ”΄ Problems with conditional compilation: implementation hidden by directives {$IFDEF}, which are not fulfilled.

Particular attention should be paid to conditional compilation directives. It often happens that implementation code is wrapped in a macro test that is not defined in the current build configuration. As a result, from the compiler's point of view, the function was declared, but its code was excluded from the assembly, which leads to a linking error. This is especially true when working with cross-platform projects FireMonkey, where the code for Windows and macOS may differ.

⚠️ Warning: When using conditional compilation, always ensure that all code branches contain the required method implementations, otherwise the platform-specific build will fail.

Problems with class methods and inheritance

Working with classes makes its own adjustments to the compilation process. When you declare a virtual method in a base class, you are required to provide an implementation, even if it is just a stub. If the method is marked as abstract, implementation is not required only if the class itself is abstract. Otherwise, an attempt to create an instance of a class or call a method will lead to that very unpleasant error. Polymorphism requires a disciplined approach to writing code.

Another nuance is related to method overriding. If you use the directive override, you must be sure that in the parent class this method actually exists and is marked as virtual or dynamic. The error may occur if you changed the parent of a class but forgot to update the methods of the child class. The compiler will look for an implementation in the new hierarchy and, if it does not find it, will issue an error message. Inheritance must be consistent across all links in the chain.

Consider a table showing common combinations of directives and their impact on implementation requirements:

Method directive Requires class implementation Can be abstract Risk of error
Standard Yes, definitely No High
Virtual Yes, if the class is not abstract Yes (with abstract) Medium
Override Yes, replaces the parent No Medium
External No (in DLL) No Low (if DLL is present)

Syntax TMyClass.MyMethod required. If you forget the class prefix, the compiler will assume that you have declared a new global procedure with the same name, leaving the class method without a body. This is a classic beginner mistake that can be easily corrected with attention to detail.

Errors when working with external libraries

Using third party libraries and DLLs is another area where unsatisfied forward declarations often come up. When you declare a function from an external library using a directive external, you must specify the export name and library exactly. If the name of the function in the DLL is different from the name in your code (for example, due to name decoration in C++), the linker will not be able to find the symbol. Dynamic linking requires precision in names.

Often the problem lies in the path to the library file or in the fact that the DLL itself does not contain the necessary export function. Unlike static libraries, errors with external dependencies can appear not only during compilation, but also when running the program. However, if the linker does not find the import library (.lib or .a) during the build phase, it will immediately throw a missing symbol error. Static linking more demanding on the availability of files at the time of compilation.

  • πŸ”΅ Check the exact name of the exported function (case is important).
  • πŸ”΅ Make sure the path to the DLL is correct or the file is in PATH.
  • πŸ”΅ For C++ libraries use the directive name to specify the exact name of the export.

Also worth mentioning is the issue of architectural compatibility. If you are developing a 64-bit application, all included static libraries must also be 64-bit. Trying to link a 32-bit object file into a 64-bit project will generate an error that can be mistaken for a missing declaration, when in fact the file formats are incompatible. Processor architecture dictates its own rules of binary compatibility.

Impact of conditional compilation and macros

Complex projects are often full of directives {$IFDEF}, {$IFNDEF} and other preprocessor designs. This is a powerful tool for code management, but it is also a common source of unsatisfied forward errors. If you declare a method in an interface without conditions, but wrap its implementation in a macro test that is not defined, the method will remain without a body. The compiler will simply β€œcut out” a piece of code, considering it non-existent.

You need to be especially careful when working with platform-dependent code. For example, a method may be declared for all platforms, but the implementation is written only for Windows. When compiling under Linux or macOS you will receive a linking error. The solution is to create dummy implementations for unsupported platforms or use compilation conditions in the interface declaration itself. Cross-platform requires careful planning of the code structure.

⚠️ Attention: Always check the active macros in the project options (Project Options β†’ Delphi Compiler β†’ Conditional defines) to ensure that the required code ends up in the build.

β˜‘οΈ Conditional compilation check

Done: 0 / 4

Another pitfall is redefining macros in different modules. If a symbol is defined in one unit DEBUG_MODE, but not in another, then the implementation of a function that depends on this symbol can be assembled in one place and skipped in another, which will lead to desynchronization. Global Consistency compilation settings are critical for large projects.

Diagnostic and debugging methods

When you encounter an error, the first step should be to carefully examine the compiler message. Typically the IDE will highlight a line that declares a method that has no implementation. However, if there are many modules, the search may take a long time. Use a project-wide search (Ctrl+Shift+F) to find all mentions of the problematic function name. This will help you understand where it is declared and where it is (not) implemented. Search tools significantly speed up diagnosis.

A useful technique is to gradually eliminate parts of the code. If the error appears after adding a new module, try temporarily commenting out its connection in uses. If the error disappears, the problem is definitely in this module. You can also temporarily remove the body of the suspicious function and see if the error message changes. Elimination method allows you to localize the source of the problem.

Don't forget about compilation logs. Enable Verbose linking in the settings to see exactly which object files are being processed and where exactly the linker is losing symbols. This may provide a clue if the problem is with the order of modules in the project or with missing files. Log details often contains a clue.

Practical recommendations for prevention

To minimize the risk of such errors occurring in the future, you should follow certain coding rules. First, always write the implementation of a method immediately after it is declared, especially in interfaces. Don’t put off filling out function bodies until later. Secondly, use the IDE's capabilities to auto-generate code: right-clicking on the class name and selecting "Implement Interface" will create stubs for all methods automatically. Automation reduces the risk of human error.

Regularly refactor and remove unused code. If a method is no longer needed, remove it from both the interface and the implementation. Leaving β€œdangling” ads is a direct path to linking errors. It is also useful to use static code analyzers, which can warn about methods without implementation even before the compilation stage. Proactive approach saves time.

  • 🟒 Use auto-generation of method implementations through the IDE context menu.
  • 🟒 Immediately remove declarations of methods that are no longer used.
  • 🟒 Check signatures for consistency when changing parameter types.
  • 🟒 Test the project build on different configurations (Debug/Release, 32/64 bit).

Following these simple rules will help you keep your code clean and avoid frustrating compilation errors. Remember that the Delphi compiler is a strict but fair tool that will point out logical inconsistencies in your code. Code discipline - the key to successful development.

What to do if there is an implementation, but the error remains?

Check to see if the implementation is accidentally commented out or hidden by conditional compilation directives. Also make sure that the implementation file is added to the project and compiled.

Could this error be caused by a virus on the system?

Extremely rare. This is usually a bug in the code. However, if the antivirus blocks the compiler's access to temporary files or DLLs, this could theoretically lead to linking problems.

How to quickly find all methods without implementation in the project?

Use the built-in code inspector or third-party static analysis plugins that can scan the project for declared but not implemented methods.