Was ist der beste Weg, um eine Objective-C-Namespace-Kollision zu lösen?


174

Objective-C hat keine Namespaces. Es ist ähnlich wie bei C, alles befindet sich in einem globalen Namespace. Es ist üblich, Klassen mit Initialen voranzustellen. Wenn Sie beispielsweise bei IBM arbeiten, können Sie ihnen "IBM" voranstellen. Wenn Sie für Microsoft arbeiten, können Sie "MS" verwenden. und so weiter. Manchmal beziehen sich die Initialen auf das Projekt, z. B. Adium-Präfixklassen mit "AI" (da keine Firma dahinter steht, könnten Sie die Initialen nehmen). Apple stellt Klassen mit NS voran und sagt, dass dieses Präfix nur für Apple reserviert ist.

So weit so gut. Das Anhängen von 2 bis 4 Buchstaben an einen vorangestellten Klassennamen ist jedoch ein sehr, sehr begrenzter Namespace. Beispielsweise könnten MS oder KI eine völlig andere Bedeutung haben (KI könnte beispielsweise künstliche Intelligenz sein), und ein anderer Entwickler könnte sich dafür entscheiden, sie zu verwenden und eine gleichnamige Klasse zu erstellen. Bang , Namespace-Kollision.

Okay, wenn dies eine Kollision zwischen einer Ihrer eigenen Klassen und einem externen Framework ist, das Sie verwenden, können Sie die Benennung Ihrer Klasse leicht ändern, keine große Sache. Aber was ist, wenn Sie zwei externe Frameworks verwenden, beide Frameworks, für die Sie keine Quelle haben und die Sie nicht ändern können? Ihre Anwendung ist mit beiden verknüpft, und Sie erhalten Namenskonflikte. Wie würden Sie diese lösen? Was ist der beste Weg, um sie so zu umgehen, dass Sie immer noch beide Klassen verwenden können?

In C können Sie diese umgehen, indem Sie nicht direkt mit der Bibliothek verknüpfen. Stattdessen laden Sie die Bibliothek zur Laufzeit mit dlopen (), suchen das gesuchte Symbol mit dlsym () und weisen es einem globalen Symbol zu (das Sie kann beliebig benennen) und dann über dieses globale Symbol darauf zugreifen. Wenn Sie beispielsweise einen Konflikt haben, weil eine C-Bibliothek eine Funktion mit dem Namen open () hat, können Sie eine Variable mit dem Namen myOpen definieren und auf die open () -Funktion der Bibliothek verweisen lassen, wenn Sie also das System open () verwenden möchten. Verwenden Sie einfach open () und wenn Sie das andere verwenden möchten, greifen Sie über die myOpen-ID darauf zu.

Ist in Objective-C etwas Ähnliches möglich, und wenn nicht, gibt es eine andere clevere, knifflige Lösung, mit der Sie Namespace-Konflikte lösen können? Irgendwelche Ideen?


Aktualisieren:

Nur um dies zu verdeutlichen: Antworten, die vorschlagen, wie Namespace-Kollisionen im Voraus vermieden oder ein besserer Namespace erstellt werden können, sind auf jeden Fall willkommen. Ich werde sie jedoch nicht als Antwort akzeptieren, da sie mein Problem nicht lösen. Ich habe zwei Bibliotheken und ihre Klassennamen kollidieren. Ich kann sie nicht ändern; Ich habe keine Quelle von beiden. Die Kollision ist bereits da und Tipps, wie sie im Voraus hätte vermieden werden können, helfen nicht mehr. Ich kann sie an die Entwickler dieser Frameworks weiterleiten und hoffe, dass sie in Zukunft einen besseren Namespace wählen, aber im Moment suche ich nach einer Lösung, um mit den Frameworks jetzt in einer einzigen Anwendung zu arbeiten. Irgendwelche Lösungen, um dies zu ermöglichen?


7
Sie haben eine gute Frage (was zu tun ist, wenn Sie zwei Frameworks mit einer Namenskollision benötigen), die jedoch im Text vergraben sind. Überarbeiten Sie es, um es klarer zu machen, und vermeiden Sie vereinfachende Antworten wie die, die Sie jetzt haben.
Benzado

