Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Full-featured interfaces for C99

License

NotificationsYou must be signed in to change notification settings

hirrolot/interface99

Repository files navigation

Full-featured interfaces inspired by Rust and Golang. Multiple inheritance, superinterfaces, and default implementations supported. No external tools required, pure C99.

Shape
#include<interface99.h>#include<stdio.h>#defineShape_IFACE                      \    vfunc( int, perim, const VSelf)      \    vfunc(void, scale, VSelf, int factor)interface(Shape);
Rectangle
Triangle
typedefstruct {inta,b;}Rectangle;intRectangle_perim(constVSelf) {VSELF(constRectangle);return (self->a+self->b)*2;}voidRectangle_scale(VSelf,intfactor) {VSELF(Rectangle);self->a *=factor;self->b *=factor;}impl(Shape,Rectangle);
typedefstruct {inta,b,c;}Triangle;intTriangle_perim(constVSelf) {VSELF(constTriangle);returnself->a+self->b+self->c;}voidTriangle_scale(VSelf,intfactor) {VSELF(Triangle);self->a *=factor;self->b *=factor;self->c *=factor;}impl(Shape,Triangle);
Test
voidtest(Shapeshape) {printf("perim = %d\n",VCALL(shape,perim));VCALL(shape,scale,5);printf("perim = %d\n",VCALL(shape,perim));}intmain(void) {Shaper=DYN_LIT(Rectangle,Shape, {5,7});Shapet=DYN_LIT(Triangle,Shape, {10,20,30});test(r);test(t);}

(Based onexamples/shape.c.)

Output
perim = 24perim = 120perim = 60perim = 300

Highlights

  • Minimum boilerplate. Forget about maintaining virtual tables -- just writeimpl(Shape, Rectangle) and Interface99 will do it for you!

  • Portable. Everything you need is a standard-conforming C99 compiler; neither the standard library, nor compiler/platform-specific functionality or VLA are required.

  • Predictable. Interface99 comes with formalcode generation semantics, meaning that the generated data layout is guaranteed to always be the same.

  • Comprehensible errors. Interface99 isresilient to bad code.

  • Battle-tested. Interface99 is used atOpenIPC to develop real-time streaming software for IP cameras; this includes anRTSP 1.0 implementation along with ~50k lines of private code.

Features

FeatureStatusDescription
Multiple interface inheritanceA type can inherit multiple interfaces at the same time.
SuperinterfacesOne interface can require a set of other interfaces to be implemented as well.
Marker interfacesAn interface with no functions.
Single/Dynamic dispatchDetermine a function to be called at runtime based onself.
Multiple dispatchDetermine a function to be called at runtime based on multiple arguments. Likely to never going to be implemented.
Dynamic objects of multiple interfacesGiven interfacesFoo andBar, you can construct an object of both interfaces,FooBar obj.
Default implementationsSome interface functions may be given default implementations. A default function can call other functions and vice versa.
Data and implementation separationNew interfaces can be implemented for existing types.

Installation

Interface99 consists of one header fileinterface99.h and one dependencyMetalang99. To use it in your project, you need to:

  1. Addinterface99 andmetalang99/include to your include directories.
  2. Specify-ftrack-macro-expansion=0 (GCC) or-fmacro-backtrace-limit=1 (Clang) to avoid useless macro expansion errors.

If you use CMake, the recommended way isFetchContent:

include(FetchContent)FetchContent_Declare(    interface99    URL https://github.com/hirrolot/interface99/archive/refs/tags/vx.y.z.tar.gz# vx.y.z)FetchContent_MakeAvailable(interface99)target_link_libraries(MyProject interface99)# Disable full macro expansion backtraces for Metalang99.if(CMAKE_C_COMPILER_IDSTREQUAL"Clang")  target_compile_options(MyProjectPRIVATE -fmacro-backtrace-limit=1)elseif(CMAKE_C_COMPILER_IDSTREQUAL"GNU")  target_compile_options(MyProjectPRIVATE -ftrack-macro-expansion=0)endif()

(By default,interface99/CMakeLists.txt downloads Metalang99v1.13.5 from the GitHub releases; if you want to override this behaviour, you can do so by invokingFetchContent_Declare earlier.)

Optionally, you canprecompile headers in your project that rely on Interface99. This will decrease compilation time, because the headers will not be compiled each time they are included.

Happy hacking!

Tutorial

This section is based on a collection of well-documentedexamples, each of which demonstrates one specific aspect of Interface99.

Basic usage

  1. Interface definition.

Syntax:interface(Shape);

An interface definition expands to a virtual table structure and a so-calledinterface object type. In the case ofexamples/shape.c:

// interface(Shape);typedefstructShapeVTableShapeVTable;typedefstructShapeShape;structShapeVTable {int (*perim)(constVSelf);void (*scale)(VSelf,intfactor);};structShape {void*self;constShapeVTable*vptr;};

Here,Shape.self is the pointer to an object whose type implementsShape, andShape.vptr points to a corresponding virtual table instance. InsideShapeVTable, you can observe the mysteriousVSelf bits -- they expand to parameters of typevoid * restrict (with extraconst forperim); when calling these methods, Interface99 will substituteShape.self for these parameters.

Usually, interface definitions go in*.h files.

  1. Implementation definition.
LinkageSyntax
Internalimpl(Shape, Rectangle);
ExternalimplExtern(Shape, Rectangle);

An implementation definition expands to nothing but a virtual table instance of a particular implementer. In the case ofexamples/shape.c:

// impl(Shape, Rectangle);staticconstShapeVTableRectangle_Shape_impl= {    .perim=Rectangle_perim,    .scale=Rectangle_scale,};

(If you were usingimplExtern, this definition would beextern likewise.)

Note that inside function implementations, we useVSELF, which simply casts the parameter introduced byVSelf to a user-defined type (const Rectangle orRectangle in our case):

intRectangle_perim(constVSelf) {VSELF(constRectangle);return (self->a+self->b)*2;}voidRectangle_scale(VSelf,intfactor) {VSELF(Rectangle);self->a *=factor;self->b *=factor;}
  1. Dynamic dispatch.

Once an interface and its implementations are both generated, it is time to instantiate an interface object and invoke some functions upon it.

First of all, to instantiateShape, use theDYN_LIT macro:

Shape r = DYN_LIT(Rectangle, Shape, {5, 7});test(r);

Here,DYN_LIT(Rectangle, Shape, {5, 7}) createsShape by assigningShape.self to&(Rectangle){5, 7} andShape.vptr to the aforementioned&Rectangle_Shape_impl. Eventually, you can acceptShape as a function parameter and perform dynamic dispatch through theVCALL macro:

voidtest(Shapeshape) {printf("perim = %d\n",VCALL(shape,perim));VCALL(shape,scale,5);printf("perim = %d\n",VCALL(shape,perim));}

Finally, just a few brief notes:

  • BesidesVCALL, you also haveVCALL_OBJ,VCALL_SUPER, andVCALL_SUPER_OBJ. They all serve a different purpose; for more information, please refer totheir documentation.
  • In practice,DYN is used more often thanDYN_LIT; it just accepts an ordinary pointer instead of an initialiser list, which means that you canmalloc it beforehand.
  • If your virtual function does not acceptself, you can invoke it asobj.vptr->foo(...).
  • If you want to call an interface function on some concrete type, just writeVTABLE(T, Iface).foo(...).

Congratulations, this is all you need to know to write most of the stuff!

Superinterfaces

Interface99 has the feature called superinterfaces, or interface requirements.examples/airplane.c demonstrates how to extend interfaces with new functionality:

#defineVehicle_IFACE                              \    vfunc(void, move_forward, VSelf, int distance) \    vfunc(void,    move_back, VSelf, int distance)interface(Vehicle);#defineAirplane_IFACE                          \    vfunc(void,   move_up, VSelf, int distance) \    vfunc(void, move_down, VSelf, int distance)#defineAirplane_EXTENDS (Vehicle)interface(Airplane);

(Note that#define Airplane_EXTENDS must appear prior tointerface(Airplane);.)

Here,Airplane extendsVehicle with the new functionsmove_up andmove_down. Everywhere you haveAirplane, you can also operateVehicle:

Airplanemy_airplane=DYN_LIT(MyAirplane,Airplane, {.x=0, .y=0});VCALL_SUPER(my_airplane,Vehicle,move_forward,10);VCALL_SUPER(my_airplane,Vehicle,move_back,3);

Internally, Interface99 embeds superinterfaces' virtual tables into those of subinterfaces, thereby forming avirtual table hierarchy. For example, you can specifyRepairable andArmoured along withVehicle, and they all will be included intoAirplaneVTable like so:

// #define Airplane_EXTENDS (Vehicle, Repairable, Armoured)typedefstructAirplaneVTable {void (*move_up)(VSelf,intdistance);void (*move_down)(VSelf,intdistance);constVehicleVTable*Vehicle;constRepairableVTable*Repairable;constArmouredVTable*Armoured;}AirplaneVTable;

Default implementations

Sometimes we wish to define default behaviour for several implementers; this is supported bydefault implementations.

Take a look atexamples/default_impl.c. In this example, we define the interfaceDroid:

#defineDroid_IFACE                         \    vfunc(const char *, name, void)         \    vfuncDefault(void, turn_on, Droid droid)interface(Droid);

The macrovfuncDefault tells Interface99 to use the default implementation forturn_on automatically. But where is it located? Here:

voidDroid_turn_on(Droiddroid) {printf("Turning on %s...\n",droid.vptr->name());}

As you can see, default implementations follow a strict naming convention,<iface>_<default-func-name> , which provides Interface99 with sufficient information to generate a virtual table. Additionally, as a developer, you can also rely on this convention and call a default function of a third-party interface. ForC_3PO, we use the default implementation ofturn_on, and the resulting virtual table would look like this:

staticconstDroidVTableC_3PO_Droid_impl= {    .name=C_3PO_name,    .turn_on=Droid_turn_on,};

But forR2_D2, we use a custom implementationR2_D2_turn_on:

voidR2_D2_turn_on(Droiddroid) {Droid_turn_on(droid);puts("Waaaaoow!");}#defineR2_D2_turn_on_CUSTOM ()impl(Droid,R2_D2);

(R2_D2_turn_on_CUSTOM tells Interface99 to use the custom implementation instead of the default one; this is because it is impossible to detect at compile-time whether a specific function is defined or not.)

And the virtual table would be:

staticconstDroidVTableR2_D2_Droid_impl= {    .name=R2_D2_name,    .turn_on=R2_D2_turn_on,};

Please, note that you have to specify() for the*_CUSTOM attribute; do not leave it empty.

Syntax and semantics

Having a well-defined semantics of the macros, you can write an FFI which is quite common in C.

EBNF syntax

<iface-def>::="interface(" <iface>")" ;<iface>::= <ident> ;<func>::= <regular-func>| <default-func> ;<regular-func>::="vfunc("        <func-ret-ty>"," <func-name>"," <func-params>")" ;<default-func>::="vfuncDefault(" <func-ret-ty>"," <func-name>"," <func-params>")" ;<func-ret-ty>::= <type> ;<func-name>::= <ident> ;<func-params>::= <parameter-type-list> ;<impl>::="impl("           <iface>"," <implementer>")" ;<implExtern>::="implExtern("     <iface>"," <implementer>")" ;<declImpl>::="declImpl("       <iface>"," <implementer>")" ;<declImplExtern>::="declImplExtern(" <iface>"," <implementer>")" ;<implementer>::= <ident> ;<dyn>::="DYN("     <implementer>"," <iface>"," <ptr>")" ;<dyn-lit>::="DYN_LIT(" <implementer>"," <iface>",""{" <initializer-list>"}"")" ;<vtable>::="VTABLE("  <implementer>"," <iface>")" ;<vself-params>::="VSelf" ;<vself-cast>::="VSELF(" <type>")" ;(* <expr> must be an expression of an interface object type.*)<vcall>::="VCALL("           <expr>"," <func-name> <vcall-args>")" ;<vcall-obj>::="VCALL_OBJ("       <expr>"," <func-name> <vcall-args>")" ;<vcall-super>::="VCALL_SUPER("     <expr>"," <iface>"," <func-name> <vcall-args>")" ;<vcall-super-obj>::="VCALL_SUPER_OBJ(" <expr>"," <iface>"," <func-name> <vcall-args>")" ;<vcall-args>::= ["," <argument-expression-list> ] ;<requirement>::= <iface> ;
Note: shortened vs. postfixed versions

Each listed identifier in the above grammar corresponds to a macro name defined by default -- these are calledshortened versions. On the other hand, there are alsopostfixed versions (interface99,impl99,vfunc99, etc.), which are defined unconditionally. If you want to avoid name clashes caused by shortened versions, defineIFACE99_NO_ALIASES before includinginterface99.h. Library headers are strongly advised to use the postfixed macros, but without resorting toIFACE99_NO_ALIASES.

Notes:

  • For every interface<iface>, the macro<iface>_IFACE must expand to{ <func> }*.
  • For any interface, a macro<iface>_EXTENDS can be defined, which must expand to"(" <requirement> { "," <requirement> }* ")".
  • For any interface function implementation, a macro<implementer>_<func-name>_CUSTOM can be defined, which must expand to"()".

Semantics

(It might be helpful to look at thegenerated output ofexamples/shape.c.)

interface

Expands to

typedef struct <iface>VTable <iface>VTable;typedef struct <iface> <iface>;struct <iface>VTable {    // Only if <iface> is a marker interface without superinterfaces:    char dummy;    <func-ret-ty>0 (*<func-name>0)(<func-params>0);    ...    <func-ret-ty>N (*<func-name>N)(<func-params>N);    const <requirement>0VTable *<requirement>;    ...    const <requirement>NVTable *<requirement>;};struct <iface> {    void *self;    const <iface>VTable *vptr;}

(char dummy; is needed for an empty<iface>VTable because a structure must have at least one member, according to C99.)

I.e., this macro defines a virtual table structure for<iface>, as well as the structure<iface> that is polymorphic over<iface> implementers. This is generated in two steps:

  • Function pointers. For each<func-name>I specified in the macro<iface>_IFACE, the corresponding function pointer is generated.
  • Requirements obligation. If the macro<iface>_EXTENDS is defined, then the listed requirements are generated to obligate<iface> implementers to satisfy them.

impl

Expands to

static const <iface>VTable VTABLE(<implementer>, <iface>) = {    // Only if <iface> is a marker interface without superinterfaces:    .dummy = '\0',    <func-name>0 = either <implementer>_<func-name>0 or <iface>_<func-name>0,    ...    <func-name>N = either <implementer>_<func-name>N or <iface>_<func-name>N,    <requirement>0 = &VTABLE(<implementer>, <requirement>0),    ...    <requirement>N = &VTABLE(<implementer>, <requirement>N),}

I.e., this macro defines a virtual table instance of type<iface>VTable for<implementer>. It is generated in two steps:

  • Function implementations. If<func-name>I is defined viavfuncDefault and<implementer>_<func-name>I_CUSTOM isnot defined,<iface>_<func-name>I is generated (default implementation). Otherwise,<implementer>_<func-name>I is generated (custom implementation).
  • Requirements satisfaction. If the macro<iface>_EXTENDS is defined, then the listed requirements are generated to satisfy<iface>.

implExtern

The same asimpl but generates anextern definition instead ofstatic.

declImpl

Expands tostatic const <iface>VTable VTABLE(<implementer>, <iface>), i.e., it declares a virtual table instance of<implementer> of type<iface>VTable.

declImplExtern

The same asdeclImpl but generates anextern declaration instead ofstatic.

DYN

Expands to an expression of type<iface>, with.self initialised to<ptr> and.vptr initialised to&VTABLE(<implementer>, <iface>).

<ptr> is guaranteed to be evaluated only once.

DYN_LIT

DYN_LIT(<implementer>, <iface>, ...) expands toDYN(<implementer>, <iface>, &(<implementer>)...). The... must take the form of an initialiser list incompound literals.

VTABLE

Expands to<implementer>_<iface>_impl, i.e., a virtual table instance of<implementer> of type<iface>VTable.

VSelf/VSELF

VSelf is an object-like macro that expands to a function parameter of typevoid * restrict, with an implementation-defined name. In order to downcast this parameter to an implementer type, there exists a function-like macroVSELF.VSELF(T) which brings a variableself of typeT * restrict into the scope, and initialises it to theVSelf-produced parameter name casted toT * restrict.

VSelf can be used on any position for any virtual function, however, it only makes sense to use it as a first parameter.VSELF(T) can be used everywhere inside a function with theVSelf parameter.

VCALL_*

TheVCALL_* macros are meant tocall avirtual method, which is avfunc/vfuncDefault that accepts eitherVSelf or an interface object (of a containing interface type) as a first parameter.

For methods acceptingVSelf, there existVCALL andVCALL_SUPER:

  • VCALL(obj, func) =>obj.vptr->func(obj.self).
  • VCALL(obj, func, args...) =>obj.vptr->func(obj.self, args...).
  • VCALL_SUPER(obj, superiface, func) =>obj.vptr->superiface->func(obj.self).
  • VCALL_SUPER(obj, superiface, func, args...) =>obj.vptr->superiface->func(obj.self, args...).

For methods accepting an interface object, there areVCALL_OBJ andVCALL_SUPER_OBJ:

  • VCALL_OBJ is the same asVCALL except that it passesobj tofunc instead ofobj.self.
  • VCALL_SUPER_OBJ is the same asVCALL_SUPER except that it passes(superiface){obj.self, obj.vptr->superiface} tofunc instead ofobj.self.

Miscellaneous

  • The macrosIFACE99_MAJOR,IFACE99_MINOR,IFACE99_PATCH,IFACE99_VERSION_COMPATIBLE(x, y, z), andIFACE99_VERSION_EQ(x, y, z) have thesame semantics as of Metalang99.

  • For each macro usingML99_EVAL, Interface99 provides itsMetalang99-compliant counterpart which can be used inside derivers and other Metalang99-compliant macros:

MacroMetalang99-compliant counterpart
interfaceIFACE99_interface
implIFACE99_impl
implExternIFACE99_implExtern

(Anarity specifier anddesugaring macro are provided for each of the above macros.)

Guidelines

  • Writeimpl(...)/implExtern(...) right after all functions are implemented; do not gather all implementation definitions in a single place.
  • If you useClang-Format, it can be helpful to addvfunc andvfuncDefault to theStatementMacros vector (seeour.clang-format). It will instruct the formatter to place them onto different lines.

Pitfalls

  • Both interfaces that you implement for a single type can have a function with the same name, thus resulting in a name collision. However, you can elegantly workaround like this:
// `MyType_Iface1_foo` function definition here...#defineIface1_foo MyType_Iface1_fooimpl(Iface1,MyType);#undef Iface1_foo// `MyType_Iface2_foo` function definition here...#defineIface2_foo MyType_Iface2_fooimpl(Iface2,MyType);#undef Iface2_foo

The same holds for custom implementations:

// Use a custom implementation for `Iface1::bar`.#defineMyType_bar_CUSTOM ()impl(Iface1,MyType);#undef MyType_bar_CUSTOM// Use the default `Iface2::bar`.impl(Iface2,MyType);

Design choices

The design of Interface99 may raise some questions. In this section, you may find answers why it was designed in this way.

VCALL_*

Instead of using theVCALL_* macros, we could instead generate functions that accept an interface object as a first parameter, with the rest of parameters being arguments to a particular method:

voidShape_scale(Shapeshape,intfactor) {shape.vptr->scale(shape.self,factor);}

But this approach does not work for superinterfaces' methods, as well as for methods accepting an interface object instead ofVSelf or a combination thereof. For this reason, I decided to stick to more expressiveVCALL_* macros, although at the cost of some IDE support.

self type safety

Since there can be many specific implementations of a virtual method (likeRectangle_scale orTriangle_scale),selfmust be of typevoid *. But the problem is that in concrete implementations, we still wantself to be of some concrete type; and sincevoid * andT * may be incompatible types, assigning a concrete method acceptingT * to a virtual method fieldresults in UB.

To solve the problem, we may want to generate untyped wrapper functions that acceptvoid *restrict self and pass the downcasted version to the underlying method:

voidRectangle_scale_wrapper(void*restrictself,intfactor) {Rectangle_scale((Rectangle* restrict)self,factor);}

But the reason we donot do this is that in C99, it is impossible to differentiatevoid from other types; if the return type isvoid, we must not emitreturn with an expression, otherwise, wemust. We could come up with something likevfuncVoid andvfuncDefaultVoid but this would increase the learning curve and complicate the design and implementation of Interface99.

However, casting untypedself to a particular type is still quite unpleasant. The best thing I came up with is theVSelf andVSELF(T) mechanism, which nonetheless works quite well.

Credits

Thanks to Rust and Golang for their implementations of traits/interfaces.

Blog posts

Release procedure

  1. UpdateIFACE99_MAJOR,IFACE99_MINOR, andIFACE99_PATCH ininterface99.h.
  2. UpdateCHANGELOG.md.
  3. Release the project inGitHub Releases.

FAQ

Q: Why use C instead of Rust/Zig/whatever else?

A: SeeDatatype99's README >>.

Q: Why not third-party code generators?

A: SeeMetalang99's README >>.

Q: How does it work?

A: Interface99 is implemented uponMetalang99, a preprocessor metaprogramming library that allows enriching pure C with some custom syntax sugar.

Q: Does it work on C++?

A: Yes, C++11 and onwards is supported.

Q: How Interface99 differs from similar projects?

A:

  • Less boilerplate. In particular, Interface99 deduces function implementations from the context, thus improving code maintenance. To my knowledge, no other alternative can do this.

  • Small. Interface99 only features the software interface concept, no less and no more -- it does not bring all the other fancy OOP stuff, unlikeGObject orCOS.

  • Depends on Metalang99. Interface99 is built uponMetalang99, the underlying metaprogramming framework. With Metalang99, you can also useDatatype99.

Other worth-mentioning projects:

Q: What about compile-time errors?

Error: missing interface implementation

[playground.c]

#defineFoo_IFACE vfunc(void, foo, int x, int y)interface(Foo);typedefstruct {chardummy;}MyFoo;// Missing `void MyFoo_foo(int x, int y)`.impl(Foo,MyFoo);

[/bin/sh]

playground.c:12:1: error: ‘MyFoo_foo’ undeclared here (not in a function)   12 | impl(Foo, MyFoo);      | ^~~~

Error: improperly typed interface implementation

[playground.c]

#defineFoo_IFACE vfunc(void, foo, int x, int y)interface(Foo);typedefstruct {chardummy;}MyFoo;voidMyFoo_foo(constchar*str) {}impl(Foo,MyFoo);

[/bin/sh]

playground.c:12:1: warning: initialization of ‘void (*)(int,  int)’ from incompatible pointer type ‘void (*)(const char *)’ [-Wincompatible-pointer-types]   12 | impl(Foo, MyFoo);      | ^~~~playground.c:12:1: note: (near initialization for ‘MyFoo_Foo_impl.foo’)

Error: unsatisfied interface requirement

[playground.c]

#defineFoo_IFACE vfunc(void, foo, int x, int y)interface(Foo);#defineBar_IFACE   vfunc(void, bar, void)#defineBar_EXTENDS (Foo)interface(Bar);typedefstruct {chardummy;}MyBar;voidMyBar_bar(void) {}// Missing `impl(Foo, MyBar)`.impl(Bar,MyBar);

[/bin/sh]

playground.c:19:1: error: ‘MyBar_Foo_impl’ undeclared here (not in a function); did you mean ‘MyBar_Bar_impl’?   19 | impl(Bar, MyBar);      | ^~~~      | MyBar_Bar_impl

Error: typo inDYN

[playground.c]

#defineFoo_IFACE vfunc(void, foo, void)interface(Foo);typedefstruct {chardummy;}MyFoo;voidMyFoo_foo(void) {}impl(Foo,MyFoo);intmain(void) {Foofoo=DYN(MyFoo,/* Foo */Bar,&(MyFoo){0}); }

[/bin/sh]

playground.c: In function ‘main’:playground.c:14:28: error: ‘Bar’ undeclared (first use in this function)   14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); }      |                            ^~~playground.c:14:28: note: each undeclared identifier is reported only once for each function it appears inplayground.c:14:31: error: expected ‘)’ before ‘{’ token   14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); }      |                            ~~~^      |                               )

