Softwarearchitektur Initialisierung Grafik API mit Handle
-
@Pixma und deine VulkanEngine.h hat keinen Includeguard
-
@manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:
@Pixma Ausgerechnet die problematischen Funktionen bleiben im Header?
lol, ich hab die problematischen Funktionen nun in die Cpp ausgelagert und die Fehler sind weniger geworden. Ich habe nun nurnoch zwei Fehler. Jedoch verstehe ich nicht, warum dies der Grund war.
Ich hab ja eigentlich nur den Code refactored und keine großen Änderungen vorgenommen.
Ich hab die Code Snippets in meinen vorherigen Post korrigiert.Nun hab ich noch Fehlermeldungen:
1>------ Build started: Project: Testbed, Configuration: Debug Win32 ------ 1>DirectXEngine.cpp 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *' 1> with 1> [ 1> _Ty=const DirectXEngine * 1> ] 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled 1> with 1> [ 1> _Ty=DirectXRenderer 1> ] 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled 1> with 1> [ 1> _Ty=DirectXRenderer 1> ] 1>c:\users\dennis\source\repos\testbed\testbed\directxengine.cpp(15): note: see reference to function template instantiation 'std::shared_ptr<DirectXRenderer> std::make_shared<DirectXRenderer,const DirectXEngine*>(const DirectXEngine *&&)' being compiled 1>DirectXRenderer.cpp 1>Testbed.cpp 1>VulkanEngine.cpp 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'VulkanRenderer::VulkanRenderer(VulkanRenderer &&)': cannot convert argument 1 from '_Ty' to 'VulkanEngine *' 1> with 1> [ 1> _Ty=const VulkanEngine * 1> ] 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const VulkanEngine*>(const VulkanEngine *&&)' being compiled 1> with 1> [ 1> _Ty=VulkanRenderer 1> ] 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const VulkanEngine*>(const VulkanEngine *&&)' being compiled 1> with 1> [ 1> _Ty=VulkanRenderer 1> ] 1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.cpp(15): note: see reference to function template instantiation 'std::shared_ptr<VulkanRenderer> std::make_shared<VulkanRenderer,const VulkanEngine*>(const VulkanEngine *&&)' being compiled 1>Generating Code... 1>Done building project "Testbed.vcxproj" -- FAILED. ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
DIe Fehlermeldungen liegen aber irgendwie mit dem memory file zusammen. Kann es sein, dass meine Smart Pointer noch nicht stimmen?
@Schlangenmensch sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:
@Pixma und deine VulkanEngine.h hat keinen Includeguard
ich hab ihn nun mit kopiert. Den hatte ich beim kopieren aus meiner Solution übersehen.
Danke für den Hinweis
-
@Pixma das ist dann wohl der erwartete const-Fehler
-
@Pixma Um die Antwort von @manni66 zu erweitern:
in
std::shared_ptr<IRenderer> DirectXEngine::create_renderer() const { return directxrenderer_ptr{ std::make_shared<DirectXRenderer>(this) }; }
ist der
this
Pointer const. Du übergibst also einconst DirectXEngine*
DirectXRenderer erwartet aberDirectXEngine*
Das sagt dir auch die Compiler Fehlermeldung:
'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
1> with
1> [
1> _Ty=const DirectXEngine *
1> ]
-
@Schlangenmensch sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:
@Pixma Um die Antwort von @manni66 zu erweitern:
in
std::shared_ptr<IRenderer> DirectXEngine::create_renderer() const { return directxrenderer_ptr{ std::make_shared<DirectXRenderer>(this) }; }
ist der
this
Pointer const. Du übergibst also einconst DirectXEngine*
DirectXRenderer erwartet aberDirectXEngine*
Das sagt dir auch die Compiler Fehlermeldung:
'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
1> with
1> [
1> _Ty=const DirectXEngine *
1> ]Danke für deinen Hinweis. Das hab ich irgendwie garnicht gecheckt, dass das Problem das const ist. Ich hab das const entfernt und nun läuft es.
Ich habe das Listing in meinen Post nochmals angepasst.
Allen anderen auch.
Ich merke, dass ich doch noch viel zu lernen hab.
Ich will mir auf jedenfall nochmal das Thema Smart Pointer genauer anschauen und Design Patterns.
-
@Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:
So wenn ich deine Erklärung der Handles richtig verstanden hab, machen Handles dann mehr Sinn für verschiedene Objekte die im Speicher verwaltet werden.
Handles machen z.B. Sinn:
- Wenn man die eigene "Komponente" (Library, Module - wie auch immer du es nennen willst) vor falschem Input schützen muss. z.B. zwischen Kernel und Usermode. Der Kernel darf niemals seine eigenen Datenstrukturen kaputt machen, nur weil er z.B. einen falschen Zeiger vom Programm bekommen hat. Also verwendet er für seine Objekte Handles. Das Programm bekommt nur das Handle und der Kernel kann dann prüfen ob das Handle "valid" ist. Wenn nicht kann er einen Fehler zurückgeben anstatt irgendeinen Speicherbereich zu überschreiben.
- Wenn man gar keine normalen Zeiger hergeben kann, weil der der das Objekt verwenden möchte gar keinen Zugriff auf den Speicher hat wo das eigentliche Objekt liegt. Beispielsweise hast du keinen direkten Zugriff auf Kernel-Speicher, Speicher der Grafikkarte oder Speicher auf einem Remote-System mit dem du über RPC kommunizierst. Also werden dort Handles verwendet.
- Wenn man die eigentlichen Objekte im Speicher herumschieben möchte. Das wird in der Spieleprogrammierung soweit ich weiss öfters verwendet, damit man Objekte gleichen Typs in einem kompakten Array halten kann. Hat mit Caching zu tun - Daten aus einen zusammenhängenden Speicherbereich zu lesen oder modifizieren geht schneller als wenn man die selbe Anzahl Bytes an zigtausend Stellen verstreut im Speicher liest/modifiziert.
- Wenn man die eigentlichen Objekte evtl. löschen möchte bevor das Programm das sie erzeugt hat sie explizit freigibt. Beispiel dafür wäre Tracing - also wenn man Daten über bestimmte Prozesse/die Ausführung eines Programms sammelt. Dabei kann es sinnvoll sein bestimmte Traces vorzeitig zu verwerfen - z.B. wenn sie zu alt sind und die Vermutung besteht dass das Programm einfach "vergessen" hat den Trace zu beenden.
Jedes Objekt zum Beispiel ein Mesh oder eine Textur bekommt, dann ein Handle zugewiesen. Verwaltet wird das ganze dann über eine Handle Tabelle, wo jedes Objekt einem Handle zugeordnet werden kann.
Würde ich nicht so machen. Wozu? Handles sind nichts was man einfach so als "best practice" überall drüberschütten sollte wo man es drüberschütten kann. Wenn du keinen konkreten Grund hast, dann lass es bleiben. Kostet nur unnötig Performance und macht den Code unnötig komplizierter. Und du willst BTW normalerweise auch gar nicht dass du grundverschiedene Objekte wie Texturen und Meshes über den selben Datentyp ansprichst. D.h. wenn du da aus irgend einem (guten) Grund Handles verwenden willst, dann solltest du verschiedene Handle-Typen dafür machen. Also z.B. eine kleine Klasse die nur einen Integer mit den Handle-Wert enthält. Im Programm sollte dann nur diese Klasse verwendet werden (=niemals der Integer-Wert direkt), damit das ganze type-safe bleibt.
-
@hustbaer
ah, ok danke für deine Erklärung.
Das hat mir enorm weiter geholfen, ich hab mich auch nochmal näher mit Handles nun beschäftigt.
Was mir nun klar wurde, so wie ich Handles verstanden und verwendet habe ist einfach nur falsch.
Ein Handle ist wohl kein Pointer, sondern nur ein Index auf eine Handletable.Zu deinem zweiten und dritten Punkt:
@hustbaer sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:- Wenn man gar keine normalen Zeiger hergeben kann, weil der der das Objekt verwenden möchte gar keinen Zugriff auf den Speicher hat wo das eigentliche Objekt liegt. Beispielsweise hast du keinen direkten Zugriff auf Kernel-Speicher, Speicher der Grafikkarte oder Speicher auf einem Remote-System mit dem du über RPC kommunizierst. Also werden dort Handles verwendet.
- Wenn man die eigentlichen Objekte im Speicher herumschieben möchte. Das wird in der Spieleprogrammierung soweit ich weiss öfters verwendet, damit man Objekte gleichen Typs in einem kompakten Array halten kann. Hat mit Caching zu tun - Daten aus einen zusammenhängenden Speicherbereich zu lesen oder modifizieren geht schneller als wenn man die selbe Anzahl Bytes an zigtausend Stellen verstreut im Speicher liest/modifiziert.
Wenn ich das so richtig verstehe, verwendet man Handles nur dazu in Kombination mit Handle Tables um verwendeten eigenen Speicher zu verwalten. Das ist ja auch das was du glaube mit den zwei Punkten ausdrücken wolltest, oder? Also Memory Allocation Strategien, um belegten Speicher zu defragmentieren zum Beispiel.
Ich hab mir da noch ein YouTube Video angeschaut, wo Handles und Handle Tables näher erläutert werden:
https://www.youtube.com/watch?v=Qsx5MYV985A
-
@Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:
Ein Handle ist wohl kein Pointer, sondern nur ein Index auf eine Handletable.
Das ist eher ein Implementierungsdetail, ich würde mich da nicht so daran aufhängen.
-
@Mechanics
Ja, stimmt schon.
Der Knackpunkt ist auf jeden Fall dass wenn man "Handle" sagt es üblicherweise keine Garantie gibt dass es ein Zeiger ist. Weil man sonst halt auch einfach gleich Zeiger sagen könnteOb diese Handlewerte/Zeigerwerte dann z.B. noch in einer Map/einem Table stehen, so dass man prüfen kann ob sie gültig sind, ist ein Implementierungsdetail.
Worauf ich einfach hinaus wollte ist, dass es in C++ kaum Sinn macht nackte Zeiger als Handles zu verwenden. Man gewinnt nichts dadurch. Wenn der einzige Grund ist dass man das Layout des Objekts vor dem der es verwendet "verstecken" will, dann kann man das auch anders erreichen. z.B. indem man nur eine forward-declaration für alle Klassen in den öffentlichen Header-Files anbietet. Dann kann man weiter Zeigern arbeiten, muss aber nicht dauernd rumcasten.
Man kann das dann natürlich immer noch als "Handle" bezeichnen, nur ist es mMn. sinnfrei das zu machen. Denn wenn ich weiss dass es ein Zeiger ist, dann verwende ich gleich den Begriff Zeiger und nicht den weniger spezifischen Begriff "Handle".
-
@hustbaer
Ich fand deine Erklärung gut und verständlich.
Hatte nach der Antwort von Pixma aber das Gefühl, dass er das als die einzige mögliche Definition von Handle wahrnimmt und wollte nochmal darauf hinweisen.
-
@Mechanics
OK, dankeWobei "Handle" schon ein blödes Wort ist. Grundsätzlich ist ein Handle ja einfach nur etwas mit dem man ein Objekt referenzieren/identifizieren kann -- egal wie. Sogesehen ist jeder Zeiger konzeptuell auch ein Handle.
Wenn man aber davon spricht dass man Handles "statt" Zeigern verwendet, dann ist normalerweise etwas in der Art gemeint was ich versucht habe zu erklären.
Die numerischen Werte von Handle und Zeiger können dann natürlich immer noch übereinstimmen (wie bei Windows HMODULE z.B.), aber es muss noch mehr dran sein. Weil "Handle statt Zeiger" sonst keinen Sinn machen würde. (Etwas mehr wie bei Windows HMODULE z.B. dass die OS Funktionen denen man ein HMODULE geben kann nicht crashen wenn man ihnen eine Hausnummer gibt, weil sie vorher prüfen ob das "Handle" das sie da bekommen haben gültig ist.)
-
Hallo @Mechanics und @hustbaer
vielen Dank für dein Hinweis.
Ich denke, ich hab nun verstanden was Handles sind und wann man die verwendet.
Danke für eure Erklärungen, das hat mir wirklich enorm geholfen.