Смесващи версии на MSVCRT

И така, имам C++ библиотека със статично свързано копие на MSVCRT. Искам всеки да може да използва моята библиотека с всяка версия на MSVC Runtime. Какъв е най-добрият начин за постигане на тази цел?

Вече доста внимавам как се правят нещата.

  1. Паметта никога не преминава DLL бариерата, за да бъде освободена
  2. C++ обектите по време на изпълнение не се предават през бариери (т.е. вектори, карти и т.н., освен ако не са създадени от тази страна на бариерата)
  3. Между бариерите не се предават манипулатори на файлове или ресурси

И все пак все още имам някакъв прост код, който причинява повреда на купчината.

Имам такъв обект в моята библиотека:

class Foos
{
public: //There is an Add method, but it's not used, so not relevant here
    DLL_API Foos();
    DLL_API ~Foos();

private:
    std::map<std::wstring, Foo*> map;
};

Foos::~Foos()
{
    // start at the begining and go to the end deleting the data object
    for(std::map<std::wstring, Foo*>::iterator it = map.begin(); it != map.end(); it++)
    {
        delete it->second;
    }
    map.clear();
}

И след това го използвам от приложението си така:

void bar() {
    Foos list;
}

След като извикам тази функция отвсякъде, получавам предупреждение за отстраняване на грешки за повреда на стека. И ако наистина го оставя да изтече, това всъщност поврежда стека и грешката на сегмента.

Моето приложение за обаждания е компилирано с инструменти на платформата Visual Studio 2012. Библиотеката е компилирана с помощта на инструменти на платформата Visual Studio 2010.

Дали това е нещо, което абсолютно не трябва да правя, или всъщност нарушавам правилата за използване на множество изпълнения?


person Earlz    schedule 14.11.2013    source източник
comment
Грешката все още ли възниква, ако премахнете кода от деструктора? (Питам, защото този код трябва да е no-op)   -  person anatolyg    schedule 14.11.2013
comment
@anatolyg да, все още получавам повреда на стека при премахване на целия код от деструктора   -  person Earlz    schedule 14.11.2013
comment
Всичко, което прави, създава полето map (защото не е указател) и след това го унищожава. Това очевидно причинява повреда на стека. Ако се насоча към VS2010 и създам приложението си по този начин, то работи добре   -  person Earlz    schedule 14.11.2013


Отговори (4)


Паметта никога не преминава DLL бариерата

Но го прави. Всъщност много пъти. Вашето приложение създаде хранилището за обекта на класа, в този случай в стека. И след това предава указател към методите в библиотеката. Започвайки с извикването на конструктора. Този показалец е това в кода на библиотеката.

Това, което се обърка в сценарий като този, е, че не е създадено правилното количество хранилище. Имате VS2012 компилатор, който да разгледа декларацията на вашия клас. Той използва изпълнението на VS2012 на std::map. Вашата библиотека обаче е компилирана с VS2010, тя използва напълно различна реализация на std::map. Със съвсем различен размер. Огромни промени благодарение на C++11.

Това е просто пълна повреда на паметта по време на работа, кодът във вашето приложение, който записва стекови променливи, ще повреди std::map. И обратното.

Излагането на C++ класове през границите на модула е изпълнено с такива капани. Обмисляйте го само когато можете да гарантирате, че всичко е компилирано с точно същата версия на компилатора и точно същите настройки. Няма преки пътища за това, не можете да смесвате кода за компилиране на Debug и Release. Създаването на библиотеката така, че никакви подробности за внедряването да не са изложени, със сигурност е възможно, трябва да спазвате тези правила:

  • Разкривайте само чисти интерфейси с виртуални методи, типовете аргументи трябва да са прости типове или интерфейсни указатели.
  • Използвайте фабрика за класове, за да създадете екземпляра на интерфейса
  • Използвайте преброяване на препратки за управление на паметта, така че винаги библиотеката да освобождава.
  • Определете основните детайли като опаковане и конвенция за обаждане със строги правила.
  • Никога не позволявайте на изключенията да преминат границата на модула, използвайте само кодове за грешки.

Дотогава ще сте на път да напишете COM код, също и стила, който виждате използван например в DirectX.

person Hans Passant    schedule 14.11.2013

Членската променлива map все още се създава от приложение с някои вътрешни данни, разпределени от приложението, а не от DLL (и те могат да използват различни реализации на map). Като основно правило не използвайте стекови обекти от DLL, добавете нещо като Foos * CreateFoos() във вашия DLL.

person Alex Watson    schedule 14.11.2013
comment
Е, това всъщност е обвиване на всички разпределения тогава. Опитах new Foos() и след това щеше да има повреда в купчината на delete list - person Earlz; 14.11.2013
comment
@Earlz: по-сложно е от това, като цяло не искате да излагате вътрешността на която и да е структура от данни, която може да има различно двоично оформление или може да разпределя памет чрез CRT функции (и STL подобектите ще имат различно двоично оформление и разпределете памет с помощта на new). На практика това означава, че трябва да прибягвате до идиома PIMPL почти навсякъде. - person Matteo Italia; 14.11.2013
comment
@MatteoItalia Всъщност това (вашият коментар) е най-добрият отговор! - person ; 14.11.2013

C++ обектите по време на изпълнение не се предават през бариери (т.е. вектори, карти и т.н., освен ако не са създадени от тази страна на бариерата)

Вие правите точно това. Вашият Foos обект се създава от основната програма в стека и след това се използва в библиотеката. Обектът съдържа карта като част от него...

Когато компилирате основната програма, тя преглежда заглавните файлове и т.н., за да определи колко стеково пространство да разпредели за обекта Foos. И извиква конструктора, който е дефиниран в библиотеката... Което може да е очаквало съвсем различно оформление/размер на обекта

person jcoder    schedule 14.11.2013
comment
Забележете, че това зависи не само от CRT версията, но дори и от флаговете за компилация. STL обектите имат различно оформление на паметта, когато са компилирани в режим на отстраняване на грешки или освобождаване. - person Matteo Italia; 14.11.2013
comment
Така че, ако трябваше да променя бита map, така че да е указател и да се разпределя и освобождава в конструктора/деструктора, вероятно ще е добре, защото тогава DLL ще контролира разпределението... Мислех си дори изброяването на private полета в заглавните файлове не беше задължително, но предполагам, че е необходимо за разпределения. - person Earlz; 14.11.2013
comment
Да, това вероятно ще свърши работа. Без изброяване на частни полета не е задължително :) - person jcoder; 14.11.2013

Може да не отговаря на вашите нужди, но не забравяйте, че внедряването на цялото нещо в заглавни файлове опростява проблема (донякъде) :-)

person manuell    schedule 14.11.2013