4
Dies ist mein größter Kritikpunkt am aktuellen Design der Objective-C-Sprache. Schauen Sie sich die Antworten unten an. Diejenigen, die sich tatsächlich mit der Frage befassen (NSBundle-Entladen, Verwenden von DO usw.), sind abscheuliche Hacks, die für etwas so Triviales wie das Vermeiden eines Namespace-Konflikts einfach nicht notwendig sein sollten.
Erikpreis

@erikprice: Amen. Ich lerne obj-c und treffe genau dieses Problem. Kam hier auf der Suche nach einer einfachen Lösung ... lahm.
Dave Mateer

1
Technisch gesehen bieten sowohl C als auch Objective-C Unterstützung für mehrere Namensräume - jedoch nicht genau das, wonach das OP sucht. Siehe objectivistc.tumblr.com/post/3340816080/…

Hmm, das wusste ich nicht. Eine schreckliche Designentscheidung, nein?
Nico

Antworten:


47

Wenn Sie nicht gleichzeitig Klassen aus beiden Frameworks verwenden müssen und auf Plattformen abzielen, die das Entladen von NSBundle unterstützen (OS X 10.4 oder höher, keine GNUStep-Unterstützung), und die Leistung für Sie wirklich kein Problem darstellt, glaube ich Sie können jedes Mal ein Framework laden, wenn Sie eine Klasse daraus verwenden müssen, und es dann entladen und das andere laden, wenn Sie das andere Framework verwenden müssen.

Meine ursprüngliche Idee war, mit NSBundle eines der Frameworks zu laden, dann die Klassen innerhalb dieses Frameworks zu kopieren oder umzubenennen und dann das andere Framework zu laden. Hierbei gibt es zwei Probleme. Erstens konnte ich keine Funktion zum Kopieren der Daten finden, auf die verwiesen werden soll, um eine Klasse umzubenennen oder zu kopieren, und alle anderen Klassen in diesem ersten Framework, die auf die umbenannte Klasse verweisen, verweisen jetzt auf die Klasse aus dem anderen Framework.

Sie müssten eine Klasse nicht kopieren oder umbenennen, wenn es eine Möglichkeit gäbe, die Daten zu kopieren, auf die ein IMP verweist. Sie können eine neue Klasse erstellen und dann über Ivars, Methoden, Eigenschaften und Kategorien kopieren. Viel mehr Arbeit, aber es ist möglich. Sie hätten jedoch immer noch ein Problem mit den anderen Klassen im Framework, die auf die falsche Klasse verweisen.

BEARBEITEN: Der grundlegende Unterschied zwischen der C- und der Objective-C-Laufzeit besteht meines Wissens darin, dass die Funktionen in diesen Bibliotheken beim Laden von Bibliotheken Zeiger auf Symbole enthalten, auf die sie verweisen, während sie in Objective-C Zeichenfolgendarstellungen der enthalten Namen dieser Symbole. In Ihrem Beispiel können Sie also dlsym verwenden, um die Adresse des Symbols im Speicher abzurufen und an ein anderes Symbol anzuhängen. Der andere Code in der Bibliothek funktioniert weiterhin, da Sie die Adresse des ursprünglichen Symbols nicht ändern. Objective-C verwendet eine Nachschlagetabelle, um Klassennamen Adressen zuzuordnen, und es handelt sich um eine 1-1-Zuordnung, sodass Sie nicht zwei Klassen mit demselben Namen haben können. Um beide Klassen zu laden, muss der Name einer von ihnen geändert werden. Wenn jedoch andere Klassen auf eine der Klassen mit diesem Namen zugreifen müssen,


5
Ich glaube, das Entladen von Bundles wurde bis 10.5 oder höher nicht unterstützt.
Quinn Taylor

93

Das Präfixieren Ihrer Klassen mit einem eindeutigen Präfix ist grundsätzlich die einzige Option, aber es gibt verschiedene Möglichkeiten, dies weniger belastend und hässlich zu machen. Es gibt eine lange Diskussion der Optionen hier . Mein Favorit ist die @compatibility_aliasObjective-C-Compiler-Direktive ( hier beschrieben ). Sie können @compatibility_aliaseine Klasse "umbenennen", sodass Sie Ihre Klasse mit FQDN oder einem solchen Präfix benennen können:

@interface COM_WHATEVER_ClassName : NSObject
@end

