PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Wie funktionieren Plugins?


rotalever
2007-08-14, 15:32:44
Die Frage klingt einfach, aber ich kann mir selbst keine vernünftige Antwort geben. Wie funktionieren Plugins für Software? Damit meine ich, wie kann der Code des Plugins mit der Software interagieren? Braucht es da bestimmte Schnittstellen, wie sehen diese aus?
Oftmals liegt eine Software kompiliert vor. Dann kompiliert jemand einen Plugin in eine dll Datei und es funktioniert, aber warum? Bisher dachte ich immer, dass Bibliotheken während der Compilezeit des eigentlichen Programms "bekannt" sein müssen.

JTHawK
2007-08-14, 15:38:15
Für Plugins gibt es z.B. ein "Pluginverzeichnis", jenes wird beim Start der Software (oder per Hand durch eine Funktion, "Update Plugins" oder ähnliches) nach kompatiblen Plugins (zb. .dll Dateien) gescannt. Jene Registrieren sich dann beim Hauptprogramm bzw. werden von diesem nach verwertbaren/relevanten Informationen und Funktionen gecheckt. Besteht ein Plugin die Anforderungen des Hauptprogramms werden dessen Funtionen freigeschaltet. Selbstverständlich nur, wenn das Hauptprogramm eine Pluginschnittstelle besitzt. .... ganz grob gesagt :D

Monger
2007-08-14, 15:39:34
Keine Ahnung wie das bei den alten Sprachen funktioniert, aber bei allen interpretierten Sprachen (also vorwiegend Java/.NET) werden die Bibliotheken ja erst bei Bedarf angezogen.

Es gibt da spezielle Methoden, mit denen man Klassen zur Laufzeit laden kann. Die werden dann auf ein Interface oder eine (abstrakte) Oberklasse gecastet und ganz normal benutzt.

Edit: bei Java benutzt man dazu den ClassLoader (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html), womit man erstmal z.B. sich alle Klassen sucht die in einem bestimmten Verzeichnis liegen. Dann erzeugt man aus den Class Objekten eine neue Instanz (siehe "Class" Klasse), und castet das ganze dann auf ein festgelegtes Interface. Natürlich müssen die Plugins diesem Interface entsprechen, sonst geht da nix! ;)

rotalever
2007-08-14, 16:05:08
Bei interpretierten Sprachen kann ich mir das auch noch irgendwie vorstellen, aber bei fertig kompiliertem, zum Beispiel C,C++?

EcHo
2007-08-14, 17:50:18
Gibtes sehr viele Möglichekiten, z.B. auch http://www.osgi.org/ (dynamisches laden /starten/beenden/updaten zur laufzeit)

Coda
2007-08-14, 17:55:56
Das Hostprogramm stellt halt eine fest definierte Schnittstelle bereit die dann von der DLL angesprochen wird nachdem sie geladen wurde. Eigentlich kein Hexenwerk.

Fruli-Tier
2007-08-14, 18:01:19
Für Plugins gibt es z.B. ein "Pluginverzeichnis", jenes wird beim Start der Software (oder per Hand durch eine Funktion, "Update Plugins" oder ähnliches) nach kompatiblen Plugins (zb. .dll Dateien) gescannt. Jene Registrieren sich dann beim Hauptprogramm bzw. werden von diesem nach verwertbaren/relevanten Informationen und Funktionen gecheckt. Besteht ein Plugin die Anforderungen des Hauptprogramms werden dessen Funtionen freigeschaltet. Selbstverständlich nur, wenn das Hauptprogramm eine Pluginschnittstelle besitzt. .... ganz grob gesagt :D
Ich habs noch nicht gemacht, mir auch noch keine bestehenden Programme mit Pluginunterstützung angesehen, aber so in etwa (grob ;)) muss das laufen. Um es mal konkreter zu formulieren:

Eine Anwendung erwartet von Plugins (in Form von DLLs), dass sie bestimmte Funktionen exportieren. Ist dies gegeben, also es werden alle erwarteten Funktionen über die DLL angeboten, so kann sich das Programm diese DLL als gültiges Plugin "merken" oder was auch immer damit noch geschehen soll. Je nach Bedarf werden nun einfach über die vordefinierte Schnittstelle - die von jeder DLL erfüllt werden muss und vom Programmierer der Anwendung definiert wird - die Funktionen aufgerufen und es kommen mehr oder weniger sinnvolle Dinge hinten raus.

