0

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";
}
Björn Larsson
  • 317
  • 1
  • 5
  • 19
  • 4
    The pointer created by `c_str` is invalid as soon as the temporary `wstring` is destroyed when `createCow` exits, c++ won't copy raw strings for you – user657267 Apr 05 '18 at 18:27
  • @user657267 Yes, I've figured so much myself too. :-) Question is, how do one fix it? I've tried going for a direct wstring approach but all other answers I've found discourages that. The examples only uses ints, bools and the like, or strings only as method parameters/returns. – Björn Larsson Apr 05 '18 at 18:36
  • 1
    Use the pimpl idiom or inheritance to hide the class implementation and use `wstring` on the dll side, and have the client side set the string with a function that takes a `wchar_t const*`. – user657267 Apr 05 '18 at 18:58
  • I understand that as "No, you can't do what you want because in order for your code to work you have to use methods to set the values in the container class and your client won't be able to see them without using another method." Yet, I've seen frameworks like OpenCV done it. Perhaps I misunderstood something... – Björn Larsson Apr 06 '18 at 11:19
  • Because when I use OpenCV, I do it after installing it with Vcpkg, which downloads source code and not binaries. I probably use the source code of OpenCV even though it compiles into a DLL later on, and that's why I can access the members in its' classes. – Björn Larsson Apr 06 '18 at 11:31
  • However @user657267 your second comment lead me to a solution that is as close to what I was asking for (in the question), albeit with C4251 warnings. Should I update my question with the new code, delete my question, answer my own question or wait for you to write an answer? – Björn Larsson Apr 06 '18 at 11:35
  • If you have C4251 warnings you may still be doing something unsafe, I've posted an example of what I meant in an answer. – user657267 Apr 07 '18 at 01:50

2 Answers2

1

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();
}
user657267
  • 20,568
  • 5
  • 58
  • 77
  • Great! The code compiles without warnings and prints the desired string! Now I just need to find a way to make `Animal` copyable so the object can be passed to other methods? :-) – Björn Larsson Apr 09 '18 at 09:20
  • 1
    @BjörnLarsson for that a typical method is a virtual `clone` method, I've updated the code. – user657267 Apr 09 '18 at 17:57
  • Thank you for that! :-D But unfortunately, it doesn't seem to be what I was looking for. I have updated my question to reflect your code suggestions, but I still can't pass the object around to methods other than the one in which the object was created. :-( – Björn Larsson Apr 17 '18 at 06:48
  • 1
    You can't copy a `unique_ptr`, that's exactly the point, they're meant to be unique. Change the signature of `printMessage` to `void printMessage(unique_ptr const& animal)`. – user657267 Apr 17 '18 at 11:16
  • You still have a potential problem because the `new` used in the DLL might not match the `delete` used in the calling library. E.G. see [this SO question](https://stackoverflow.com/questions/2266218/memory-heap-management-across-dlls). You need to export a custom `FreeAnimal` function that calls `delete` for you (and maybe make a header-only implementation of an RAII wrapper class). – Peter Torr - MSFT Jul 29 '18 at 23:16
0

Place and retrieve string data to/from class members in external DLLs in the same way as in the source library.

I see header defined correctly, just don't forget to set ANIMALLIBRARY_EXPORTS in your source library, but not target library.

I believe reason why you get string corrupted is that pointer to local varialbe is retunred, and local varialbe is destroyed when leaving scope, so pointer is invalid

Animal createCow() {

    Animal cow;

    wstring sound = createCowSound(); //sound is local varialbe
    cow.Sound = sound.c_str(); //here you set pointer to local variable

    return cow; 
} //local variable is destroyed here

To fix the problem either provide operator= in the Animal class, to make copy of object that is pointed by 'const wchar_t* Sound;' pointer, or replace const wchar_t* Sound; by wstring Sound; in the Animal class.

atrelinski
  • 432
  • 2
  • 9
  • Using `wstring` doesn't work since it gives _warning C4251_. Overloading the `operator= fells` like detonating an atomic bomb to get rid of the fox stealing chickens. Could you elaborate that solution? – Björn Larsson Apr 06 '18 at 09:24
  • Overloading `operator=`, are you perhaps referring to the pimpl idiom? – Björn Larsson Apr 06 '18 at 11:03
  • @BjörnLarsson, please refer to this article about removing C4251 warning https://web.archive.org/web/20141227011407/http://support.microsoft.com/kb/168958. – atrelinski Apr 07 '18 at 00:18
  • @BjörnLarsson in regard to `operator=`. `createCow()` method return copy of `cow` (not `cow` object). Unless Animal class doesn't provide it's own `operator=`, the default implementation will be generated by compiler and this operator will be used to create copy. Because Animal class use pointer, default implementation just copy pointer, but not object that pointer is referring to. – atrelinski Apr 07 '18 at 00:39