Search code examples
c++dllwstring

Memory corruption of wstring data in class placed in external DLL


Goal

Storing string data in classes placed in external DLLs.

Guides Used

Create and use your own Dynamic Link Library

Environment

  • Microsoft Visual Studio Community 2017, version 15.6.4
  • Windows 10 Pro x64

Why is this question unique?

Other QAs are talking about DLLs or classes in the same, but none handles the subject of strings in external DLL classes.

Description

I have encountered a problem when working with strings in container classes (classes whose purpose is to hold data) in DLLs. From my point of view it seems like something goes wrong with the memory position.

From Internet I've learnt that one can't easily use wstring (or string for that matter) in DLLs and that one should use pointers instead (const wchar_t*).

However, even doing so, the data in the strings in the objects seems to get corrupted if I pass them around ever so slightly.

How do I

  1. Place and retrieve string data...
  2. to/from class members...
  3. in DLLs...
  4. without having the data go missing or getting corrupt?

The code

The header in the DLL:

// AnimalLibrary.h - Contains declarations of animal methods.
#pragma once

#ifdef ANIMALLIBRARY_EXPORTS
#define ANIMALLIBRARY_API __declspec(dllexport)
#else
#define ANIMALLIBRARY_API __declspec(dllimport)
#endif

class Animal {
public:
    ANIMALLIBRARY_API static Animal* GetAnimal();
    virtual ~Animal() = 0;
    virtual void SetSound(const wchar_t* sound) = 0;
    virtual wchar_t const* GiveSound() const = 0;
    virtual Animal* clone() const = 0;
};

The body of the DLL:

// AnimalLibrary.cpp : Defines the exported functions for the DLL application.
#include "stdafx.h"
#include "AnimalLibrary.h"
#include <string>

using namespace std;

Animal* Animal::GetAnimal() {
    class RealAnimal :public Animal {
    public:
        void SetSound(wchar_t const* sound) override {
            this->sound = sound;
        }
        wchar_t const* GiveSound() const override {
            return sound.c_str();
        }
        Animal* clone() const override {
            return new RealAnimal{ *this };
        }
    private:
        wstring sound;
    };
    return new RealAnimal{};
}

Animal::~Animal() = default;

And finally the application itself:

// StringInDllClass.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "AnimalLibrary.h"
#include <iostream>
#include <string>
#include <memory>

using namespace std;

int main();

void printMessage(unique_ptr<Animal> animal);
unique_ptr<Animal> createCow();
wstring createCowSound();

int main()
{
    unique_ptr<Animal> cow = createCow();

    //This row won't compile due to
    //error C2280: attempting to reference a deleted function:
    //'unique_ptr<Animal,default_delete<_Ty>>::unique_ptr(
    //const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)'
    printMessage(cow);

    system("pause");
    return 0;
}

void printMessage(unique_ptr<Animal> animal) {
    wcout << L"The animal says " << animal->GiveSound() << endl;
}

unique_ptr<Animal> createCow() {

    unique_ptr<Animal> cow{ Animal::GetAnimal() };
    cow->SetSound(createCowSound().c_str());

    return cow;
}

wstring createCowSound() {
    return L"Moo";
}

Solution

  • Here's a barebones implementation using inheritance, this avoids passing potentially unsafe types across the dll boundary, while allowing you to use standard library types inside the dll itself. You could also achieve the same thing with the pimpl idiom.

    AnimalLibrary.h

    #pragma once
    
    class Animal 
    {
    public:
        __declspec(dllexport) static Animal* getAnimal();
        virtual ~Animal() =0;
        virtual void setSound(wchar_t const* name) =0;
        virtual wchar_t const* getSound() const =0;
        virtual Animal* clone() const =0;
    };
    

    AnimalLibrary.cpp

    #include "AnimalLibrary.h"
    #include <string>
    
    Animal* Animal::getAnimal()
    {
        class RealAnimal : public Animal
        {
        public:
            void setSound(wchar_t const* name) override
            {
                sound = name;
            }
    
            wchar_t const* getSound() const override
            {
                return sound.c_str();
            }
    
            Animal* clone() const override
            {
                return new RealAnimal{*this};
            }
    
        private:
            std::wstring sound;
        };
    
        return new RealAnimal{};
    }
    
    
    Animal::~Animal() =default;
    

    test.cpp

    #include <iostream>
    #include <memory>
    #include "AnimalLibrary.h"
    
    int main()
    {
        std::unique_ptr<Animal> cow{Animal::getAnimal()};
        cow->setSound(L"moo");
        std::wcout << cow->getSound();
        decltype(cow) dog{cow->clone()};
        dog->setSound(L"woof");
        std::wcout << dog->getSound();
    }