Fruli-Tier
2007-08-14, 18:03:08
Oftmals liegt eine Software kompiliert vor. Dann kompiliert jemand einen Plugin in eine dll Datei und es funktioniert, aber warum? Bisher dachte ich immer, dass Bibliotheken während der Compilezeit des eigentlichen Programms "bekannt" sein müssen.
DLLs können dynamisch - daher, oh Wunder, der Name Dynamic Link Library - vom laufenden Programm geladen werden.

@Coda
Deine eloquenten Erklärungen sind einfach unerreicht ;)

Arokh
2007-08-14, 18:55:24
DLLs können dynamisch - daher, oh Wunder, der Name Dynamic Link Library - vom laufenden Programm geladen werden.hier muß man unterscheiden: es gibt das automatische Laden einer DLL beim Programmstart und das dynamische Laden während das Programm läuft.
Das "dynamic" in DLL hat nichts mit letzterer Dynamik zu tun, sondern bezieht sich darauf, daß die DLL anders als eine statische Library nicht schon zur Compilier- oder besser gesagt Link-Zeit in das Programm eingebunden wird.
Zum automatischen Laden muß eine statische Lib, die sog. Import-Lib, an das Programm gelinkt sein, die dann das Laden der DLL auslöst. Beim dynamischen Laden wird die Win32-API-Funktion LoadLibrary() aufgerufen und dann mittels GetProcAddress() Funktionspointer auf Funktionen in der DLL extrahiert.
Der Vorteil des dynamischen Ladens ist, daß man die Fehlerbehandlung selber gestalten kann, wenn die DLL nicht gefunden wird. Beim automatischen Laden gibt es nur eine Fehlermeldung, daß die und die DLL nicht gefunden wurde, worauf das Programm abbricht.
Der Nachteil ist, daß das dynamische Laden viel mehr Handarbeit erfordert, und Probleme bei OOP macht, da mit LoadLibrary() nur freie Funktionen, aber keine Methoden von Klassen geladen werden können.

Fruli-Tier
2007-08-14, 19:15:08
hier muß man unterscheiden: es gibt das automatische Laden einer DLL beim Programmstart und das dynamische Laden während das Programm läuft.
Das "dynamic" in DLL hat nichts mit letzterer Dynamik zu tun, sondern bezieht sich darauf, daß die DLL anders als eine statische Library nicht schon zur Compilier- oder besser gesagt Link-Zeit in das Programm eingebunden wird.
Zum automatischen Laden muß eine statische Lib, die sog. Import-Lib, an das Programm gelinkt sein, die dann das Laden der DLL auslöst. Beim dynamischen Laden wird die Win32-API-Funktion LoadLibrary() aufgerufen und dann mittels GetProcAddress() Funktionspointer auf Funktionen in der DLL extrahiert.
Der Vorteil des dynamischen Ladens ist, daß man die Fehlerbehandlung selber gestalten kann, wenn die DLL nicht gefunden wird. Beim automatischen Laden gibt es nur eine Fehlermeldung, daß die und die DLL nicht gefunden wurde, worauf das Programm abbricht.Stimmt, das war von mir zu lachs formuliert.
Der Nachteil ist, daß das dynamische Laden viel mehr Handarbeit erfordert, und Probleme bei OOP macht, da mit LoadLibrary() nur freie Funktionen, aber keine Methoden von Klassen geladen werden können.Mit einem Pointer auf eine abstrakte Klasse kann man jedoch auf eine in der DLL erstellte Instanz (welche natürlich von der abstrakten Klasse abgeleitet sein muss) zugreifen.

rotalever
2007-08-14, 20:01:51
Beim dynamischen Laden wird die Win32-API-Funktion LoadLibrary() aufgerufen und dann mittels GetProcAddress() Funktionspointer auf Funktionen in der DLL extrahiert.
Ok, das verstehe ich. Also muss es ein festes Interface geben (irgendwie auch klar...) mit dem das Programm mit dem Plugin kommuniziert. Das Plugin selbst kann dann durch entsprechende Funktion des OS reingeladen werden.

Für mich waren diese Plugin-Geschichten bisher nämlich immer ein Buch mit Sieben Siegeln.

Neomi
2007-08-14, 22:54:54
Am besten hilft wohl ein eher konkretes Beispiel, ich fang dann mal an. Als Beispiel nehme ich mal ein Import-/Exportplugin für 3D Studio Max. Nichts schwieriges, nur die Kommunikation.

