#1 24.10.2007 19:03:54

bjoern
ProMember
Ort: OBK
Registriert: 18.07.2006
Beiträge: 112
Web-Seite

DLLs und anderes...

Hallo Leute tongue

Möchte jetzt einen richtigen Engine programmieren, gehe dabei nach dem Buch "3D-Spielprogrammierung" vor.

Das Prinzip ist (kurz) folgendes:
- Man hat ein Interface, also das, was der Engine bietet
- Die Dll-Unit implementiert dieses, und exportiert eine Funktion, die einen Zeiger auf ein neu angelegtes Objekt
  des abgeleiteten Typs zurückgibt
- Eine Unit stellt quasi die Benutzung der DLL zur Verfügung, d.h. sie ruft obige Funktion auf und gibt den
  Zeiger weiter
- Die eigentliche Anwendung kann dann über diesen Zeiger auf das angelegte Engine-Objekt zugreifen.

Verwirrend, hier meine Codes:

Das Interface, die Methode INIT soll also z.B. mal eine RenderHauptMethode werden o.ä.

Code:

unit S7Engine_IRenderDevice;

interface

type
  S7_IRenderDevice = class
    public
      procedure Init(); virtual; abstract;
  end;

implementation

end.

Die DLL:

Code:

library S7Engine_D3D;

{ Wichtiger Hinweis zur DLL-Speicherverwaltung: ShareMem muss sich in der
  ersten Unit der unit-Klausel der Bibliothek und des Projekts befinden (Projekt-
  Quelltext anzeigen), falls die DLL Prozeduren oder Funktionen exportiert, die
  Strings als Parameter oder Funktionsergebnisse übergeben. Das gilt für alle
  Strings, die von oder an die DLL übergeben werden -- sogar für diejenigen, die
  sich in Records und Klassen befinden. Sharemem ist die Schnittstellen-Unit zur
  Verwaltungs-DLL für gemeinsame Speicherzugriffe, BORLNDMM.DLL.
  Um die Verwendung von BORLNDMM.DLL zu vermeiden, können Sie String-
  Informationen als PChar- oder ShortString-Parameter übergeben. }
  

uses
  SysUtils,
  Classes,
  S7Engine_IRenderDevice;

{$R *.res}

type
  S7_D3D = class(S7_IRenderDevice)
  public
    constructor create();
    procedure Init(); override;
  end;

constructor S7_D3D.create();
begin
  //
end;

procedure S7_D3D.Init();
var
  f:TextFile;
begin
  assignfile(f,'C:\test.txt');
  rewrite(f);
  writeln(f,'otto');
  closefile(f);
end;

function CreateRenderDevice(): S7_IRenderDevice; stdcall;
begin
   result := S7_D3D.create();
end;


exports
  CreateRenderDevice;

begin
end.

Die Unit zum Benutzen der DLL:

Code:

unit S7Engine_Renderer;

interface

uses S7Engine_IRenderDevice, Windows, SysUtils;

type
  S7_Renderer = class
    public
      function CreateDevice(): boolean;
      function GetDevice(): S7_IRenderDevice;
    private
      Device: S7_IRenderDevice;
  end;

  type
    TCreateRenderDevice = function(): S7_IRenderDevice; stdcall;

implementation

function S7_Renderer.CreateDevice(): boolean;
var
  Handle: THandle;
  CreateRenderDevice: TCreateRenderDevice;
begin
  result := true;

  Handle := LoadLibrary(PChar(ExtractFilePath(ParamStr(0))+'S7Engine_D3D.dll'));
  if Handle <> 0 then begin
    @CreateRenderDevice := GetProcAddress(Handle, 'CreateRenderDevice');
    if @CreateRenderDevice <> nil then begin
      Device := CreateRenderDevice();
    end else Result := false;
  end else Result := false;

  FreeLibrary(Handle);
end;

function S7_Renderer.GetDevice(): S7_IRenderDevice;
begin
  result := Device;
end;


end.

Ein Beispielprogramm:

Code:

unit CIM_Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, S7Engine_Renderer;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  My_S7_Renderer: S7_Renderer;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin

  My_S7_Renderer := S7_Renderer.Create;
  if not My_S7_Renderer.CreateDevice then Application.MessageBox('Error','Error');
  My_S7_Renderer.GetDevice.Init();

end;

end.

Hoffe es ist so verständlich.... Nun meine Fragen:

