The Unix C library API can only be reliably used from C
utcc.utoronto.ca
utcc.utoronto.ca
Funny because usually the reason for having a C API is to provide universal compatibility to be called from any language. If you are doing this, please avoid macros.
Not at all, that is why DCOM exists.
COM offers a language agnostic component model for interoperability between development stacks on Windows, and a modern OS ABI.
Last I looked at it, it was created in 1993 (enterprise OOP craze?) and was just regular DLLs containing regular code, wrapped in tons of symbols cruft to make a "dynamic classes protocol" work.
If you actually have to deal with it directly from a non-managed language, I'm very doubtful that it's nicer than a well-written C header file. What I did when I saw MFC macros and 40 lines of boilerplate to marshal arguments to call into a single entry point, was to RUN, which is probably a good idea unless you want to put tons of supporting tooling and code generation in place.
Using COM from C++ Builder, VB 6, VB.Script, Delphi, Perl or Python, to use 90's only examples, did not have anything to do with "MFC macros and 40 lines of boilerplate to marshal arguments to call into a single entry point".
No one uses MFC to create COM nowadays, unless they are dealing with mid-90's code.
It is modern, because a component based ABI, with language interoperability in mind, is much better than a fossiled ABI designed for PDP-11 clones.
> No one uses MFC to create COM nowadays, unless they are dealing with mid-90's code.
I guess that's what I was given...
Now tell me what will be easier to call from my hobby programming language, COM or regular object code?
> because a component based ABI
> It is modern, because a component based ABI, with language interoperability in mind, is much better than a fossiled ABI designed for PDP-11 clones.
It is literally the same "fossiled ABI", wrapped in more shitrolls to hold all the crap.
If you just mean the APIs are more pleasant to use, then maybe that is so, due to the structured objects system, if you have all the tooling in place. I'm not sure I've ever used it from a managed language, and in general, hiding cruft by adding more cruft is not my cup of tea.
Here is one example that I recently found. It shows the bullshit you have to go through to interface with COM as a C programmer: https://pastebin.com/3YvWQa5c
Can we write the COM part of that code in a better way?
And on Embarcadero's side, C++ Builder Component Wrappers.
He is so knowledgable about COM, that in 2018 writes ExitScope manually instead of using either ComPtr or _com_ptr_t.
I've quickly looked up C++/CX for example, and IT IS A DIFFERENT LANGUAGE WITH DIFFERENT SYNTAX. You have to be kidding me. So am I supposed to learn about this new language and write this one stupid function call that I want to make in a totally separate file and build it with a new build chain?
My position is, the task at hand would be so much simpler if we could just call a stupid function that we link to. I don't know a lot of problems with that approach. Tell me the problems, and tell me why COM solves them without introducing a whole bunch of new problems.
> He is so knowledgable about COM, that in 2018 writes ExitScope manually instead of using either ComPtr or _com_ptr_t.
He is not very knowledgeable about it. He even freely admits that. And he shouldn't be, just to call a friggin' function.
Regarding the ExitScope, look closely what he does there. He's building a defer statement into C++. The defer statement, while I dislike cleverness including classes and macros, is an interesting idea because it is general (unlike ComPtr or whatever, or even unique_ptr). It doesn't work just on COM objects. And it allows you to put the code inline like regular code - just closer to the point where you constructed something (not just objects) so you can more easily see it's properly cleaned up.
The thing is that, while making a new separate class sort-of guarantees proper construction and destruction, it comes at a cost because it artificially compartmentalizes the code. You get this one property (proper cleanup) but you are forced to take that other property. When defining a separate class, it's very hard to see the context in which this thing will be used. And unless it's used in many places, the idea of a clean abstraction usually doesn't come to realization. You'll just end up with a bunch of garbage class code, and when reading it you have to make some guesses about what you can assume. And you end up with two distinct pieces of code that are likely to fall out of sync.
The typical experience I've had with reading such OOP code is that I get dizzy very quickly, and it's extremely exhausting, because at each line I'm reading I have to ask, "where will this line jump implicitly? Which exceptions could it throw?" and if I find the implicit jumps correctly, the whole game just starts over in the next line there. Before I have a rough understanding of what this code does, I'll have opened 123 different files that are each extremely vague on their own.
I've seen people making separate C++ classes like "TemporaryLocale" that just switches the locale to "C" and back because there was ONE stupid problem somewhere. There is no good reason to make yet another class unless there are a couple points where this will be used, such that knowing and internalizing the class pays off for the programmer / reader. (And the proper solution would have been to just not use any of the crap that depends on the locale).
Looks like Microsoft freely admits that its previous Technologies like C++/CX are "clunky" to use (which I wager is PR speak for "impractical bullcrap"). Not to mention other technologies which are officially described as "verbose, tedious and complex".
Now it seems like C++/WinRT is the next holy grail and they provide instructions how to port your previous code (which is now obsolete - thanks Microsoft!) from any of the previous technologies to the new blessed one. We'll see how that works out. If I understand correctly, C++/WinRT allows you to write mostly standard-conforming C++, which is an incredible feat! </s>. It might make it easier to write code that is portable between platforms.
Meanwhile I'll just continue using plain C, instead of pulling out the last hair I still have on my head. Because nobody's still given me a reasonable argument what's wrong with plain functions and a linker.
And nobody has been able to give a good argument why we need an obscure technology like COM to achieve a reasonable amount of compile-time and run-time introspection. Why shouldn't we just be able to specify which sets of plain old functions should be used as constructors and destructors, for example? That would allow to write all kinds of OOP languages on top for anyone who is willing to make the associated tradeoffs.
IMO, COM more levels the playing field by making it as cumbersome to use from C as from other languages, rather than by making it as easy to use from other language as it is to use it from C.
Having said that, while I object to most ideas that "C is missing", I do think that better constant expressions and expression macros (functions on the syntactic-expressions level) would be a usability improvement in APIs compared to (token stream) preprocessor macros. Even though in the presence of the preprocessor, constants and expression macros would be somewhat redundant.
One advantage of expression macros is that you do macro expansion after parsing to an AST. You get a valid AST parse with macro expansion if and only if you get a valid parse without expansion. Expression macros are regular AST elements, and are scoped just like regular functions or variables. They don't have a weird textual scoping scheme with conditional compilation or un-/redefinability. Getting rid of that should make it easier to translate them to other languages.
Another advantage is that they allow you to actually know which constructs in an interface are part of the API. An expression macro is always supposed to be used directly by the consumer of the API, or at indirectly through another macro that is. That particular problem of the C preprocessor could in fact be solved if headers cleaned up after themselves, but in practice, preprocessor is just to messy so nobody does that.
I have to admit that I'm coming around on rust. I still think that their fanboy marketing is obnoxious...but I need to get over my penchant for finding reasons to be angry.
Yes, this. I could even tolerate cpp as it is, if I had this metadata. It is relatively easy to generate wrapper.c that simply re-invokes all interfaces, converting them to homogeneous ffi-able form. Like Pascal’s .int files which described everything you want to know as a unit user.
On the other hand, there are some things you simply cannot do without macros. I usually use function-like macros to forward to other functions with additional arguments (like __FILE__, or #arg, sizeof(arg)) which is extremely convenient and allows to cut out a lot of error-prone boilerplate.
From a different language, you can still call the function that is being forwarded to. Now try doing that with C++ templates or whatever...
C11 7.21.1.3: "The macros are [...] stderr, stdin, stdout which are expressions of type ‘‘pointer to FILE’’ that point to the FILE objects associated, respectively, with the standard error, input, and output streams."
[1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
The C ABI almost never is the OS ABI. On systems with separate address spaces, the OS ABI needs to cross a kernel boundary. In theory, you could use the same ABI for regular function calls, but the overhead wouldn’t make your compiler popular.
Why is that? Aren't the macros expanded by the preprocessor, and end up as a specific piece of code under a function name in a library?
Or does this mean there's no e.g. X function that uses one or more preprocessor macros inside it, but the very X function is but a macro itself (so it's only "callable" by the public API function name, from C code before expansion)?
Sure, but you don't know the name of that function so you can't write the FFI declaration for it. You could reverse engineer it, but this would be platform and compiler specific, so non-portable and unstable.
I come at this from a Linux perspective, but to me the user/kernel interface (sys calls etc.) should be the well defined abstraction boundary. The C library is a useful api making that easy to use, but to dictate that people can only make sys calls through C is silly and ends up with these sort of problems.
OpenBSD and, very notably, Windows instead have a defined library interface. Windows has somewhat stable “system calls”, but only through NTDLL.dll. The real stable interface is kernel32, etc.
There are arguments in favor of both approaches. One significant, if rarely appreciated benefit of the user/kernel approach is that it enables user programs that don’t use the C ABI at all. Go on Linux is like this, and Go and the vDSO have interoperability issues because the vDSO is a bit FFI-ish in Go, and Go’s FFI is not very fast.
Here's a talk where Bryan Cantrill describes the tightly coupled Solaris kernel/libc combination as a "welded unit", and mocks the Linux "libc ecosystem" and the complexity of ENOSYS handling in glibc.
I'd even say the Linux system call interface is better than the POSIX one. Well documented, no errno. I wrote a liblinux in C and Rust and it's been useful for accessing newer system calls with no libc stubs. The more subtle problem: standard C libraries are incompatible with direct system calls. They maintain a lot of global state which will be invalidated if the program calls the kernel directly. For example, clone() cannot be used with glibc.
The ideal solution is to start from scratch and build a new user space without using C at all.
These are some pretty significant exceptions though, especially in the networking-related fields. I don't want the programmes on my router to suddenly not boot after a system upgrade. Especially when I don't have the source code or if the compilers haven't caught up yet.
This specific case would be resolved if some standard (ISO C, POSIX, etc) defined a public API to do it, e.g. errno_r
(Of course, that doesn’t fix any other cases where some public functions are really macros to private functions, but this appears to be the most significant such case.)
> More advanced languages should not be forced to deal with it
(I'm more in agreement with the definition you offer than your usage.)
C has pointers which can easily enough be used as "out parameteters". In fact the C standard library regularly makes use of these. It's not amazing, but it works fine, and it's usually fairly easy to go back and forth e.g. expressing a stat(2) call is reasonably straightforward.
The C API could do this, it just doesn’t.
let res, _, _ = fun_which_returns_3_tuple ()
vs struct my_result { int res; int other; int third; };
...
struct my_result r = fun_which_returns_struct ();
printf ("%d\n", r.res);So either return the whole struct as values or, alternatively - and this is the way most library functions do it - call the function with a pointer to an empty struct which the function then fills. That leaves the return value for error status.
Mind you, under the hood all your structs will be returned through pointers to caller-allocated memory, unless it can fit into 8 bytes.
“To return a user-defined type by value in RAX, it must have a length of 1, 2, 4, 8, 16, 32, or 64 bits. [...] Otherwise, the caller assumes the responsibility of allocating memory and passing a pointer for the return value as the first argument.”
https://docs.microsoft.com/en-us/cpp/build/x64-calling-conve...
Moving structures around 'by value' rather than 'by reference' is wasteful in my view, but if you have to then you should no longer worry about the underlying mechanism until optimization time rolls around.
I'm not sure what you mean by this. Tuples are typically isomorphic to structs with unnamed fields.
It’s really rather common eg several time or socket functions take a pointer as « output parameter », fill it and return an error code.
Not the best but not the worst either.
FWIW, in rust there is std::io::last_os_error().
libc::perror works too.
It seems like Rust also needs this, either through linker magic or extern C:
https://github.com/rust-lang/rust/search?utf8=%E2%9C%93&q=er...