Zuerst wird das Plugin-Verzeichnis (bzw. alle in einer entsprechenden Liste eingetragenen Verzeichnisse) nach verkappten DLL-Dateien (die Endung ist meist schon an die jeweilige Plugin-Kategorie angepaßt, die Dateien sind aber DLLs) durchsucht, diese werden geladen. Der Einstieg sieht grob so aus:

Main.cpp
#include <windows.h>
#include "Max.h"
#include "istdplug.h"
#include "iparamb2.h"
#include "iparamm2.h"

#include "FooExporterClassDesc.h"
#include "FooImporterClassDesc.h"

FooExporterClassDesc FooExporterDesc;
FooImporterClassDesc FooImporterDesc;

HINSTANCE hInstance;

#define EXPORT __declspec(dllexport)

BOOL WINAPI DllMain(HINSTANCE hInstDLL, ULONG fdwReason, LPVOID lpvReserved)
{
hInstance = hInstDLL;

return(TRUE);
}

EXPORT const TCHAR* LibDescription()
{
return(_T("Export to / Import from Foo File"));
}

EXPORT int LibNumberClasses()
{
return(2);
}

EXPORT ClassDesc* LibClassDesc(int i)
{
switch (i)
{
case 0: return (&FooExporterDesc);
case 1: return (&FooImporterDesc);
}

return(NULL);
}

EXPORT ULONG LibVersion()
{
return(VERSION_3DSMAX);
}

Erst werden ein paar Standardheader inkludiert, um Interfaces für von Max bereitgestellte Basisklassen zu definieren. Nur abstrakte Klassen, keine Implementierungen, was aber zum Aufbau von gültigen VTables natürlich reicht.

Als zweites werden globale Objekte von Klassen erstellt, die nur zur groben Beschreibung der Plugins (mehrere in einer DLL möglich) und zur Erstellung der entsprechenden Objekte dienen.

DllMain wird von Windows aufgerufen, wenn die DLL geladen wird. Sie dient hier nur zur Initialisierung des Instanzhandles, um z.B. später auf interne Ressourcen zugreifen zu können. Danach bekommt Max einen Handle auf die DLL.

Max erwartet jetzt bestimmte exportierte Funktionen. LibVersion wird aufgerufen, um abzufragen, für welche Version das Plugin gedacht ist. Dabei wird einfach ein in den Headern des SDKs definierter Wert zurückgegeben. Wenn die Version nicht bekannt ist (z.B. Max 8 Plugin mit Max 6), kann an der Stelle abgebrochen werden. Bei (zu) alter Version wird entweder auch abgebrochen und das Plugin ignoriert, oder es werden im späteren Verlauf der Ausführung Kompatibilitätssettings benutzt (z.B. in der älteren Version genutzte Defaultsettings für neu erstellte Objekte anstelle der aktuellen Defaultsettings). Bei großen Produkten, die auch mit Plugins zu früheren Versionen funktionieren sollen, ist sowas nötig, bei kleinen Projekten kann so eine Absicherung zumindest nicht schaden.

Mit LibDescription wird einfach eine Beschreibung abgefragt, die dann dem Benutzer angezeigt wird (z.B. im Splashscreen beim Startup, wenn die gerade ladenden Pluginnamen durchrattern) aber keine weitere Relavanz hat.

LibNumberClasses ist sehr wichtig, die Funktion sagt einfach wieviele Plugins in der DLL enthalten sind. In diesem Fall 2, ein Importer und ein Exporter. So oft wird dann auch LibClassDesc von Max aufgerufen, um die Beschreibungen der einzelnen Pluginklassen zu bekommen. Hier werden dann die Pointer auf die global angelegten Objekte zurückgegeben. Das sind ganz einfache Klassen (von ClassDesc abgeleitet, damit Max die virtuellen Methoden aufrufen kann) mit ihrer VTable als einziger Eigenschaft. Hier ist das Beispiel der Exporterbeschreibung:

FooExporterClassDesc.h
#ifndef _INC_FOOEXPORTERCLASSDESC_H_
#define _INC_FOOEXPORTERCLASSDESC_H_

#define FOOEXPORTER_CLASS_ID Class_ID(0x01234567, 0x89ABCDEF)

#include <windows.h>
#include "Max.h"
#include "istdplug.h"
#include "iparamb2.h"
#include "iparamm2.h"

#include "FooExporter.h"