- Warum gibt es eine Zugriffsverletzung bei Adresse 00452465 in Modul "Cim.exe". Lesen von Adresse 00BE6800.?
Der Fehler tritt auf bei  My_S7_Renderer.GetDevice.Init();, sonst gäbs ja auch das Errorfenster. Er scheint also
Die Prozedur zu finden, aber der Zugriff auf Init klappt nicht.
Die Methode CreateRenderDevice funzt auch, habe da mal provisorisch eine Ausgabe auf test.txt gemacht... die gabs dann.

- Pointer?!
Ich programmiere jetzt viel c++. Wie ist es eigentlich bei Delphi:
Werden Methodenaufrufe call by value oder call by reference übergeben?
procedure Test(a: MyClass);
Ist a eine Kopie? Wenn ja dann sind meine Programme ja langsam ohne dass ich das wusste ... big_smile
Wenn ich mit var arbeite (procedure Test(var i: integer)) kann ich ja die Variable verändern,
da muss es also Call by reference sein, oder? Also sollte ich generell mit Var arbeiten, auch wenn ich das Objekt nicht verändere?!

- Pointer die 2.
Kann ich in Delphi auch Pointer auf Klassen haben?
Wollte soetwas:
procedure CreateRenderDevice(Device: ^S7_IRenderDevice);

aber das ging nicht... lies sich nicht kompilieren. Müsste doch aber eigentlich?

- letzte Frage
Was haltet ihr von dieser Implementation allgemein? D.h. gut für einen Engine?


Wer bis hier gelesen hat vielen Dank ! wink
Björn

Offline

 

#2 25.10.2007 06:29:16

JorEl
ExtremeMember
Registriert: 29.01.2005
Beiträge: 894

Re: DLLs und anderes...

Zitat:

Werden Methodenaufrufe call by value oder call by reference übergeben?

Das hängt von der Funktionsdeklaration und vom Datentyp ab. Wenn du zb procedure A(const x : Integer) verwendest wird nur die Referenz übergeben, wenn du statt const var benutzt wird ebenfalls die Referenz übergeben aber mit dem Unterschied, dass keine Konstanten erlaubt sind und du die Variable auch selbst setzen darfst. Verwendest du garnichts, so (und da bin ich jetzt allerdings nicht ganz sicher) wird der Inhalt eifnach kopiert. Der grössere Unterschied zu C++ ist allerdings, dass die ersten drei Parameter prinzipiell in Registern übergeben werden während C++ immer auf den stack pushed. Ausserdem werden die Parameter soweit ich weiss in umgekehrter Reihenfolge übergeben, d.h. wenn du Kompatiblität zu den C++ calling conventions haben willst solltest du für die dll immer stdcall verwenden.

Zitat:

Kann ich in Delphi auch Pointer auf Klassen haben?

Klar, du musst dann den pointer nur wieder auf den Klassentyp casten um darauf zugreifen zu können. procedure CreateRenderDevice(Device: ^S7_IRenderDevice); macht allerdings nicht wirklich Sinn, ^ ist der Dereferenzierungsoperator, was sollte der in dieser Form bewirken? Eine Klasse ist schon eine Referenz, sie zu dereferenzieren ist nicht sinnvoll. D.h. wenn du zb einen pointer p hast kannst du via p := myclassinstance diesem die Referenz der Klasse zuweisen. Zugriff auf die Instanz bekommst du dann mit TMyClassInstance(p).

Zitat:

Was haltet ihr von dieser Implementation allgemein? D.h. gut für einen Engine?

Das ist jetzt nur meine Meinung - und da gibt es durchaus auch Gegenargumente, aber: nein, nicht gut. smile Du siehst ja, dass du schon am Anfang Schwierigkeiten damit bekommst, stell dir mal vor wie toll das bei komplexeren Situationen zu debuggen ist. Ich hab in einer alten Engine mal das particle system in Form von dll's implementiert und das war einfach nur die Hölle. Zwar schön wenn es so modular ist, aber das war es meiner Meinung nach nicht wert, die Zeit die dabei sinnlos mit Fehlersuche verschwendet wurde hätte ich weitaus besser nutzen können. Eine komplette engine auf dll Basis halte ich für unrealisierbar, vor allem sehe ich auch keinen wesentlichen Vorteil, es seidenn du willst unbedingt support für Delphi und C++, aber auch dann müsstest du zumindest sämtliche Deklarationen für beide Sprachen schreiben. Es gibt kaum etwas das so komplex ist wie eine vernünftige 3D Engine, da sollte man es sich nicht noch unnötig schwer machen indem man dll's verwendet.

