B
Guten Tag zusammen,
Erstmal eine Einleitung zum Thema (ja, die ist wichtig): Ich weiß nicht, ob jedem der Begriff "Binärkompatibilität" klar ist bzw. vielleicht gibt es auch mehrere Bedeutungen. Hier jedenfalls mein Verständnis davon: Ist eine DLL binärkompatibel, so kann sie z.B. als Release erstellt und trotzdem von Debug Builds verwendet werden. Grundlegend ist dieses Problem in C++ nicht behandelt, es erspart einem also eine Menge Kompilierzeit, wenn man für Projekte jeweils nur einen Build erstellen muss, um mehrere Kunden mit verschiedenen Einstellungen beliefern zu können.
Hauptproblem bei dieser Angelegenheit ist die Reservierung von dynamischem Speicher. Da es bei verschiedenen Einstellungen des Compilers und/oder verschiedenen Versionen der STL zu voneinander getrennt verwalteten Heaps sowie unterschiedlich großen Objekten kommen kann, ist es wichtig, ein Objekt immer nur auf dem Heap zu löschen, auf dem es auch erstellt wurde. Dieses Problem habe ich in Griff bekommen und die Erklärung ist mehr zum Gesamtverständnis gedacht.
Kommen wir also zum konkreten Fall, während diesem wird immer davon ausgegangen, dass die DLL in Release und die referenzierende Anwendung in Debug erstellt wird.
Wir haben also eine DLL, die eine Methode mit einem std::vector eines Typs X als Parameter enthält. Diese Methode wird jetzt in der Anwendung aufgerufen, natürlich mit einem std::vector<X>. Kritisch ist hierbei, dass ein std::vector in Debug und Release unterschiedlich verwaltet wird bzw. unterschiedlich groß ist. Somit ist ein reibungsloses Arbeiten mit dem std::vector nicht möglich. Es muss also sichergestellt werden, dass Daten, die über die Schnittstelle geschoben werden, in jedem Fall binärkompatibel sind. In meinem Fall kann ich also einen std::vector mit einem Array ersetzen. Das Array ist definitiv kompatibel, da der Typ X immer gleichgroß ist.
Zur Lösung dieses Problems wird also ein std::vector vor der Übergabe an die DLL in einen Container gepackt, dass die Liste in ein Array umkopiert. Dieses WrapperObjekt kann ohne Probleme auf die andere Seite geschoben und wieder entpackt werden, und zwar in einen passenden und mit der DLL kompatiblen std::vector. Bis hierhin komme ich ebenfalls noch alleine.
Und jetzt beginnt der Hamster zu humpeln: Nehmen wir an, der Typ Y enthält intern einen std::vector/eine std::list/wasauchimmer vom Typ X. Dieser Typ Y wird jetzt wieder in einen std::vector gepackt und einer Methode übergeben. Bei der Übergabe muss jetzt also nicht nur auf das Umkopieren des "äußeren" std::vectors geachtet, sondern auch der "innere" STLContainer berücksichtigt werden. Hierzu erhält der Wrapper eine kleine Ergänzung, indem ich die Elemente des Arrays um jeweils einen "Tag" erweitere, der zur Speicherung eines weiteren Wrappers vorgesehen ist. So kann ich die inneren Listen selber auch nochmal wrappen und alles in einen gemeinsamen großen Container packen.
Grundsätzlich von der Idee her sehe ich hier keinen Fehler, immerhin läuft das Wrappen des Typs X reibungslos, warum also nicht auch, wenn es Bestandteil eines Wrappers für den Typ Y ist? Die Wahrheit ist allerdings, dass es nicht funktioniert und hier fängt das Thema an, mich zu überfordern. Falls jemand noch drangeblieben ist, jetzt wird es langsam spannend.
Ich habe also ein Beispiel aufgesetzt mit folgendem Typ:
#ifndef WRAPPER_OBJECT_H
#define WRAPPER_OBJECT_H
#include <list>
class WrapperObject
{
public:
std::list<int > m_elements;
WrapperObject() {}
WrapperObject(int) { m_elements = std::list<int>(10); }
};
#endif //WRAPPER_OBJECT_H
Von diesem Typ wird jetzt in der Anwendung ein std::vector erstellt:
std::vector<WrapperObject> v;
v.push_back(WrapperObject(0));
Er enthält also ein WrapperObject mit einer Liste von 10 Ganzzahlen. Wird dieses Objekt nun in den Container gepackt und rübergeschoben, läuft alles wunderbar. Ein erster Erfolg!
Versuche ich mich allerdings mit einem anderen Typen als int für die innere Liste, bekomme ich eine Access Violation ab dem Zeitpunkt, ab dem ich auf die innere Liste zugreifen will. Die Exception wird an folgender Stelle ausgelöst (das wirft mir mit die meisten Rätsel auf):
Die Container-Klasse:
template<class T>
struct mwContainerItem
{
T m_element;
void * m_tag;
};
template<class T>
class mwContainer
{
STLList ToList()
{
STLList l;
for(size_t i=0; i<m_size; ++i) <---- Crash bei m_size
{
l.push_back(m_elements[i].m_element); <---- Ebenfalls bei m_elements
}
return l;
}
...
mwContainerItem<T>* m_elements;
size_t m_size;
};
Die genaue Position der Exception:
const_reference operator[](size_type _Pos) const
{ // subscript nonmutable sequence
#if _HAS_ITERATOR_DEBUGGING
if (size() <= _Pos)
{
_DEBUG_ERROR("vector subscript out of range");
_SCL_SECURE_OUT_OF_RANGE;
}
#endif /* _HAS_ITERATOR_DEBUGGING */
_SCL_SECURE_VALIDATE_RANGE(_Pos < size());
return (*(_Myfirst + _Pos)); <---- HIER
}
Es scheint so, als wäre der komplette innere Container nach der Übergabe in irgendeiner Art und Weise zerstört, dennoch kann ich ToList() aufrufen. Ich zerbreche mir jetzt seit mehreren Tagen den Kopf darüber und mir gehen langsam die Ideen/Ansätze aus. Ich kann das Problem nichtmal richtig debuggen, da es ja in Release erstellt werden muss. Wenn ich beides in Release oder beides in Debug builde, funktioniert es natürlich.
Ich hoffe wirklich, dass sich jemand darauf einen Reim machen kann und mir einen Anstoß gibt, was ich übersehen haben könnte. Ich bin für jeden dankbar, der sich dem Problem annimmt! Wenn noch Fragen offen sind oder die Erklärung nicht eindeutig genug war, bitte ich um Nachfrage, ich werde ziemlich kurzfristig darauf eingehen!
Ansonsten euch einen schönen Tag noch, ich werde mich weiter damit rumschlagen.