extern HINSTANCE hInstance;

class FooExporterClassDesc : public ClassDesc2
{
public:
int IsPublic() { return(TRUE); }
void* Create(BOOL bLoading = FALSE) { return(new FooExporter()); }
const TCHAR* ClassName() { return(TEXT("FooExporter")); }
SClass_ID SuperClassID() { return(SCENE_EXPORT_CLASS_ID); }
Class_ID ClassID() { return(FOOEXPORTER_CLASS_ID); }
const TCHAR* Category() { return(TEXT("Export")); }

const TCHAR* InternalName() { return(TEXT("FooExporter")); }
HINSTANCE HInstance() { return(hInstance); }
};

#endif // _INC_FOOEXPORTERCLASSDESC_H_

Diese Methoden kann Max mit dem abgefragten Pointer jetzt alle aufrufen (und tut es auch). Die Methoden IsPublic, ClassName, Category, InternalName und HInstance sind einfach nur zur Abfrage von nicht unwichtigen, aber auch nicht weiter komplizierten oder interessanten Dingen vorhanden.

Wichtig (in dieser Reihenfolge) sind...

SuperClassID: damit weiß Max, daß es sich um eine vom definierten Interface SceneExport abgeleitete Klasse handelt. Den Pointer vom erstellten Objekt kann Max dann passend umcasten, hat also eine gezielte Möglichkeit zur Kommunikation mit dem Plugin. Weil SceneExport als Interface für Exporter genutzt wird, stellt Max das Plugin erstmal beiseite, bis der User eine Szene exportieren will. Dann "erinnert" sich Max daran, daß da ja ein zusätzlicher Exporter schlummert.

Create: mit der Methode erstellt Max sich ein Objekt des Exporters, sobald der User etwas (egal was und in welchem Format) exportieren will. Auch wenn sich herausstellt, daß dieser spezielle Exporter nicht gefragt ist, immerhin muß Max ihn als Option anbieten können und weiß noch nichtmal, welche Formate der Exporter bietet. Will der User was anderes, wird der Exporter eben wieder freigegeben.

ClassID: interessiert Max nicht, könnte aber andere Plugins interessieren, die speziell mit diesem Exporter kommunizieren wollen. Bei Exportern zwar irrelevant, bei anderen Plugintypen (z.B. Materialien oder Renderer) aber nicht. Wenn ein anderes Plugin aber trotzdem diesen Exporter will, dann weiß es (durch die Header, in denen die ClassID definiert wird), wonach es Max zu fragen hat.

Der Exporter selbst sieht dann so aus:

FooExporter.h
#ifndef _INC_FOOEXPORTER_H_
#define _INC_FOOEXPORTER_H_

#include <windows.h>
#include "Max.h"
#include "istdplug.h"
#include "iparamb2.h"
#include "iparamm2.h"

class FooExporter : public SceneExport
{
public:
static HWND hParams;

int ExtCount();
const TCHAR* Ext(int n);
const TCHAR* LongDesc();
const TCHAR* ShortDesc();
const TCHAR* AuthorName();
const TCHAR* CopyrightMessage();
const TCHAR* OtherMessage1();
const TCHAR* OtherMessage2();
unsigned int Version();
void ShowAbout(HWND hWnd);

BOOL SupportsOptions(int n, DWORD Options);
int DoExport(const TCHAR *pszName, ExpInterface *ei, Interface *gi,
BOOL bSuppressPrompts = FALSE, DWORD Options = 0);

FooExporter();
~FooExporter();

protected:
// many things internal to this exporter
};

#endif // _INC_FOOEXPORTER_H_

FooExporter.cpp
#include "FooExporter.h"

FooExporter::FooExporter() {}
FooExporter::~FooExporter() {}

int FooExporter::ExtCount()
{
return(1);
}

const TCHAR* FooExporter::Ext(int n)
{
switch (n)
{
case 0: return(_T("foo"));
}

return(_T(""));
}

const TCHAR* FooExporter::LongDesc()
{
return(_T("Neomis nonexisting Foo File Format"));
}

const TCHAR* FooExporter::ShortDesc()
{
return(_T("Foo File"));
}

const TCHAR* FooExporter::AuthorName()
{
return(_T("Neomi"));
}

const TCHAR* FooExporter::CopyrightMessage()
{
return(_T(""));
}

const TCHAR* FooExporter::OtherMessage1()
{
return(_T(""));
}

