Повреждение памяти с помощью GetProcessHeap/HeapAlloc
В качестве упражнения по изучению / тестированию моих ограничений я пытаюсь создать библиотеку DLL, которая может принимать заданное значение, сериализовать его и десериализовать для программы, скомпилированной с другим компилятором. Пока все прошло лучше, чем ожидалось. Тем не менее, я столкнулся с проблемой повреждения памяти.
Вот код, который я использую:
//POD_base.h: contains the base template POD (plain old data) class. Also contains memory allocation and deallocation functions.
namespace pod_helpers
{
void* pod_malloc(size_t size)
{
HANDLE heapHandle = GetProcessHeap();
HANDLE storageHandle = nullptr;
if (heapHandle == nullptr)
{
return nullptr;
}
storageHandle = HeapAlloc(heapHandle, 0, size);
return storageHandle;
}
void pod_free(void* ptr)
{
HANDLE heapHandle = GetProcessHeap();
if (heapHandle == nullptr)
{
return;
}
if (ptr == nullptr)
{
return;
}
HeapFree(heapHandle, 0, ptr);
}
}
template<typename T>
class pod
{
protected:
pod();
pod(const T& value);
pod(const pod& copy); // no copy ctor in any pod
~pod();
pod<T>& operator=(pod<T> value);
operator T() const;
T get() const;
void swap(pod<T>& first, pod<T>& second);
};
pod
специализация для int
:
//POD_basic_types.h: contains pod specializations for basic datatypes
template<>
class pod<int>
{
typedef int original_type; //these typedefs are meant to make the specializations easier to write and understand, since they're all the same except for the underlying datatypes.
typedef std::int32_t safe_type;
public:
pod() : data(nullptr) {}
pod(const original_type& value)
{
set_from(value);
}
pod(const pod<original_type>& copyVal)
{
original_type copyData = copyVal.get();
set_from(copyData);
}
~pod()
{
release();
}
pod<original_type>& operator=(pod<original_type> value)
{
swap(*this, value);
return *this;
}
operator original_type() const
{
return get();
}
protected:
safe_type* data;
original_type get() const
{
original_type result;
result = static_cast<original_type>(*data);
return result;
}
void set_from(const original_type& value)
{
data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type)));
if (data == nullptr)
{
return;
}
new(data) safe_type (value);
}
void release()
{
if (data)
{
pod_helpers::pod_free(data);
data = nullptr;
}
}
void swap(pod<original_type>& first, pod<original_type>& second)
{
using std::swap;
swap(first.data, second.data);
}
};
Моя DLL использует преимущества такого скрытого преобразования типов, как это:
virtual pod<int> Add(const pod<int> number1, const pod<int> number2);
pod<int> CCDLL_v1_implementation::Add(const pod<int> number1, const pod<int> number2)
{
int workingNum1, workingNum2;
workingNum1 = number1;
workingNum2 = number2;
return workingNum1 + workingNum2;
}
Затем моя тестовая программа загружает DLL через LoadLibrary
/GetProcAddress
, Все идет нормально; Я подтвердил, что DLL на самом деле загружена и Add
функция называется. Я также подтвердил, что pod<int>
"s set_from
вызывается с правильными значениями. Тем не менее, это то, где вещи ломаются.
Я жду set_from
выделить достаточно места для одного safe_type
(в этом случае, std::int32_t
), затем сохраните переданное значение в выделенной памяти. Когда я проверяю значение *data
в set_from
это похоже на случай. Тем не менее, когда я получаю pod<int>
значение через get
стручок data
кажется мусором. Это больше не указывает на значение pod
был принят во время set_from
,
я знаю set_from
вызывается на стороне EXE и get
вызывается на стороне DLL. Мое понимание процесса заключается в следующем:
EXE -> creates pod<int> (allocating memory via GetProcessHeap/HeapAlloc) -> constructs the pod with a given value, which is passed to set_from.
The DLL's Add function is called, with the given pod passed to it.
DLL -> accesses pod<int> -> gets the pod's stored value via get -> accesses the memory the EXE allocated
DLL does its calculations (here, a simple addition) with the pod's value
DLL -> creates pod<int> (allocating memory via GetProcessHeap/HeapAlloc) -> constructs the pod with a given value, which is passed to set_from.
The DLL's newly-constructed pod is returned to the EXE.
EXE -> accesses the pod's internal value via get -> accesses the memory the DLL allocated
Это, кажется, ломает, где или DLL или EXE должен получить доступ к памяти, выделенной другому. Я видел в другом месте на SO, что с помощью этой комбинации GetProcessHeap
/HeapAlloc
должен работать через границы DLL. HeapCreate
Документация также наводит на мысль о той же идее:
Память частного объекта кучи доступна только процессу, который его создал. Если динамически подключаемая библиотека (DLL) создает частную кучу, куча создается в адресном пространстве процесса, который вызывает DLL, и она доступна только этому процессу.
как это документация дляGetProcessHeap
(выделение мое):
Функция GetProcessHeap получает дескриптор кучи по умолчаниюдля вызывающего процесса.
Как ни странно, однако,GlobalAlloc
страдал такими же проблемами GetProcessHeap
/HeapAlloc
сделал, заставляя меня еще раз спросить, что здесь происходит не так. Еще более странно, когда я компилирую и EXE, и DLL одним и тем же компилятором, все работает как положено.
Я делаю неправильные предположения о том, как этот процесс распределения / освобождения работает? Должен ли я использовать что-то кроме GetProcessHeap
/HeapAlloc
? Или я просто пытаюсь сделать невозможное?
Обновление с информацией, полученной из комментариев:
Проходя pod
по ссылке (CCDLL_v1_implementation::Add(const pod<int>& number1, const pod<int>& number2)
работает правильно. Только передача по значению не делает.
Кажется, не имеет значения, передаю ли я "развернутые" аргументы pod
функция или я обернуть аргументы в pod
сначала:
pod<int> a = 9;
pod<int> b = 2;
CCDLL_lib->Add(a, b);
выдает тот же испорченный data
как
CCDLL_lib->Add(9, 2);
Единственная разница, кажется, заключается в том, что pod
s сначала вызовет копию c'or, когда Add
вызывается, а оставленные без аргументов аргументы просто вызывают обычный c'or.
Это также не кажется проблемой макета класса:
if (reinterpret_cast<const void*>(&data) == reinterpret_cast<const void*>(this))
{
//simple debug messagebox here
}
оценивается как true с обеих сторон границы EXE/DLL.
Мне удалось доказать, что проблема лежит на границе EXE/DLL, хотя я до сих пор не знаю, почему.
Я добавил новый метод в DLL, чтобы сосредоточиться на одном аргументе вместо двух:
void Square(pod<int> number);
Тогда я назвал это так:
pod<int> a = 9;
CCDLL_lib->Square(a);
Из серии отладочных сообщений появляется следующая последовательность:
Regular c'tor called (EXE)
set_from(9) called (EXE)
copy c'tor called (EXE)
get returns 9 to the copy c'tor (EXE)
set_from(9) called (EXE)
Square called (DLL) with an invalid pod (Visual Studio shows the data pointer pointing to garbage at this point)
Если я изменю Square
вместо этого принять ссылку (virtual void Square(pod<int>& number) = 0;
), следующая последовательность происходит:
Regular c'tor called (EXE)
set_from(9) called (EXE)
Square called (DLL) with a valid pod (Visual Studio shows the data pointer holding correct data)