0

As a learning/testing-my-limits exercise, I'm trying to make a DLL that can take a given value, serialize it, and de-serialize it for a program compiled with another compiler. So far everything's gone better than expected. However, I'm running into what appears to be a memory corruption issue.

Here's the code I'm using:

//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);
};

A pod specialization for 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);
  }
};

My DLL takes advantage of this behind-the-scenes type conversion like this:

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;
}

Then my test program loads the DLL via LoadLibrary/GetProcAddress. So far, so good; I've confirmed that the DLL is actually loaded and the Add function called. I've also verified that pod<int>'s set_from is being called with the correct values. However, this is where things break down.

I expect set_from to allocate enough space for one safe_type (in this case, std::int32_t) value, then store the passed value within the allocated memory. When I check the value of *data within set_from, this seems to be the case. However, when I'm retrieving the pod<int>'s value via get, the pod's data appears to be garbage. It no longer points to the value the pod was passed during set_from.

I know set_from is called on the EXE's side and get is called on the DLL's side. My understanding of the process is as follows:

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

This seems to be breaking down where either the DLL or the EXE has to access memory the other allocated. I've seen elsewhere on SO that using this combination of GetProcessHeap/HeapAlloc should work across DLL boundaries. HeapCreate's documentation is also suggestive of the same idea:

The memory of a private heap object is accessible only to the process that created it. If a dynamic-link library (DLL) creates a private heap, the heap is created in the address space of the process that calls the DLL, and it is accessible only to that process.

as is the documentation for GetProcessHeap (emphasis mine):

The GetProcessHeap function obtains a handle to the default heap for the calling process.

Oddly enough, however, GlobalAlloc suffered the same problems GetProcessHeap/HeapAlloc did, making me further question what's going wrong here. Even odder, when I compile both the EXE and the DLL with the same compiler, everything works as expected.

Am I making incorrect assumptions about the way this allocation/deallocation process works? Should I be using something other than GetProcessHeap/HeapAlloc? Or am I simply trying to do the impossible?


Update with information gained from the comments:

Passing a pod by reference (CCDLL_v1_implementation::Add(const pod<int>& number1, const pod<int>& number2) works correctly. Only passing by value does not.

It doesn't appear to matter whether I pass "unwrapped" arguments to a pod-taking function or whether I wrap the arguments in pods first:

pod<int> a = 9;
pod<int> b = 2;

CCDLL_lib->Add(a, b);

produces the same corrupted data as

CCDLL_lib->Add(9, 2);

The only difference seems to be that wrapping arguments in pods first will call the copy c'tor when Add is called, and leaving arguments unwrapped will just call the regular c'tor.

This also doesn't seem to be a class layout issue:

if (reinterpret_cast<const void*>(&data) == reinterpret_cast<const void*>(this))
{
  //simple debug messagebox here
}

evaluates to true on both sides of the EXE/DLL boundary.


I've managed to prove the issue lies on the EXE/DLL boundary, although I still don't know why.

I added a new method to the DLL so I could focus on one argument instead of two:

void Square(pod<int> number);

Then I called it like this:

pod<int> a = 9;
CCDLL_lib->Square(a);

From a series of debug messageboxes, the following sequence occurs:

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)

If I change Square to accept a reference instead (virtual void Square(pod<int>& number) = 0;), the following sequence occurs:

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)
19
  • There's nothing wrong with the way you're using the heap. In fact, from your description, things are going wrong before any of the heap allocations are freed? However: I don't use C++ much, but it looks to me as if some of the code in the pod class might be compiled directly into the executable rather than calling the DLL. If a single pod object is being accessed by both the DLLs version and the EXEs version of the code, that would explain your problem. Commented Mar 23, 2014 at 21:19
  • 1
    You can't operate on a class object from multiple compilers, because (for a start) there's no standard for the way the members are laid out in memory. If the EXE writes data starting at the fourth byte of the object and the DLL expects it to start at the eighth byte, boom! That's almost certainly why you're getting the wrong value for data; you're not reading it from where it was written. Commented Mar 23, 2014 at 22:54
  • 1
    @Harry As far as I'm aware, the class's layout should be standard. The offsetof docs state it works on a standard-layout class, and std::is_standard_layout returned true when I tested it on a pod. Commented Mar 23, 2014 at 23:10
  • 1
    @computerfreaker: You're passing pod<int> by value to the Add function, which will invoke the copy constructor of your pod. Maybe passing the data by reference will help (i.e. virtual pod<int> Add(const pod<int>& number1, const pod<int>& number2);). Otherwise, the problem may be with the data copy workflow. Also, for the sake of analysis, I suggest you try the approach with an inputs-only function first before trying inputs and outputs to determine where the problem is. Commented Mar 24, 2014 at 23:32
  • 1
    Yes, at this point I'd hazard a guess that the object is being read from the wrong address. (You could confirm by getting the constructor to report the value of this and Square to report the value of &number.) Another thought: if this is 64-bit code, by any chance does the problem go away if you have more than one pointer stored in pod objects? Commented Mar 26, 2014 at 3:53

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.