- Notifications
You must be signed in to change notification settings - Fork14
Full-featured interfaces for C99
License
hirrolot/interface99
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
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 = 300Minimum boilerplate. Forget about maintaining virtual tables -- just write
impl(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.
| Feature | Status | Description |
|---|---|---|
| Multiple interface inheritance | ✅ | A type can inherit multiple interfaces at the same time. |
| Superinterfaces | ✅ | One interface can require a set of other interfaces to be implemented as well. |
| Marker interfaces | ✅ | An interface with no functions. |
| Single/Dynamic dispatch | ✅ | Determine a function to be called at runtime based onself. |
| Multiple dispatch | ❌ | Determine a function to be called at runtime based on multiple arguments. Likely to never going to be implemented. |
| Dynamic objects of multiple interfaces | ✅ | Given interfacesFoo andBar, you can construct an object of both interfaces,FooBar obj. |
| Default implementations | ✅ | Some interface functions may be given default implementations. A default function can call other functions and vice versa. |
| Data and implementation separation | ✅ | New interfaces can be implemented for existing types. |
Interface99 consists of one header fileinterface99.h and one dependencyMetalang99. To use it in your project, you need to:
- Add
interface99andmetalang99/includeto your include directories. - 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!
This section is based on a collection of well-documentedexamples, each of which demonstrates one specific aspect of Interface99.
- 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.
- Implementation definition.
| Linkage | Syntax |
|---|---|
| Internal | impl(Shape, Rectangle); |
| External | implExtern(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;}
- 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:
- Besides
VCALL, you also haveVCALL_OBJ,VCALL_SUPER, andVCALL_SUPER_OBJ. They all serve a different purpose; for more information, please refer totheir documentation. - In practice,
DYNis used more often thanDYN_LIT; it just accepts an ordinary pointer instead of an initialiser list, which means that you canmallocit beforehand. - If your virtual function does not accept
self, you can invoke it asobj.vptr->foo(...). - If you want to call an interface function on some concrete type, just write
VTABLE(T, Iface).foo(...).
Congratulations, this is all you need to know to write most of the stuff!
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;
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.
Having a well-defined semantics of the macros, you can write an FFI which is quite common in C.
<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>_IFACEmust expand to{ <func> }*. - For any interface, a macro
<iface>_EXTENDScan be defined, which must expand to"(" <requirement> { "," <requirement> }* ")". - For any interface function implementation, a macro
<implementer>_<func-name>_CUSTOMcan be defined, which must expand to"()".
(It might be helpful to look at thegenerated output ofexamples/shape.c.)
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>Ispecified in the macro<iface>_IFACE, the corresponding function pointer is generated. - Requirements obligation. If the macro
<iface>_EXTENDSis defined, then the listed requirements are generated to obligate<iface>implementers to satisfy them.
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>Iis defined viavfuncDefaultand<implementer>_<func-name>I_CUSTOMisnot defined,<iface>_<func-name>Iis generated (default implementation). Otherwise,<implementer>_<func-name>Iis generated (custom implementation). - Requirements satisfaction. If the macro
<iface>_EXTENDSis defined, then the listed requirements are generated to satisfy<iface>.
The same asimpl but generates anextern definition instead ofstatic.
Expands tostatic const <iface>VTable VTABLE(<implementer>, <iface>), i.e., it declares a virtual table instance of<implementer> of type<iface>VTable.
The same asdeclImpl but generates anextern declaration instead ofstatic.
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(<implementer>, <iface>, ...) expands toDYN(<implementer>, <iface>, &(<implementer>)...). The... must take the form of an initialiser list incompound literals.
Expands to<implementer>_<iface>_impl, i.e., a virtual table instance of<implementer> of type<iface>VTable.
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.
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_OBJis the same asVCALLexcept that it passesobjtofuncinstead ofobj.self.VCALL_SUPER_OBJis the same asVCALL_SUPERexcept that it passes(superiface){obj.self, obj.vptr->superiface}tofuncinstead ofobj.self.
The macros
IFACE99_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 using
ML99_EVAL, Interface99 provides itsMetalang99-compliant counterpart which can be used inside derivers and other Metalang99-compliant macros:
| Macro | Metalang99-compliant counterpart |
|---|---|
interface | IFACE99_interface |
impl | IFACE99_impl |
implExtern | IFACE99_implExtern |
(Anarity specifier anddesugaring macro are provided for each of the above macros.)
- Write
impl(...)/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 add
vfuncandvfuncDefaultto theStatementMacrosvector (seeour.clang-format). It will instruct the formatter to place them onto different lines.
- 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);
The design of Interface99 may raise some questions. In this section, you may find answers why it was designed in this way.
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.
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.
Thanks to Rust and Golang for their implementations of traits/interfaces.
- Comparing Golang and Interface99
- What’s the Point of the C Preprocessor, Actually?
- Macros on Steroids, Or: How Can Pure C Benefit From Metaprogramming
- Extend Your Language, Don’t Alter It
- Update
IFACE99_MAJOR,IFACE99_MINOR, andIFACE99_PATCHininterface99.h. - Update
CHANGELOG.md. - Release the project inGitHub Releases.
A: SeeDatatype99's README >>.
A: SeeMetalang99's README >>.
A: Interface99 is implemented uponMetalang99, a preprocessor metaprogramming library that allows enriching pure C with some custom syntax sugar.
A: Yes, C++11 and onwards is supported.
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:
- typeclass-interface-pattern, though it is rather a general idea than a ready-to-use implementation.
- OOC -- a book about OO programming in ANSI C.
[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); | ^~~~[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’)[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[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}); } | ~~~^ | )[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_implFrom 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.
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.
A: Interface99 is known to work on these compilers:
- GCC
- Clang
- MSVC
- TCC
About
Full-featured interfaces for C99
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.
