Code Clinic: Making Libunistd C and C++ Compatible

Linux Tux Mascot

Linux Tux Mascot

A tiny #define macro trick makes it easy to create C++ libraries that are compatible with C and C++ applications, while Libunistd makes Linux code build in Windows VC++

HOLLYWOOD, CA (GoshRobin) 2022/7/19 – So I set out today to fix a few bugs in CinePaint, software used by Hollywood as a cross-platform, open source alternative to Adobe Photoshop. Long ago, I wrote the open source libunistd library, the Windows port of the Linux POSIX/BSD/System V operating system calls. I did this in order to port Linux CinePaint to Windows without changing the application code.

Microsoft did an update to Visual Studio to prevent C programs from calling the C++ STL. Although the libunistd is compatible with C or C++ applications, it uses C++ behind the scenes. Because I hadn’t known so well what I was doing when I first created libunistd, my use of extern “C” for compatibility with C programs was sloppy. Before I could work on bugs in CinePaint, I would have to go back to fix Libunistd to eliminate some complaints from the new compiler.

fatal error C1189: #error: STL1003: Unexpected compiler, expected C++ compiler.

Microsoft made an update to Visual Studio to prevent C programs from calling the C++ STL directly. Makes sense. I wasn’t doing that in libunistd, but Visual Studio thought maybe I was. So instead of fixing CinePaint bugs as planned, it was off to fix libunistd.

C++ compilers keep getting better. It’s not a bad thing that VC++, Xcode, g++ and clang improve their compilers. However, it can come at an inconvenient time, that we must stop to go back and fix some of our legacy code that isn’t as clean as it should have been.

The Beauty of extern “C”

C++ compilers support function overloading by name-mangling, by encoding the names of functions with the function argument types. If we try to link a C++ function to a C program, there are issues. The C compiler is expecting plain old C function names, not our fancy C++ names. We can turn off name-mangling in C++ using the extern keyword, at the cost of losing the ability to do function overloading of any function defined to have C syntax this way.

When building a library, we use a little bit of #define trickery so C compilers will not be exposed to the C++ extern “C” syntax. Like this…

// cfunc.h
// Copyright 2022 CinePaint MIT Open Source
// 15 July 2022 Robin.Rowe@CinePaint.org

#ifndef cfunc_h
#define cfunc_h

#ifdef __cplusplus
#define CFUNC extern "C"
#else
#define CFUNC extern
#endif

#endif

And then we create of C-compatible header like this:

// clock_gettime.h 
// Copyright 2022 CinePaint MIT Open Source
// 13 July 2022 Robin.Rowe@CinePaint.org

#ifndef clock_gettime_h
#define clock_gettime_h

#include "cfunc.h"

typedef int clockid_t;
typedef long long useconds_t;

enum
{ CLOCK_REALTIME,
CLOCK_MONOTONIC,
CLOCK_PROCESS_CPUTIME_ID,
CLOCK_THREAD_CPUTIME_ID
};

CFUNC int clock_getres(clockid_t clk_id, struct timespec *res);
CFUNC int clock_gettime(clockid_t clk_id, struct timespec *tp);
CFUNC int clock_settime(clockid_t clk_id, const struct timespec *tp);
CFUNC int sleep(useconds_t delay);
CFUNC int usleep(useconds_t delay);
CFUNC int nanosleep(const struct timespec *req, struct timespec *rem);

#endif

In our C++ source file clock_gettime.cpp, our implementation goes like this…

int sleep(useconds_t delay)
{  std::this_thread::sleep_for(std::chrono::seconds(delay));
   return 0;
}

int usleep(useconds_t delay)
{  std::this_thread::sleep_for(std::chrono::nanoseconds(delay));
   return 0;
}

Note that we’re calling the C++ thread library, not implementing our library using C code. That’s fine because the interface to our library looks like C.

Our CFUNC macro is only for header files, for making function or data declarations. We do not use CFUNC in our implementation .cpp files. VC++ can see that our function or data was declared as having C linkage.

Because VC++ will name-mangle not only functions, but data too, if we have any global variables, then those must also be declared as CFUNC if intended to link with C code. For example…

CFUNC const char* optarg;

That optarg is data and not a function doesn’t change the extern syntax. So we use the same CFUNC macro.

When I wrote the libunistd library originally, I hadn’t thought of this CFUNC macro trick. Writing the extern “C” declarations the long way was error prone.  That I didn’t do it quite right is why the VC++ upgrade broke libunistd. A little refactoring made the compiler happy and made my code easier to read.

About Robin Rowe

Robin Rowe

I’m Robin Rowe. People call me Robin or Rob. I founded my first start-up, a car company, when I was 16. My family is in real estate and agriculture, owns the largest organic farm in Illinois. Not wishing to run the family business, I went another way. I have 30 years experience in product design and financial systems. And, with trading stocks and now crypto on my own account, I’ve made high ROI year over year.

As a student, I studied math and dance, then joined NBC-TV as a technical director. As a professor, I taught computer science at the University of Washington and the Naval Postgraduate School. As a navy research scientist, I produced a VR war game to train NATO Special Forces and a flight simulator to test naval aviators. At multi-billion dollar defense company SAIC, I advanced from program manager to enterprise manager and chief technologist, launching two profitable divisions and their AI research lab.

Recently, I led a 1-year project with the UN WHO as Augmented Reality Group Manager and Game Producer. Designed a 3D hospital simulation for the WHO to train doctors to save lives. I’ve led technology collaborations with Meta, Intel, Oracle, Universal and the Department of Defense. As an innovator, I’ve been hired by Capgemini Financial Services, Lenovo, AT&T, GoPro and DreamWorks Animation. My current focus is on developing the metaverse.