Code beim Starten ausführen

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2395
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Code beim Starten ausführen

Beitrag von Jonathan »

Guten Abend, ihr C++ Profis :D

Ich möchte sicherstellen, dass ein wenig Code beim Starten des Programmes automatisch ausgeführt wird. Aktuell habe ich 2 Probleme, die man damit sicherlich schön lösen könnte, aber ich würde gerne auch eine Art Überblick über die ganze Thematik haben. Konkret:

1) gl3w lädt OpelGL Extensions. Da gibt es diese gl3wInit() die sehr viele globale Funktionszeiger setzt, das sollte also unabhängig von einem Initialisierten Ogl Kontext sein. Ich muss jetzt also sicherstellen, dass diese Funktion aufgerufen wurde, bevor irgendeine OpenGL Funktion aufgerufen wird. Darüber hinaus ist gl3w Teil einer Bibliothek und ich möchte nicht in jedem Programm, dass sie benutzt, diese Funktion irgendwo selber aufrufen müssen, es soll automatisch geschehen, wenn gl3w benutzt (->inkludiert) wird.

2) Ich habe Image Klasse und eine ganze Reihe an Loadern, die Image Objekte aus verschiedenen Daten konstruieren. Ich würde jetzt gerne einen allgemeinen Loader haben, indem sich alle speziellen registrieren (inklusiv Test, ob dieser spezielle Loader eine Datei lesen kann) und der dann sämtliche unterstütze Dateien laden kann.

Das erste sollte relativ einfach mit einem einzelnen, Globalen Objekt gehen, dass im C-Tor halt die Funktion aufruft. Das zweite Problem ist schon etwas kniffeliger, da ja die Reihenfolge, in der die Objekte erstellt werden, definiert sein muss. Ich habe hier in einem Thread mal gelesen, dass man das schon ziemlich gut machen kann, aber leider weiß ich nicht, wie. Und trial&error find ich doof, nach Möglichkeit sollte es nicht nur funktionieren sondern standardkonform sein und garantiert funktionieren.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von dot »

Jonathan hat geschrieben:1) gl3w lädt OpelGL Extensions. Da gibt es diese gl3wInit() die sehr viele globale Funktionszeiger setzt, das sollte also unabhängig von einem Initialisierten Ogl Kontext sein.
Nope, du musst gl3wInit() aufrufen, nachdem ein OpenGL Kontext erzeugt wurde...
Jonathan hat geschrieben:2) Ich habe Image Klasse und eine ganze Reihe an Loadern, die Image Objekte aus verschiedenen Daten konstruieren. Ich würde jetzt gerne einen allgemeinen Loader haben, indem sich alle speziellen registrieren (inklusiv Test, ob dieser spezielle Loader eine Datei lesen kann) und der dann sämtliche unterstütze Dateien laden kann.
Wo ist das Problem? Mach dir halt so ein allgemeines Loader Objekt, das dann im Konstruktor seine ganzen Unterloader initialisiert!?
Benutzeravatar
Jonathan
Establishment
Beiträge: 2395
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von Jonathan »

Zu 2): Das Projekt benutzt CMake und man kann auswählen, welche Loader kompiliert werden sollen und welche nicht. Früher hab ich mit defines gearbeitet, die das im Code geregelt haben. Leider gab es irgendwann Probleme, als bei einer etwas komplexeren Projektstruktur durch einen Bug im CMake Script die defines mal da waren und mal nicht, was zu nicht debugbaren Crashes beim starten führte. Da ich ein robustes System haben wollte, sind also alle defines rausgeflogen und die CMake Flags regeln nur noch, ob die Dateien kompiliert werden, oder eben nicht. Daher möchte ich jetzt, dass ein Loader, wenn seine cpp-Datei kompiliert wird, sich automatisch irgendwo registriert.

Zu 1): Hm, ok, das steht sogar in der Doku. Allerdings würde mich interessiere, warum das so ist. Ich habe Dlls noch nie wirklich von Hand geladen, aber wenn man sich den Code anschaut, sucht er nur die Dll und setzt die Funktionszeiger. Wofür braucht man da den Kontext?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von dot »