const TCHAR* FooExporter::OtherMessage2()
{
return(_T(""));
}

unsigned int FooExporter::Version()
{
return(100); // major * 100 + minor
}

void FooExporter::ShowAbout(HWND hWnd)
{
return;
}

BOOL FooExporter::SupportsOptions(int n, DWORD Options)
{
switch (n)
{
case 0:
if ((Options & ~(SCENE_EXPORT_SELECTED)) == 0)
return(TRUE);

break;
}

return(FALSE);
}

int FooExporter::DoExport(const TCHAR *pszName, ExpInterface *ei, Interface *gi, BOOL bSuppressPrompts, DWORD Options)
{
return(IMPEXP_SUCCESS);
}

Erst fragt Max mit ExtCount nach, wie viele Dateierweiterungsn überhaupt unterstützt werden, dann holt es sich für jede davon mit Ext die tatsächliche Erweiterung. Die Erweiterungen (in dem Fall nur eine) werden dann zusammen mit den Beschreibungen aus LongDesc und ShortDesc dem User zur Auswahl gestellt. Die Informationen aus AuthorName, CopyrightMessage, OtherMessage1 und OtherMessage2 werden einfach durchgereicht. Mit Version kommt noch eine Versionsnummer dazu, alles soweit recht selbsterklärend. ShowAbout ist auch noch da und bekommt als Parameter den Handle vom Parentwindow, dem sich die modale Infobox unterordnen soll, wenn der Exporter denn (auf Userwunsch aber erst) eine darstellen will.

Für Max relevant ist das Ergebnis von SupportsOptions, was zu jedem Format des Exporters abgefragt wird. In diesem Fall sagt er "kann ich nicht", wenn irgend ein anderes Bit als SCENE_EXPORT_SELECTED gesetzt ist, dieses eine aber kann er. Das hat zur Folge, daß Max diesen Exporter nicht nur im Exportdialog für die komplette Szene anbietet, sondern auch in "Export Selected". Dazu wird Max damit die Erlaubnis erteilt, beim Aufruf von DoExport diese Option zu nutzen.

Bisher war das Plugin rein passiv und durfte nichts anderes tun als Fragen zu beantworten. Aber nur durch so eine Auskunftsfreudigkeit hat der User die Chance, "Foo" aus dem Exportmenü auszuwählen. Tut er das und gibt eine passende Zieldatei an, dann ruft Max die Methode DoExport auf. Jetzt endlich kann das Exporterplugin aktiv werden und bekommt die Kontrolle. Nur was tun?

DoExport bekommt neben der Zieldatei, den gewünschten Optionen und möglicherweise dem Verbot, den User was zu fragen (z.B. wenn es per Stapelverarbeitung automatisch abgearbeitet werden soll), noch zwei sehr interessante Interfaces. Mit denen kann es jetzt selbst bei Max beliebige Fragen stellen, z.B. nach den Objekten in der Szene, bei den Objekten dann nach deren Meshes, eben alles was es exportieren will.

So, das war es erstmal. So funktionieren kleine Plugins für große Applikationen. Dabei sind Importer und Exporter noch die einfachsten Plugins, da sie keine eigenen Daten in der Szene hinterlegen müssen. Materialien, Modifier für die Geometriepipeline und eigentlich fast alle anderen Klassen an Plugins auch werden eine ganze Ecke komplexer, aber das Prinzip bleibt gleich.

rotalever
2007-08-15, 14:35:46
Wow, so eine lange Erklärung. Danke. Woher lernt man sowas? (außer in Foren)

Coda
2007-08-15, 14:40:35
Indem man die Max-SDK-Dokumentation liest...

Ectoplasma
2007-08-15, 15:38:39
@Neomi, kann 3D Studio Max denn auch mit verschiedenen Compilern umgehen. Ein VTable kann nämlich durchaus unterschiedlich aufgebaut sein. Eine C++ Plug-in Schnittstelle für verschiedene Compiler zu bauen, ist dann doch etwas komplizierter. Möglich ist es aber.

Coda
2007-08-15, 16:02:35
3D Studio Max ist Visual C++ only

Gast
2007-08-15, 16:24:29
3D Studio Max ist Visual C++ only

Ah danke. Finde ich persönlich etwas schwach. Aberr man kann nicht alles haben.

rotalever
2007-08-15, 16:31:11
Ah danke. Finde ich persönlich etwas schwach. Aberr man kann nicht alles haben.
Aber damit wird man doch wohl leben können. MSVC ist doch wohl kostenlos verfügbar.