JorEl


Jesus hat gesagt - selig sind die, die da Leid erfahren, denn sie sollen getröstet werden... Ford Prefect hat gesagt - es ist unheimlich wichtig, dass wir miteinander reden und einen trinken.

Offline

 

#3 25.10.2007 10:07:48

Neo
Member
Ort: Erfurt/Thüringen
Registriert: 31.01.2005
Beiträge: 78
Web-Seite

Re: DLLs und anderes...

Wenn du DLLs nutzt um eine Kompatibilität zwischen verschiedenen Sprachen zu erreichen, dann solltest du dir zudem Interfaces und das COM anschauen, das ist "professioneller" als abstrakte Klassen. Anonsten stimme ich mit JorEl überein. Eine Engine in DLL-Form bringt ansich keine Vorteile. Ohne DLLs hast du zudem Vorteile was die Optimierung angeht, da hier Delphi für dich unrelevanten Code je nach Projekt automatisch rausoptimiert. Weiterhin ist es häufig so, das eine Engine meist mit dem Projekt weiterentwickelt wird, wodurch du sowieso ständig sich ändernde Engines hast (wenn auch nur in kleiner Form).

Neo

Offline

 

#4 25.10.2007 13:45:41

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: DLLs und anderes...

[quote=JorEl][...]
Verwendest du garnichts, so (und da bin ich jetzt allerdings nicht ganz sicher) wird der Inhalt eifnach kopiert.
[...]
JorEl

Das würde ich bestätigen, so habe ich das gelernt. Ich möchte hier noch was anmerken. Du (JorEl) hast es teilweise bereits gesagt. In einer Variable einer Klasse wird nur der Pointer auf die Daten der Instanz gespeichert. D.H. wenn ein Parameter einer Funktion z.B. myClass:TKlasse ist, dann ist das zwar nur eine Kopie, da es sich aber auch nur um einen Pointer handelt, wird keine Kopie der Instanz der Klasse angelegt. Und so viel Speicher ist ein Pointer nun auch wieder nicht.
Das hat aber noch eine zweite Konsequenz:
Nach meinen Informationen legt C++ für jede Instanz einer Klasse alles an, also Daten+Fkt. und die Variable einer Intsanz ist kein Pointer, sondern die Instanz selber. Im Gegensatz dazu hat Delphi die Funktionen einmal für alle Instanzen und jede Instanz ist nur das Paket an Daten.
Damit gibt es Probleme bei der Übertragung Delphi <--> C++. C++ --> Delphi wird meist mit Instanzen geregelt, da die anscheinend (bin mir also nicht sicher) sich passend verhalten.

Meine "Quelle" (Delphi-Klasse) findest du hier:http://www.delphidev.de/forum/viewtopic … 5450#p5450.

[quote=bjoern]- Warum gibt es eine Zugriffsverletzung bei Adresse 00452465 in Modul "Cim.exe". Lesen von Adresse 00BE6800.?
Der Fehler tritt auf bei  My_S7_Renderer.GetDevice.Init();, sonst gäbs ja auch das Errorfenster. Er scheint also
Die Prozedur zu finden, aber der Zugriff auf Init klappt nicht.

Was den Fehler angeht, bin ich mir nicht sicher, aber als erstes fällt mir der Hinweis in der Unit auf, lies ihn einfach mal durch.
Als zweites, Beim Anlegen linkst du zu einer Bibliothek, danach wird innerhalb dieser Bibliothek eine Klasse angelegt, die du speicherst, danach gibst du die Bibliothek frei. Es ist schon länger her, dass ich soetwas in Delphi gemacht habe, aber kann es sein, dass dann der Speicher wieder freigegeben wird. Du hast dann zwar noch den Pointer dahin, aber die Implementation deiner Klasse kann weg sein. Aber wie gesagt, da bin ich mir nicht sicher, werde mal nachher/morgen in meinem eigenen Versuch davon nachsehen, ich glaube aber, ich habe die Bibliothek erst beim Klassenfreigeben freigegeben.
Das war erstmal das, was mir auf Anhieb auffiel.

Zu der Engine an sich:
Ich kenne das Buch, habe es vor ein paar Jahren selber benutzt (bis Seite 200 oder so tongue ). Da ich selber damals so angefangen habe, möchte ich nichts dazu sagen, ich habe Engine-Programmierung nunmal so kennen gelernt. Aber ich habe eine Frage an dich. Du schreibst, du machst meistens C++. Warum willst du die Engine dann plötzlich in Delphi schreiben? Nach dem Buch ist es auch C++, wenn es wirklich das ist, was ich meine.