Jonathan hat geschrieben:Zu 2): Das Projekt benutzt CMake und man kann auswählen, welche Loader kompiliert werden sollen und welche nicht. Früher hab ich mit defines gearbeitet, die das im Code geregelt haben. Leider gab es irgendwann Probleme, als bei einer etwas komplexeren Projektstruktur durch einen Bug im CMake Script die defines mal da waren und mal nicht, was zu nicht debugbaren Crashes beim starten führte. Da ich ein robustes System haben wollte, sind also alle defines rausgeflogen und die CMake Flags regeln nur noch, ob die Dateien kompiliert werden, oder eben nicht. Daher möchte ich jetzt, dass ein Loader, wenn seine cpp-Datei kompiliert wird, sich automatisch irgendwo registriert.
Dann mach in jedes cpp File eine globale Variable, die in ihrem Konstruktor den Loader registriert...
Jonathan hat geschrieben:Zu 1): Hm, ok, das steht sogar in der Doku. Allerdings würde mich interessiere, warum das so ist. Ich habe Dlls noch nie wirklich von Hand geladen, aber wenn man sich den Code anschaut, sucht er nur die Dll und setzt die Funktionszeiger. Wofür braucht man da den Kontext?
Weil der Kontext an eine bestimmte Grafikkarte gebunden ist und auf einem System theoretisch mehrere Grafikkarten vorhanden sein könnten, die alle verschiedene Treiber haben...
Florian Keßeler
Beiträge: 75
Registriert: 24.07.2002, 00:00
Wohnort: Bremen
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von Florian Keßeler »

dot hat geschrieben: Dann mach in jedes cpp File eine globale Variable, die in ihrem Konstruktor den Loader registriert...
Dann pass aber auf, dass auch garantiert alles, was zum Registrieren benötigt wird vorher auch wirklich initialisiert ist. Die Reihenfolge vor allem über mehrere Übersetzungseinheiten dürfte nicht definiert sein.

Ich würde eher versuchen, die Registrierung statisch hinzubekommen. Notfalls von CMake einen kleinen eigenen Präprozessor aufrufen lassen, das dir eine Sourcedatei erzeugt, in der alle Loader in einem statischen Array o.ä. stehen...
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von dot »

Florian Keßeler hat geschrieben:
dot hat geschrieben: Dann mach in jedes cpp File eine globale Variable, die in ihrem Konstruktor den Loader registriert...
Dann pass aber auf, dass auch garantiert alles, was zum Registrieren benötigt wird vorher auch wirklich initialisiert ist. Die Reihenfolge vor allem über mehrere Übersetzungseinheiten dürfte nicht definiert sein.
Korrekt, das Problem lässt sich aber ziemlich einfach lösen:

Code: Alles auswählen

Loader& getLoader()
{
  static Loader loader;
  return loader;
}
Auch wenn immer noch keine bestimmte Reihenfolge garantiert ist, ist nun auf jeden Fall immer garantiert, dass der Loader konstruiert wird, bevor er verwendet wird...
Benutzeravatar
Jonathan
Establishment
Beiträge: 2395
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von Jonathan »

dot hat geschrieben: Weil der Kontext an eine bestimmte Grafikkarte gebunden ist und auf einem System theoretisch mehrere Grafikkarten vorhanden sein könnten, die alle verschiedene Treiber haben...
Irgendwie sehe ich immer noch keinen zwingenden Grund. Ich lade doch zunächst nur Adressen aus einer dll. Klar könnten unterschiedliche Treiber unterschiedliche dlls haben, aber gl3w macht einfach ein "libgl= LoadLibraryA("opengl32.dll");" Solange nicht bei der Kontexterstellung irgendeine Umgebungsvariable geändert wird, sollte das doch immer genau die gleiche dll laden. Und die Funktionszeiger die man lädt sind doch pro dll "konstant", die Funktionen stehen doch nicht an einer anderen Stelle, bloß weil ein Kontext erstellt wurde. Das einzige Problem, dass ich sehe ist, dass die Version automatisch abgefragt wird. Laut Internet schaut es aber so aus, als würden gl-Aufrufe außerhalb eines Kontextes einfach gar nichts tun, also ist alles wunderbar definiert. Und die Version kann ich ja nach der Kontexterstellung immer noch abfragen, wenn ich möchte.

Nochmal zurück zum eigentlichen Thema: Ist folgendes korrekt? Innerhalb einer Übersetzungseinheit werden globale Objekte in der Reihenfolge ihrer 'Definierung' initialisiert. Über mehrere Übersetzungseinheiten hinweg hat man keinerlei Garantien, in welcher Reihenfolge die globalen Objekte initialisiert werden. Statische, lokale Variablen werden genau beim ersten Aufruf ihrer Funktion initialisiert. Zerstört werden die Objekte immer in genau umgekehrter Reihenfolge (auch statische Variablen?)
Wenn ich also Initialisierungsabhängigkeiten habe müssen die untersten Objekte globale Variablen sein, alle darüber liegenden müssen statische Variablen in Funktionen sein?

Wo wir gerade dabei sind: Verursachen statische Variablen nicht einen Overhead, wenn sie nur beim ersten mal initialisiert werden?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von CodingCat »

Jonathan hat geschrieben:Nochmal zurück zum eigentlichen Thema: Ist folgendes korrekt? Innerhalb einer Übersetzungseinheit werden globale Objekte in der Reihenfolge ihrer 'Definierung' initialisiert. Über mehrere Übersetzungseinheiten hinweg hat man keinerlei Garantien, in welcher Reihenfolge die globalen Objekte initialisiert werden. Statische, lokale Variablen werden genau beim ersten Aufruf ihrer Funktion initialisiert. Zerstört werden die Objekte immer in genau umgekehrter Reihenfolge (auch statische Variablen?)
Wenn ich also Initialisierungsabhängigkeiten habe müssen die untersten Objekte globale Variablen sein, alle darüber liegenden müssen statische Variablen in Funktionen sein?
Genau. Insbesondere müssen globale Variablen, die bei ihrer Zerstörung auf statische lokale Variablen in anderen Funktionen zugreifen, deshalb sicherstellen, dass diese bereits vor der globalen Variable fertig konstruiert waren (d.h. alles, was bei der Zerstörung einer globalen Variable referenziert wird, sollte auch bei der Konstruktion der globalen Variable referenziert worden sein).
Jonathan hat geschrieben:Wo wir gerade dabei sind: Verursachen statische Variablen nicht einen Overhead, wenn sie nur beim ersten mal initialisiert werden?
Klar, mindestens ein if. In C++11 wird sogar Thread-Sicherheit garantiert, also vermutlich irgendein atomarer Zugriff, je nach Implementierung auch ein Lock (Locks sollten sich mit etwas geschickterer Code-Generierung vermeiden lassen).
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von dot »

Jonathan hat geschrieben:
dot hat geschrieben: Weil der Kontext an eine bestimmte Grafikkarte gebunden ist und auf einem System theoretisch mehrere Grafikkarten vorhanden sein könnten, die alle verschiedene Treiber haben...
Irgendwie sehe ich immer noch keinen zwingenden Grund. Ich lade doch zunächst nur Adressen aus einer dll. Klar könnten unterschiedliche Treiber unterschiedliche dlls haben, aber gl3w macht einfach ein "libgl= LoadLibraryA("opengl32.dll");" Solange nicht bei der Kontexterstellung irgendeine Umgebungsvariable geändert wird, sollte das doch immer genau die gleiche dll laden. Und die Funktionszeiger die man lädt sind doch pro dll "konstant", die Funktionen stehen doch nicht an einer anderen Stelle, bloß weil ein Kontext erstellt wurde. Das einzige Problem, dass ich sehe ist, dass die Version automatisch abgefragt wird. Laut Internet schaut es aber so aus, als würden gl-Aufrufe außerhalb eines Kontextes einfach gar nichts tun, also ist alles wunderbar definiert. Und die Version kann ich ja nach der Kontexterstellung immer noch abfragen, wenn ich möchte.
gl3w lädt nicht einfach nur die opengl32.dll. Die opengl32.dll enthält die OpenGL Runtime, welche sich unter anderem darum kümmert, den passenden ICD (Installable Client Driver, bei NVIDIA wäre das im Moment z.B. die nvoglv64.dll bzw nvoglv32.dll) zu laden, der die eigentliche OpenGL Implementierung enthält. Jeder OpenGL Context kann theoretisch völlig verschiedene Versionen der OpenGL Funktionen verwenden, je nach Grafikkarte, OpenGL Version etc. Falls du dich jetzt fragst, ob die gängige Praxis, die Funktionen einfach in globale Funktionspointer zu laden, wie es praktisch jede Library macht, dann nicht eigentlich völliger Mist ist, die Antwort lautet: Ja. Sobald du mehr als einen OpenGL Kontext in deiner Anwendung hast, bricht mit diesen Libraries potentiell alles zusammen (GLEW bietet iirc als einzige mir bekannte Library eine Möglichkeit, um mit mehreren Kontexten umzugehen, das muss aber beim Kompilieren von GLEW erst speziell aktiviert werden und abgesehen davon, war GLEW, zumindest als ich das letzte Mal nachgeschaut hab, mit OpenGL 3+ leider immer noch inkompatibel)...