@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName

@implementation ClassName //OK
//blah
@end

ClassName *myClass; //OK

Als Teil einer vollständigen Strategie könnten Sie allen Ihren Klassen ein eindeutiges Präfix wie den FQDN voranstellen und dann einen Header mit allen erstellen @compatibility_alias(ich würde mir vorstellen, dass Sie diesen Header automatisch generieren könnten).

Der Nachteil eines solchen Präfixes ist, dass Sie den wahren Klassennamen (z. B. COM_WHATEVER_ClassNameoben) in alles eingeben müssen, was den Klassennamen aus einer Zeichenfolge neben dem Compiler benötigt. Insbesondere @compatibility_aliashandelt es sich um eine Compiler-Direktive, nicht um eine Laufzeitfunktion, NSClassFromString(ClassName)die fehlschlägt (return nil) - Sie müssen sie verwenden NSClassFromString(COM_WHATERVER_ClassName). Sie können ibtoolüber die Erstellungsphase Klassennamen in einem Interface Builder nib / xib ändern, sodass Sie nicht den vollständigen COM_WHATEVER _... in Interface Builder schreiben müssen.

Letzte Einschränkung: Da es sich um eine Compiler-Direktive handelt (und zwar um eine obskure), kann sie möglicherweise nicht über Compiler hinweg portiert werden. Insbesondere weiß ich nicht, ob es mit dem Clang-Frontend aus dem LLVM-Projekt funktioniert, obwohl es mit LLVM-GCC (LLVM mit dem GCC-Frontend) funktionieren sollte.


4
Upvote für die kompatibilitätsalias, davon wusste ich nichts! Vielen Dank. Aber es löst mein Problem nicht. Ich bin nicht in der Kontrolle, Präfixe eines der von mir verwendeten Frameworks zu ändern. Ich habe sie nur in binärer Form und sie kollidieren. Was soll ich dagegen tun?
Mecki

In welche Datei (.h oder .m) soll die Zeile @compatibility_alias gehen?
Alex Basson

1
@Alex_Basson @compatibility_alias sollte wahrscheinlich in den Header aufgenommen werden, damit er überall dort sichtbar ist, wo die @ interface-Deklaration sichtbar ist.
Barry Wark

Ich frage mich, ob Sie @compatibility_alias mit Protokollnamen oder typedef-Definitionen oder irgendetwas anderem verwenden können.
Ali

Gute Gedanken hier. Könnte das Methoden-Swizzling verwendet werden, um zu verhindern, dass NSClassFromString (ClassName) für Alias-Klassen Null zurückgibt? Es wäre wahrscheinlich schwierig, alle Methoden zu finden, die einen Klassennamen haben.
Dickey Singh

12

Einige Leute haben bereits einen kniffligen und cleveren Code geteilt, der zur Lösung des Problems beitragen könnte. Einige der Vorschläge mögen funktionieren, aber alle sind weniger als ideal, und einige sind geradezu unangenehm umzusetzen. (Manchmal sind hässliche Hacks unvermeidlich, aber ich versuche sie zu vermeiden, wann immer ich kann.) Aus praktischer Sicht sind hier meine Vorschläge.

  1. Informieren Sie in jedem Fall die Entwickler über beide Frameworks des Konflikts und machen Sie deutlich, dass das Versäumnis, ihn zu vermeiden und / oder zu lösen, zu echten Geschäftsproblemen führt, die bei Nichtlösung zu Einnahmeverlusten führen können. Betonen Sie, dass die Lösung bestehender Konflikte auf Klassenbasis zwar weniger aufdringlich ist, die vollständige Änderung des Präfixes (oder die Verwendung eines Präfixes, wenn dies derzeit nicht der Fall ist, und Schande über sie!) Der beste Weg ist, um sicherzustellen, dass dies nicht der Fall ist sehe das gleiche Problem noch einmal.
  2. Wenn die Namenskonflikte auf eine relativ kleine Gruppe von Klassen beschränkt sind, prüfen Sie, ob Sie nur diese Klassen umgehen können, insbesondere wenn eine der widersprüchlichen Klassen von Ihrem Code weder direkt noch indirekt verwendet wird. Wenn ja, prüfen Sie, ob der Anbieter eine benutzerdefinierte Version des Frameworks bereitstellt, die die widersprüchlichen Klassen nicht enthält. Wenn nicht, seien Sie offen über die Tatsache, dass ihre Inflexibilität Ihren ROI durch die Verwendung ihres Frameworks verringert. Fühlen Sie sich nicht schlecht, wenn Sie innerhalb der Vernunft aufdringlich sind - der Kunde hat immer Recht. ;-);
  3. Wenn ein Framework "entbehrlicher" ist, können Sie es durch ein anderes Framework (oder eine Kombination von Code) ersetzen, entweder von Drittanbietern oder von Homebrew. (Letzteres ist der unerwünschte Worst-Case, da es sicherlich zusätzliche Geschäftskosten sowohl für die Entwicklung als auch für die Wartung verursacht.) Wenn Sie dies tun, informieren Sie den Anbieter dieses Frameworks genau darüber, warum Sie sich entschieden haben, das Framework nicht zu verwenden.
  4. Wenn beide Frameworks für Ihre Anwendung gleichermaßen unverzichtbar sind, suchen Sie nach Möglichkeiten, die Verwendung eines dieser Frameworks für einen oder mehrere separate Prozesse zu berücksichtigen, und kommunizieren Sie möglicherweise über DO, wie Louis Gerbarg vorgeschlagen hat. Je nach Kommunikationsgrad ist dies möglicherweise nicht so schlecht, wie Sie es vielleicht erwarten. Einige Programme (einschließlich QuickTime, glaube ich) verwenden diesen Ansatz, um eine detailliertere Sicherheit zu bieten, die durch die Verwendung von Sicherheitsgurt- Sandbox-Profilen in Leopard bereitgestellt wird , sodass nur eine bestimmte Teilmenge Ihres Codes kritische oder sensible Vorgänge ausführen darf. Leistung ist ein Kompromiss, kann aber Ihre einzige Option sein

Ich vermute, dass Lizenzgebühren, Bedingungen und Laufzeiten sofortige Maßnahmen in Bezug auf einen dieser Punkte verhindern können. Hoffentlich können Sie den Konflikt so schnell wie möglich lösen. Viel Glück!


8

Dies ist grob, aber Sie können verteilte Objekte verwenden, um eine der Klassen nur in einer untergeordneten Programmadresse und einem RPC zu behalten. Das wird chaotisch, wenn Sie eine Menge Dinge hin und her geben (und möglicherweise nicht möglich, wenn beide Klassen Ansichten usw. direkt manipulieren).

Es gibt andere mögliche Lösungen, aber viele davon hängen von der genauen Situation ab. Verwenden Sie insbesondere die modernen oder älteren Laufzeiten, sind Sie eine fette oder einzelne Architektur, 32 oder 64 Bit, auf welche Betriebssystemversionen zielen Sie ab, verknüpfen Sie dynamisch, statisch oder haben Sie die Wahl, und ist dies möglicherweise der Fall? Es ist in Ordnung, etwas zu tun, das möglicherweise Wartung für neue Software-Updates erfordert.

Wenn Sie wirklich verzweifelt sind, können Sie Folgendes tun:

  1. Nicht direkt mit einer der Bibliotheken verknüpfen
  2. Implementieren Sie eine alternative Version der objc-Laufzeitroutinen, die den Namen beim Laden ändert (das genaue Auschecken des objc4- Projekts hängt von einer Reihe der oben gestellten Fragen ab, sollte jedoch unabhängig von den Antworten möglich sein ).
  3. Verwenden Sie etwas wie mach_override , um Ihre neue Implementierung einzufügen
  4. Laden Sie die neue Bibliothek mit normalen Methoden. Sie durchläuft die gepatchte Linker-Routine und ändert ihren Klassennamen

Das Obige wird ziemlich arbeitsintensiv sein, und wenn Sie es gegen mehrere Bögen und verschiedene Laufzeitversionen implementieren müssen, wird es sehr unangenehm sein, aber es kann definitiv zum Funktionieren gebracht werden.


4

Haben Sie darüber nachgedacht, die Laufzeitfunktionen (/usr/include/objc/runtime.h) zu verwenden, um eine der widersprüchlichen Klassen in eine nicht kollidierende Klasse zu klonen und dann das Framework für kollidierende Klassen zu laden? (Dies würde erfordern, dass die kollidierenden Frameworks zu unterschiedlichen Zeiten geladen werden, um zu funktionieren.)