LOTIPATS

Offline

 

#5 25.10.2007 17:19:02

bjoern
ProMember
Ort: OBK
Registriert: 18.07.2006
Beiträge: 112
Web-Seite

Re: DLLs und anderes...

[quote=Lotipats]Was den Fehler angeht, bin ich mir nicht sicher, aber als erstes fällt mir der Hinweis in der Unit auf, lies ihn einfach mal durch.
Als zweites, Beim Anlegen linkst du zu einer Bibliothek, danach wird innerhalb dieser Bibliothek eine Klasse angelegt, die du speicherst, danach gibst du die Bibliothek frei. Es ist schon länger her, dass ich soetwas in Delphi gemacht habe, aber kann es sein, dass dann der Speicher wieder freigegeben wird. Du hast dann zwar noch den Pointer dahin, aber die Implementation deiner Klasse kann weg sein. Aber wie gesagt, da bin ich mir nicht sicher, werde mal nachher/morgen in meinem eigenen Versuch davon nachsehen, ich glaube aber, ich habe die Bibliothek erst beim Klassenfreigeben freigegeben.
Das war erstmal das, was mir auf Anhieb auffiel.

Zu der Engine an sich:
Ich kenne das Buch, habe es vor ein paar Jahren selber benutzt (bis Seite 200 oder so tongue ). Da ich selber damals so angefangen habe, möchte ich nichts dazu sagen, ich habe Engine-Programmierung nunmal so kennen gelernt. Aber ich habe eine Frage an dich. Du schreibst, du machst meistens C++. Warum willst du die Engine dann plötzlich in Delphi schreiben? Nach dem Buch ist es auch C++, wenn es wirklich das ist, was ich meine.

LOTIPATS

Super vielen Dank! smile Das mit der Freigabe wars! Funzt jetzt..

Was deine Frage angeht: Also ich programmiere eigentlich am liebsten und am öftensten in Delphi, nur bin ich von der Arbeit her (Praktikum) gezwungen, mit C++ zu arbeiten und habe deshalb meine Scheu ein C++-Buch zu lesen verloren... Aber wie gesagt, am liebsten Delphi!

Das mit dem Debuggen ist eine harte Nuss ihr habt Recht... bin jetzt auch wieder am Zweifeln, obwohl ich den Ansatz im Buch so gut fand... neutral


Herzlichen Dank für die Erklärungen mit den Pointer an alle!


Schönen Abend,
Björn

Offline

 

#6 07.11.2007 18:04:10

TheDon
Member
Registriert: 14.01.2006
Beiträge: 32

Re: DLLs und anderes...

Zur Parameterübergabe:

Bei der Angabe von const entscheidet der Compiler welche Variante günstiger ist, bei der Übergabe (value oder reference) und er erlaubt keine Schreibzugriffe innerhalb der Prozedur/Funktion. Beispielsweise übergibt er bei procedure f(const x:Integer); sehr wohl per value. Es hat auch nicht viel Sinn einen Zeiger auf x zu übergeben, weil er genau so gross ist, wie x slebst. Ausserdem werden, wie oben richtig beschrieben, die ersten 3 Parameter, sofern sizeof(Param_i)<=4, per Register EAX, EBX und ECX übergeben. Rückgabewert in EAX, wenn "register" calling convention
(standard) verwendet wird.

Das verwenden von Klasseninstanzen über DLLs finde ich problematisch. Und zwar kompiliert der Code in der DLL prinzipiell gegen eine andere Klasse als dein Programm. Solange man nur Methoden aufruft die in der VMT der Klasse stehen (alle virtual oder override Mehtoden) gibt das kein Problem, weil jede Klasseninstanz eine eigene VMT hat, und die wird beim Erzeugen des Objekts in der DLL erstellt. Somit werden auch die in der DLL implementierten Methoden aufgerufen. Bei allen anderen Methoden erzeugt der Compiler schon beim der compilation den Sprung zur, nach seiner Meinung nach, richtigen Methode. Diese liegt dann nicht in der DLL sonder in deiner Application.

ich hoffe ich hab dich jetzt so richtig verwirrt,
lg und viel erfolg
Günther


TheDon

Offline

 

Brett Fußzeile

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson