The minor complication you get is that the C/C++ ecosystem uses char, short, int, long, and long long to determine types, while most other languages use a more sensible i8, i16, i32, i64 system. This means you can't use the native type system of your target language as the base for mangling, since i32 and i64 do not map cleanly to int, long, and long long. (Doesn't help either that pre-stdint.h libraries for defining target-independent i64 types may have chosen a different value for i64 than stdint.h).
The real problem is that a lot of the functions you would wish to call don't actually exist in the binary library you're linking against. Inline methods are frequently excluded as linkable targets, and you often don't want to link to them anyways, because they're meant to be inlined (think something like std::vector<T>::operator[]). On top of that, templated things are rarely instantiated (and have code generated for them) until their point of use).
If you restrict the API to a subset of the language, you can make C++ ABI just fine. My favorite style is abstract interfaces.
For the GUI example, look at WinRT. It exposes very high-level functionality, expressed in C++ as IUnknown-derived interfaces. These interfaces can be consumed or implemented by any compilers, or even other languages.
Such APIs work well even for very performance-critical code like DirectX or Direct2D. But there's downside, such APIs is not the most idiomatic form of C++: no exceptions, no iterators or other template shenanigans, no RTTI, no standard collections not even strings. All that stuff needs to be replaced with some equivalents.
When I only have C++ clients, I don't usually inherit from IUnknown, instead making an abstract base class with virtual destructor, and using std::unique_ptr smart pointers instead of CComPTR.
Name mangling wasn’t originally standardized, so every compiler did it differently, and now it’s too late to standardize.