Sie können die Klassen ivars, Methoden (mit Namen und Implementierungsadressen) und Namen mit der Laufzeit überprüfen und Ihre eigenen dynamisch erstellen, um das gleiche ivar-Layout, die gleichen Methodennamen / Implementierungsadressen und nur den Namen zu erhalten (um das zu vermeiden) Kollision)


3

Verzweifelte Situationen erfordern verzweifelte Maßnahmen. Haben Sie darüber nachgedacht, den Objektcode (oder die Bibliotheksdatei) einer der Bibliotheken zu hacken und das kollidierende Symbol in einen alternativen Namen zu ändern - von gleicher Länge, aber unterschiedlicher Schreibweise (aber Empfehlung, gleiche Namenslänge)? Von Natur aus böse.

Es ist nicht klar, ob Ihr Code die beiden Funktionen direkt mit demselben Namen, aber unterschiedlichen Implementierungen aufruft oder ob der Konflikt indirekt ist (und es ist auch nicht klar, ob er einen Unterschied macht). Es besteht jedoch zumindest eine externe Chance, dass das Umbenennen funktioniert. Es könnte auch eine Idee sein, den Unterschied in der Schreibweise zu minimieren, damit die Umbenennung die Dinge nicht aus der Reihenfolge bringt, wenn die Symbole in einer Tabelle in einer sortierten Reihenfolge vorliegen. Dinge wie die binäre Suche werden verärgert, wenn das Array, das sie suchen, nicht wie erwartet in sortierter Reihenfolge sortiert ist.


Das Ändern der Bibliothek auf der Festplatte kommt nicht in Frage, da die Lizenz dies nicht zulässt. Das Ändern der Symbole im Speicher wäre möglich, aber ich sehe keine Möglichkeit, wie dies getan werden könnte (Laden der Bibliothek in den Speicher, Ändern und dann Übergeben an den dynamischen Linker ... siehe keine Methode dafür).
Mecki

3
OK - Zeit zu entscheiden, welche der beiden Bibliotheken weniger wichtig ist, und einen Ersatz zu finden. Und erklären Sie demjenigen, für den Sie bezahlen, aber dass Sie aufgeben, dass der Grund, warum Sie sich ändern, auf diese Kollision zurückzuführen ist und sie Ihr Geschäft behalten können, indem sie Ihr Problem beheben.
Jonathan Leffler

2

@compatibility_alias wird in der Lage sein, Klassennamensraumkonflikte zu lösen, z

@compatibility_alias NewAliasClass OriginalClass;

Dadurch werden jedoch keine Aufzählungen, Typedefs oder Protokollnamespace-Kollisionen behoben . Darüber hinaus spielt es nicht gut mit @classVorwärtsdeklarationen der ursprünglichen Klasse. Da die meisten Frameworks mit diesen nicht klassenbezogenen Dingen wie typedefs geliefert werden, können Sie das Namespace-Problem wahrscheinlich nicht nur mit compatible_alias beheben.

Ich habe mir ein ähnliches Problem wie Ihres angesehen , aber ich hatte Zugriff auf die Quelle und baute die Frameworks auf. Die beste Lösung, die ich dafür gefunden habe, war die @compatibility_aliasbedingte Verwendung von #defines zur Unterstützung der Aufzählungen / typedefs / protocol / etc. Sie können dies unter bestimmten Bedingungen auf der Kompilierungseinheit für den betreffenden Header tun, um das Risiko zu minimieren, dass Inhalte im anderen kollidierenden Framework erweitert werden.


1

Es scheint, dass das Problem darin besteht, dass Sie nicht auf Header-Dateien von beiden Systemen in derselben Übersetzungseinheit (Quelldatei) verweisen können. Wenn Sie Objective-C-Wrapper um die Bibliotheken erstellen (wodurch sie im Prozess benutzerfreundlicher werden) und nur # die Header für jede Bibliothek in die Implementierung der Wrapper-Klassen einbeziehen, werden Namenskollisionen effektiv getrennt.

Ich habe nicht genug Erfahrung damit in Ziel-C (gerade erst angefangen), aber ich glaube, das würde ich in C tun.


1
Aber würden Sie nicht immer noch auf Kollisionen stoßen, wenn Sie versuchen würden, beide Wrapper-Header in dieselbe Datei aufzunehmen (da sie jeweils die Header des jeweiligen Frameworks selbst enthalten müssten)?
Wilco

0

Das Präfixieren der Dateien ist die einfachste Lösung, die mir bekannt ist. Cocoadev hat eine Namespace-Seite, die eine Community-Aktion ist, um Namespace-Kollisionen zu vermeiden. Fühlen Sie sich frei, Ihre eigenen zu dieser Liste hinzuzufügen, ich glaube, das ist, was es ist.

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix


Wie hilft das Präfixieren der Dateien, wenn die in den Dateien definierten Objekte das Problem verursachen? Ich kann sicherlich die h-Dateien manipulieren, aber dann wird der Linker die Objekte überhaupt nicht mehr finden, wenn er gegen das Framework mag: - /
Mecki

Ich glaube, dies wäre eher eine Bestpraxis als eine Lösung für das ursprüngliche Problem.
Ali

Dies spricht die Frage überhaupt nicht an
Madbreaks

0

Wenn Sie eine Kollision haben, würde ich vorschlagen, dass Sie sich überlegen, wie Sie eines der Frameworks aus Ihrer Anwendung heraus umgestalten könnten. Eine Kollision deutet darauf hin, dass die beiden ähnliche Dinge tun wie sie ist, und Sie könnten wahrscheinlich ein zusätzliches Framework verwenden, indem Sie einfach Ihre Anwendung umgestalten. Dies würde nicht nur Ihr Namespace-Problem lösen, sondern auch Ihren Code robuster, einfacher zu warten und effizienter machen.

Bei einer technischeren Lösung wäre dies meine Wahl, wenn ich in Ihrer Position wäre.


0

Wenn sich die Kollision nur auf der Ebene der statischen Verknüpfung befindet, können Sie auswählen, welche Bibliothek zum Auflösen von Symbolen verwendet wird:

cc foo.o -ldog bar.o -lcat

Wenn foo.ound bar.obeide Referenz das Symbol ratdann libdogwird lösen foo.o's ratund libcatwird beheben bar.oist rat.


0

Nur ein Gedanke ... nicht getestet oder bewiesen und könnte ein Zeichen sein, aber haben Sie darüber nachgedacht, einen Adapter für die von Ihnen verwendeten Klassen aus dem einfacheren Framework zu schreiben ... oder zumindest deren Schnittstellen?

Wenn Sie einen Wrapper um das einfachere Framework schreiben würden (oder um die Schnittstellen, auf die Sie am wenigsten zugreifen), wäre es nicht möglich, diesen Wrapper in eine Bibliothek zu kompilieren. Da die Bibliothek vorkompiliert ist und nur ihre Header verteilt werden müssen, würden Sie das zugrunde liegende Framework effektiv ausblenden und es mit dem zweiten Framework kombinieren können, wenn es zu Konflikten kommt.

Ich weiß natürlich zu schätzen, dass es wahrscheinlich Zeiten gibt, in denen Sie Klassen aus beiden Frameworks gleichzeitig verwenden müssen. Sie könnten jedoch Fabriken für weitere Klassenadapter dieses Frameworks bereitstellen. Auf der Rückseite dieses Punktes müssten Sie wahrscheinlich ein wenig umgestalten, um die von Ihnen verwendeten Schnittstellen aus beiden Frameworks zu extrahieren. Dies sollte ein guter Ausgangspunkt für die Erstellung Ihres Wrappers sein.

Sie können auf die Bibliothek aufbauen, wenn Sie weitere Funktionen aus der umschlossenen Bibliothek benötigen, und sie einfach neu kompilieren, wenn Sie sie ändern.

Wiederum in keiner Weise bewiesen, aber ich wollte eine Perspektive hinzufügen. ich hoffe es hilft :)


-1

Wenn Sie zwei Frameworks mit demselben Funktionsnamen haben, können Sie versuchen, die Frameworks dynamisch zu laden. Es wird unelegant sein, aber möglich. Wie es mit Objective-C-Klassen geht, weiß ich nicht. Ich vermute, die NSBundleKlasse wird Methoden haben, die eine bestimmte Klasse laden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.