/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_widget_WinOLELock_h_
#define mozilla_widget_WinOLELock_h_
#include <type_traits>
#include <minwindef.h>
#include <winbase.h>
#include "mozilla/Assertions.h"
namespace details {
// implementation of `is_complete_type_v` borrowed from Raymond Chen:
// https://devblogs.microsoft.com/oldnewthing/20190711-00/?p=102682
template <
typename,
typename =
void>
constexpr
bool is_complete_type_v =
false;
template <
typename T>
constexpr
bool is_complete_type_v<T, std::void_t<decltype(
sizeof(T))>> =
true;
// The Windows SDK typically declares handle types to be of type "pointer to
// incomplete struct type"; this is broadly a good choice, but implies that we
// need to take additional steps to avoid declaring operator* or operator-> for
// smart pointers thereto.
template <
typename T>
constexpr
bool is_dereferenceable_v =
std::is_pointer_v<T> && is_complete_type_v<std::remove_pointer_t<T>>;
template <
typename T>
constexpr
bool is_arrowable_v =
std::is_pointer_v<T> && is_complete_type_v<std::remove_pointer_t<T>> &&
std::is_class_v<std::remove_pointer_t<T>>;
// SFINAE mixins for ScopedOLELock, below.
template <
typename Derived,
typename T,
bool = is_dereferenceable_v<T>,
bool = is_arrowable_v<T>>
struct OLELockMixin {};
template <
typename D,
typename T>
struct OLELockMixin<D, T,
true,
false> {
auto operator*()
const -> std::remove_pointer_t<T>& {
T ptr =
static_cast<D
const*>(
this)->get();
MOZ_ASSERT(ptr);
return *ptr;
}
};
template <
typename D,
typename T>
struct OLELockMixin<D, T,
true,
true> : OLELockMixin<D, T,
true,
false> {
auto operator->()
const -> T {
T ptr =
static_cast<D
const*>(
this)->get();
MOZ_ASSERT(ptr);
return ptr;
}
};
}
// namespace details
// RAII scoped-handle object for ::GlobalLock()ed data -- which, in practice,
// means data associated with either the clipboard or with drag-and-drop.
//
// T must be of pointer, handle, or array type.
template <
class T>
class ScopedOLELock;
// Handle/pointer implementation of ScopedHGLock.
template <
class T>
class ScopedOLELock :
public details::OLELockMixin<ScopedOLELock<T>, T> {
public:
explicit ScopedOLELock(HGLOBAL glob) : mGlobal(glob) {
static_assert(std::is_pointer_v<T>);
mData =
static_cast<T>(::GlobalLock(mGlobal));
}
~ScopedOLELock() { ::GlobalUnlock(mGlobal); }
auto operator=(ScopedOLELock
const&) =
delete;
ScopedOLELock(ScopedOLELock
const&) =
delete;
explicit operator bool()
const {
return bool(mData); }
auto get()
const -> T {
return mData; }
// operator* and operator-> are also SFINAE'd in, when usable.
// TODO(C++20): inline them here.
private:
HGLOBAL mGlobal;
T mData;
};
template <
class U>
class ScopedOLELock<U[]> {
public:
explicit ScopedOLELock(HGLOBAL glob) : mGlobal(glob) {
static_assert(details::is_complete_type_v<U>);
mData =
static_cast<U*>(::GlobalLock(mGlobal));
auto const [quot, rem] = std::lldiv(GlobalSize(mGlobal),
sizeof(U));
mExtent = quot;
NS_WARNING_ASSERTION(
rem ==
0,
"size of alleged array is not a multiple of the array element size");
}
~ScopedOLELock() { ::GlobalUnlock(mGlobal); }
auto operator=(ScopedOLELock
const&) =
delete;
ScopedOLELock(ScopedOLELock
const&) =
delete;
explicit operator bool()
const {
return bool(mData); }
auto get()
const -> U* {
return mData; }
// STL-style, for range-based iteration and so forth
auto begin()
const -> U* {
return mData; }
auto end()
const -> U* {
return mData + mExtent; }
auto size()
const -> size_t {
return mExtent; }
auto operator[](size_t index)
const {
MOZ_ASSERT(index < mExtent);
return mData[index];
}
private:
HGLOBAL mGlobal;
U* mData;
size_t mExtent;
};
// RAII scoped-handle object for _locally-created_ ::GlobalLock()ed data.
//
// T must be of POD type. A specialication for runtime-bounded array types is
// provided below.
template <
typename T>
class ScopedOLEMemory {
static_assert(alignof(T) <=
8,
"GlobalAlloc only aligns to 8-byte boundaries");
// This could be weakened if needed, but not eliminated: the destructor must
// be trivial.
static_assert(std::is_pod_v<T>,
"type must be POD");
public:
explicit ScopedOLEMemory()
: mHandle(::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(T))) {}
~ScopedOLEMemory() {
// documented as `_Frees_ptr_opt_`, so we don't need to check for NULL
::GlobalFree(mHandle);
}
ScopedOLELock<T*> lock()
const {
return ScopedOLELock<T*>(mHandle); }
explicit operator bool()
const {
return bool(mHandle); }
HGLOBAL forget() {
HGLOBAL r{mHandle};
mHandle = nullptr;
return r;
}
private:
HGLOBAL mHandle;
};
template <
typename U>
class ScopedOLEMemory<U[]> {
static_assert(alignof(U) <=
8,
"GlobalAlloc only aligns to 8-byte boundaries");
// This could be weakened if needed, but not eliminated: the destructor must
// be trivial.
static_assert(std::is_pod_v<U>,
"type must be POD");
public:
explicit ScopedOLEMemory(size_t n)
: mHandle(::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(U) * n)),
mExtent(n) {}
~ScopedOLEMemory() { ::GlobalFree(mHandle); }
ScopedOLELock<U[]> lock()
const {
return ScopedOLELock<U[]>(mHandle); }
explicit operator bool()
const {
return bool(mHandle); }
HGLOBAL forget() {
HGLOBAL r{mHandle};
mHandle = nullptr;
return r;
}
private:
HGLOBAL mHandle;
size_t mExtent;
};
#endif // mozilla_widget_WinOLELock_h_