Ectoplasma
2007-08-15, 16:49:58
Aber damit wird man doch wohl leben können. MSVC ist doch wohl kostenlos verfügbar.

Das kommt ganz darauf an, welche VC++ Version gemeint ist. Die Version 6.0 ist nicht kostenlos. Die kostet sogar richtig viel Geld.

Coda
2007-08-15, 16:53:06
2005 ist gemeint. Und Visual C++ 6 ist doch gar nicht mehr erhältlich.

TheGamer
2007-08-15, 18:26:14
Wow, so eine lange Erklärung. Danke. Woher lernt man sowas? (außer in Foren)


Was ist daran jetzt wow? Es gibt ne Doku von Autodesk wo das drinsteht :S

Neomi
2007-08-15, 21:04:16
Wow, so eine lange Erklärung. Danke. Woher lernt man sowas? (außer in Foren)

Das lernt man entweder aus Interesse oder Notwendigkeit, in dem Fall eher letzteres. Dann werden eben Dokus gelesen, Sachen ausgetestet, Dokus gelesen, Sachen getestet, ...

SDKs zu größeren Applikationen sind meistens mit dabei, im Fall Max ist es eine optional installierbare Komponente. Da hat man dann einen SDK-Ordner in dem man dann Header-Dateien, Libraries und die passenden Dokus findet, wie beim DirectX SDK auch. Nur kommt leider die Qualität der Doku nur in den allerwenigsten Fällen halbwegs an die von DirectX heran, zumindest in den Sachen die ich kenne.

Was ist daran jetzt wow? Es gibt ne Doku von Autodesk wo das drinsteht :S

Das war jetzt leicht modifizierter (Foo) und stark gekürzter (exportiert nix) Code aus einem Plugin, das ich vor einigen Jahren geschrieben habe und aus dem Gedächnis erklärt, aber letztendlich stimmt es. Alles das stand in irgendeiner Form natürlich auch in der Doku erklärt.

TheGamer
2007-08-15, 21:10:49
Das lernt man entweder aus Interesse oder Notwendigkeit, in dem Fall eher letzteres. Dann werden eben Dokus gelesen, Sachen ausgetestet, Dokus gelesen, Sachen getestet, ...

SDKs zu größeren Applikationen sind meistens mit dabei, im Fall Max ist es eine optional installierbare Komponente. Da hat man dann einen SDK-Ordner in dem man dann Header-Dateien, Libraries und die passenden Dokus findet, wie beim DirectX SDK auch. Nur kommt leider die Qualität der Doku nur in den allerwenigsten Fällen halbwegs an die von DirectX heran, zumindest in den Sachen die ich kenne.



Das war jetzt leicht modifizierter (Foo) und stark gekürzter (exportiert nix) Code aus einem Plugin, das ich vor einigen Jahren geschrieben habe und aus dem Gedächnis erklärt, aber letztendlich stimmt es. Alles das stand in irgendeiner Form natürlich auch in der Doku erklärt.

Ja ich kenn die Doku, so prall wie DX ist sie wahrlich nicht, aber esg eht man muss eben auch forschen sag ich mal, hab schon paar Exporter und Materialplugins fuer max hinter mich gebracht Entwicklungsmaessig fuer verschiedene Dinge

rotalever
2007-08-15, 21:16:51
Was ist daran jetzt wow? Es gibt ne Doku von Autodesk wo das drinsteht :S
Ja, nur das ich ersten nix von der Firma Autodesk habe und außerdem sollte es ausdrücken, dass ich mich über die ausführliche Antwort gefreut habe.

TheGamer
2007-08-15, 21:17:49
dass ich mich über die ausführliche Antwort gefreut habe.
Ich weiss :)

Neomi gibt auf jede Frage super antworten - damit es mal gesagt ist Neomi :)

Neomi
2007-08-15, 21:28:29
Ja, schlecht ist sie nicht, wobei ich bei einem BOOL (steckt ja auch nur ein int hinter), der bei den Werten 0, 1 und 2 jeweils eine andere Bedeutung hat, schon sehr überrascht war. Leider ist immer dann, wenn etwas gebraucht wird, keine Zeit für Forschung da, weil es automatisch auch dringend ist.

Neomi gibt auf jede Frage super antworten - damit es mal gesagt ist Neomi :)

Danke! :)