Error: typo inVTABLE

[playground.c]

#defineFoo_IFACE vfunc(void, foo, void)interface(Foo);typedefstruct {chardummy;}MyFoo;voidMyFoo_foo(void) {}impl(Foo,MyFoo);intmain(void) {FooVTablefoo=VTABLE(/* MyFoo */MyBar,Foo); }

[/bin/sh]

playground.c: In function ‘main’:playground.c:14:34: error: ‘MyBar_Foo_impl’ undeclared (first use in this function); did you mean ‘MyFoo_Foo_impl’?   14 | int main(void) { FooVTable foo = VTABLE(/* MyFoo */ MyBar, Foo); }      |                                  ^~~~~~      |                                  MyFoo_Foo_impl

From my experience, nearly 95% of errors make sense.

If an error is not comprehensible at all, try to look at generated code (-E). Hopefully, thecode generation semantics is formally defined so normally you will not see something unexpected.

Q: What about IDE support?

Suggestion

A: VS Code automatically enables suggestions of generated types but, of course, it does not support macro syntax highlighting. The sad part is thatVCALL and its friends break go-to definitions and do not highlight function signatures, so we do intentionallytrade some IDE support for syntax conciseness.

Q: Which compilers are tested?

A: Interface99 is known to work on these compilers:

  • GCC
  • Clang
  • MSVC
  • TCC

[8]ページ先頭

©2009-2025 Movatter.jp