SMGreenfield
SMGreenfield

Reputation: 1720

Can an app compiled against GNU++98 / libstdc++ link to a dylib built against C++11 / libc++?

I have a C++ app that is built with Xcode 7.2.1, compiled with the GNU++98 dialect and linked against libstdc++. I need to make calls into a dylib that is compiled with C++11 and linked against libc++.

Using the default attribute visibility in the dylib's class members:

extern __attribute__((visibility("default"))) void foo(std::string const&, int);

...shows there isn't ABI compatibility (at least for strings) -- any dylib function that has a param of std::string cannot be linked to the app.

Is an appropriate solution to create a overloaded C++ member functions that only pass char* params that get constructed on the dylib side into std::string, or are there other gotchas requiring the creation of a pure C API for the dylib?

NOTE: Having now read This excellent explanation on the ABI compatibility issue, it sure seems there's little hope but to built a char-based API for the std::string params...

Upvotes: 3

Views: 105

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275650

Write your API using standard layout classes.

Standard layout classes can be quite fancy.

Here is an uncompiled array_view off the top of my head:

template<class Container>
using data_ptr_type = decltype( std::declval<Container>().data() );

template<bool b>
using bool_kt = std::integral_constant<bool, b>;

template<class T>
struct array_view {
  T* b = nullptr;
  T* e = nullptr;
  T* begin() const { return b; }
  T* end() const { return b; }
  template<class U>
  using ptr_is_compatible = bool_kt<
    std::is_same< U, T* >{} || std::is_same< U, std::remove_const_t<T>* >{} ||
    std::is_same< U, std::remove_volatile_t<T>* >{} || std::is_same< U, std::remove_cv_t<T>* >{}
  >;

  // convert from .data() and .size() containers:
  template<class In,
    std::enable_if_t<
      ptr_is_compatible< data_ptr_type< In& > >{}, int
    > = 0
  >
  array_view( In&& in ):array_view(in.data(), in.size()) {}

  // special ones:
  array_view()=default;
  array_view(array_view const&)=default;
  array_view& operator=(array_view const&)=default;

  // manual ones:
  array_view( T* ptr, std::size_t N ):array_view(ptr, ptr+N) {}
  array_view( T* s, T* f ):b(s), e(f) {}

  // from C-style array:
  template<class U, std::size_t N,
    std::enable_if_t<
      ptr_is_compatible< U* >{}
    ,int> = 0
  >
  array_view( U(&arr)[N] ):array_view(arr, N) {}

  template<class Container>
  Container copy_to() const {
    return {begin(), end()};
  }
};

While fancy, it is standard layout. So only the most insane ABI changes will break it.

Now your header file reads:

extern __attribute__((visibility("default"))) void foo(array_view<const char>, int);

and the caller can call it with foo( "hello", 7 ) or foo( std::string("hello"), 42 ) or foo( std::vector<char>{'a', 'b', 'c'}, 18 ) or whatever. You don't care.

Pointers to the begin-end of the buffer are done on the callers side, so you don't know the layout of the thing passed.

On the inside, you can marshal back to a container with a arg.to<std::string>() if you need it.

Upvotes: 1

Related Questions