In einem ernsthaften Projekt würde ich persönlich GLEW, gl3w, GLUT und den ganzen Kram jedenfalls niemals verwenden.

Die andere Frage hat Cat ja schon beantwortet.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2395
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von Jonathan »

Ok, ich habe jetzt also verschiedene Loader und einen LoaderManager. Die Loader haben jeweils eine globale Variable, die den Loader beim Manager registrieren soll. Ich möchte halt, dass ich einfach nur die Datei kompilieren muss und ohne weiteres zutun der Loader registreirt wird.
Leider werden aber nicht immer alle globalen Objekte überhaupt erstellt. Scheinbar wird da so einiges rausoptimiert. Interessanterweise wird von den momentan 2 vorhandenen Loadern nur einer beim Start registriert, der andere nur dann, wenn ich tatsächlich einmal irgendwann etwas mit dem globalen Objekt mache.
Gibt es irgendeine Möglichkeit, zu erzwingen, dass die Objekte absolut immer erstellt werden? Denn wenn dieser Mechanismus nicht zuverlässig funktioniert, ist er halt für mich komplett unbrauchbar, dann Liste ich die irgendwo manuell auf oder bastel mir vielleicht irgendwas in CMake (obwohl ich das Buil-System lieber so simpel wie möglich halten würde).
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von CodingCat »

Sicher, dass es an der Optimierung liegt? Prinzipiell sind Compiler dazu verpflichtet, globale Variablen mit nebenwirkungsbehafteter Initialisierung immer zu initialisieren, auch wenn sie vom restlichen Code niemals referenziert werden.
C++ 11 § 3.6.2 hat geschrieben:34) A non-local variable with static storage duration having initialization with side-effects must be initialized even if it is not odr-used (3.2, 3.7.1).
Mit welchem Compiler hast du das Verhalten denn beobachtet. Könntest du die wichtigsten Codeausschnitte deines Mechanismus hier zeigen?
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von CodingCat »

Krishty hat mich gerade darauf hingewiesen, dass die Sache anders aussehen kann, wenn die entsprechenden Globals ausgelagert in Bibliotheken liegen. In DLLs hat es deshalb bisweilen diese ominöse COM-Init-Funktion, deren einzige Wirkung die Schaffung einer Bibliotheksabhängigkeit durch die Referenzierung beim Aufruf ist. Wie das mit statischen Bibliotheken aussieht, kann ich gerade nicht sicher sagen. Sollte die Referenzierungsanalyse dort feingranularer durchgeführt werden, z.B. auf Basis einzelner Übersetzungseinheiten, so hättest du hier tatsächlich ein Problem.

Nachtrag: Krishty meint, bei LIBs würde tatsächlich jede enthaltene Übersetzungseinheit einzeln dazugelinkt oder wegen Nichtrefrenzierung entfernt. Keine guten Voraussetzungen für automatische Registrierung also.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Code beim Starten ausführen

Beitrag von Krishty »

seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten