问题
Related to this question, I need a mutex that works across std
implementations, or a way to atomically write and read a pointer. One thread is spawned by code compiled with mingw-w64 and the other one is Visual Studio 2019 code in a static/dynamic library.
回答1:
Export from your main executable (mingw-w64) to your DLL (VC++) - compiled with separate compilers - a synchronization/mutex "handle" (an opaque pointer, typically, though it can be an index into something too) and a pair of C-style functions (if you want, you can wrap them into classes like std::mutex
and std::lock
, exposing the same API - that'd be the safest thing to do) lock and unlock that take that handle. They can be just as bare as that, or they might include additional functionality like timeout, or try-lock - these are quite useful but not required. You can also export handle_t create()
and void delete(handle_t handle)
functions.
The point is that the sync object itself (the mutex or whatever) is always manipulated by those indirection functions to avoid errors in usage, and these functions are, depending on the compiler (that can easily be detected by the preprocessor), backed by compiler-specific atomic operation intrinsics or CRT functions, like the perfectly fitting InterlockedCompareExchange (it works under mingw-w64 too) and its Visual C++ specific compiler intrinsic variant, or GCC's __atomic (more specifically, __atomic_compare_exchange
).
回答2:
struct imutex {
virtual void lock() = 0;
virtual void unlock() = 0;
virtual ~imutex(){}
};
template<class M>
struct imp_mutex: imutex {
M m;
void lock() final override { m.lock(); }
void unlock() final override { m.unlock(); }
};
struct mymutex {
using up=std::unique_ptr<imutex, void(*)(imutex*)>;
mymutex( up m_in ):m(std::move(m_in)){}
mymutex():mymutex(up(new imp_mutex<std::mutex>{}, [](imutex* m){ delete m; })) {}
void lock(){ m->lock(); }
void unlock(){ m->unlock(); }
mymutex(mymutex&&)=delete;
private:
up m;
};
this assumes ABI compatibility of vtables and std::unique_ptr, which is plausible.
If not, replace unique ptr with something custom, and replace the virtual methods with function pointers taking a void pointer.
The point is, the mutex is created and destroyed in one library's code.
Here is a pure function pointer one. It relies that a struct containing two-three ptrs has the same layout, and that thr C calling convention is the same.
Whichever library makes the mymutex, both can then use it.
struct imutex_vtable {
void (*lock)(void*) = 0;
void (*unlock)(void*) = 0;
void (*dtor)(void*)=0;
};
template<class M>
imutex_vtable const* get_imutex_vtable(){
static const imutex_vtable vtable = {
[](void* m){ static_cast<M*>(m)->lock(); },
[](void* m){ static_cast<M*>(m)->unlock(); },
[](void* m){ delete static_cast<M*>(m); }
};
return &vtable;
}
struct mymutex {
mymutex( imutex_vtable const* vt, void* pm ):vtable(vt), pv(pm){}
template<class M>
explicit mymutex(std::unique_ptr<M> m):mymutex( get_imutex_vtable<M>(), m.release() ) {}
mymutex():mymutex(std::make_unique<std::mutex>()) {}
void lock(){ vtable->lock(pv); }
void unlock(){ vtable->unlock(pv); }
~mymutex(){ vtable->dtor(pv); }
mymutex(mymutex&&)=delete;
private:
imutex_vtable const* vtable=0;
void* pv=0;
};
This is basically implementing a simple case of C++ interface inheritance using C-like implementation, then wrapping it up in classes and templates so the user won't notice.
来源:https://stackoverflow.com/questions/56728061/mutex-that-works-across-std-implementations