Entwickler-Ecke

Dateizugriff - Bei Aufruf einer DLL passiert im Programm ... nichts ...


Xearox - Mo 16.02.15 05:51
Titel: Bei Aufruf einer DLL passiert im Programm ... nichts ...
Hallo Zusammen,

ich habe ein kleineres Problemchen, hoffe mal, das es klein ist :D

Und zwar hab ich mir eine function erstellt, mit der ich eine Art Gelände/Spielfeld bringen kann. (Ich brauche nicht unbedingt eine DLL, es ist viel mehr zum ausprobieren, da ich bisher noch nichts mit DLLs gemacht habe).

So, wenn ich nun mein Programm starte, also F9 drücke, steht unten bei Meldungen im Tab Erzeugen


Quelltext
1:
2:
3:
4:
Abhängigkeiten des Projekts werden überprüft...
Compilieren von Project1.dproj (Debug-Konfiguration)
Erfolg
Verstrichene Zeit: 00:00:00.2


Doch es passiert nichts ... Also meine Form wird nicht mehr angezeigt.

Ich habe dafür sharemem ins Programm mit eingebunden, da ich entsprechend Strings mit übergebe, außerdem übergebe ich ein Array als result in meiner function.

So, meine DLL sieht wie folgt aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
library graphic;

{ 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
  sharemem,
  SysUtils,
  Classes,
  PNGImage,
  ExtCtrls,
  Controls,
  Graphics;

type
  TImageArray = array of array of TImage;
{$R *.res}

function CreateTerrain(Zeilen, Spalten, ImageRes : integer; Owner : TWinControl;
          FilePath : string; ImageTyp : string = 'png'): TImageArray; //ImageRes = Auflösung des Bildes z.B. 64x64px
var
  Z,S : Integer;
begin
  SetLength(result,Zeilen,Spalten);
  for Z := 0 to Zeilen - 1 do
  begin
    for S := 0 to Spalten - 1 do
    begin
      result[Z,S] := TImage.Create(owner);
      if ImageTyp = 'bmp' then result[Z,S].Picture.Bitmap.LoadFromFile(ExtractFilePath(ParamStr(0))+FilePath+'.'+ImageTyp)
      else result[Z,S].Picture.LoadFromFile(ExtractFilePath(ParamStr(0))+FilePath+'.'+ImageTyp);
      result[Z,S].Left   := result[Z,S].Left + (Z * ImageRes);
      result[Z,S].Top    := result[Z,S].Top  + (S * ImageRes);
      result[Z,S].Width  := ImageRes;
      result[Z,S].Height := ImageRes;
      result[Z,S].Parent := owner;
    end;
  end;
end;

exports
  CreateTerrain;

begin
end.


Und mein TestProgramm sieht so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
unit Unit1;

interface

uses
  sharemem, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TImageArray = array of array of TImage;
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    function CreateTerrain(Zeilen, Spalten, ImageRes : integer; Owner : TWinControl;
          FilePath : string; ImageTyp : string = 'png'): TImageArray;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.CreateTerrain(Zeilen, Spalten, ImageRes : integer; Owner : TWinControl;
          FilePath : string; ImageTyp : string = 'png'): TImageArray;
          external 'graphic.dll';

procedure TForm1.Button2Click(Sender: TObject);
begin
  CreateTerrain(10,5,64,form1,'image/gras64x64');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  CreateTerrain(10,10,64,form1,'image/gras64x64','png');
end;

end.



Wie Ihr sehen könnt, es ist viel mehr rumprobieren.

Nun, wie bereits gesagt, es passiert im Programm nichts, es wird keine Form dargestellt und der Compiler spuckt auch keine Fehlermeldung aus.

Drücken von F4, F7, F8 oder F9 bringt nichts, auch etwaige halte Punkte werden übersprungen(oder nicht beachtet?).

Hab ich irgendwas Falsch gemacht? (Ich weiß, doofe frage, wenn ich nichts Falsch gemacht hätte, würde es ja funktionieren :D )


Liebe Grüße
Xearox


Edit:!!!
Nach einem Neustart geht es wieder, auch wenn bei der DLL so ein Einsprungspunkt fehlt, aber das werde ich schon alleine hinbekommen :D

Für alle die, die sich interessieren, woran es lag, ganz einfach:

Ich habe D2010 einmal geöffnet, nun habe ich D2010 nochmal gestartet damit ich neben bei aufm zweiten Bildschirm an der DLL basteln kann.
Danach konnte ich zwar die DLL compilieren und mein Programm ebenfalls, aber es ist einfach kein Fenster aufgegangen mit der Form.

Man merke, man öffne D2010 nicht zweimal :D

Wer bis hierhin gelesen hat, kann mir evtl dennoch sagen, was mit Einsprungpunkt gemeint ist, wäre lieb =)


Xion - Mo 16.02.15 11:31

Ein Einsprungspunkt ist die Adresse, an der die Funktion steht, welche du aufrufen willst. Eine .dll besteht im wesentlichen aus einer langen Folge von Befehlen, und wenn du nun eine bestimmte Funktion aufrufen willst, dann musst du deren Zeilennummer (den Einsprungspunkt dieser Funktion) kennen.

Wenn du Probleme mit dem Einsprungspunkt hast, dann heißt das eigentlich, die Funktion wurde in der .dll nicht gefunden. Kannst du mal den genauen Fehler posten?


jaenicke - Mo 16.02.15 13:40

Und statt Sharemem zu benutzen und Strings zu übergeben, was sehr unsauber ist, solltest du besser PAnsiChar oder PWideChar benutzen. Da du nur Werte an die DLL übergibst, macht das kaum einen Aufwand, aber dann hast du eine saubere Schnittstelle, die nicht nur mit Delphiprogrammen funktioniert.


SMO - Mo 16.02.15 17:03

user profile iconXearox hat folgendes geschrieben Zum zitierten Posting springen:
(Ich brauche nicht unbedingt eine DLL, es ist viel mehr zum ausprobieren, da ich bisher noch nichts mit DLLs gemacht habe).


Dann hier noch ein paar Tipps. Ein allgemeines Dll-Gerüst sieht bei mir so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
library mydll;

uses
  System.SysUtils,
  Winapi.Windows;

var
  PreviousDLLProcEx: TDLLProcEx;

procedure MyDllMainEx(Reason: Integer; Reserved: Pointer);
begin
  case Reason of
    // dll wurde geladen, Initialisierungsaufgaben können jetzt durchgeführt werden
    DLL_PROCESS_ATTACH: {...};
    // dll wird entladen, Aufräumarbeiten können jetzt durchgeführt werden
    DLL_PROCESS_DETACH: {...};
  end;
  // die Meldung an den vorigen Handler weiterleiten, falls er existiert
  if Assigned(PreviousDLLProcEx) then PreviousDLLProcEx(Reason, Reserved);
end;

{ ... code ...}

exports
  Proc1,
  etc;

begin
  DisableThreadLibraryCalls(HInstance);
  PreviousDLLProcEx := DllProcEx;
  DllProcEx := @MyDllMainEx;
  DllProcEx(DLL_PROCESS_ATTACH, nil);
end.


Hintergrund: Jede DLL hat einen "DllMain" Einstiegspunkt [https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx], der von Windows jedes Mal aufgerufen wird, wenn die DLL geladen oder entladen wird, außerdem jedes Mal wenn im laufenden Prozess (Programm) ein Thread gestartet oder beendet wird. Die Delphi-Laufzeitumgebung hat ihren eigenen DllMain-Handler, den man nicht ändern kann, aber er ruft den Prozedur-Pointer "DllProcEx" (auch DllProc) auf, falls zugewiesen, über den man diese Ereignisse dann mitbekommen kann.
Wenn man an den Thread-Ereignissen nicht interessiert ist, was bei einfachen DLLs normalerweise der Fall ist, kann und sollte man sie per DisableThreadLibraryCalls [https://msdn.microsoft.com/en-us/library/windows/desktop/ms682579%28v=vs.85%29.aspx] deaktivieren.
Für "Reason = DLL_PROCESS_ATTACH" sollte der "Reserved" Parameter der DllMain angeben, ob die DLL implizit bei Programmstart geladen wurde ("statisch"), oder explizit während der Laufzeit durch einen "LoadLibrary" Aufruf ("dynamisch"). Im letzteren Fall sollte er nil (0) sein, sonst nicht. Ich sage sollte, weil das bei Delphi leider nicht funktioniert. Wenn man sich mein Code-Gerüst ansieht, dann sieht man, dass der erste Aufruf von DllProcEx ja von mir selbst stammt, im Hauptprogrammblock. Dort habe ich keinen Zugriff auf den tatsächlichen Wert von "Reserved". Zum Glück ist die Information auf welche Weise die DLL geladen wurde fast immer unwichtig. Ansonsten muss man auf üble Hacks [https://stackoverflow.com/questions/7325369/how-can-i-tell-how-my-dll-was-loaded] zurückgreifen.

Das war jetzt wahrscheinlich mehr als du wissen wolltest, aber da ich in letzter Zeit selbst viel mit DLLs gearbeitet habe, schreibe ich es auch für mich auf, bevor ich es wieder vergesse. ;)

Wenn du DLLs bauen möchtest, die auch von nicht-Delphi-Programmen benutzt werden können, dann solltest du dem Rat von jaenicke folgen. Es ist besser, den Aufrufer selbst den nötigen Speicher reservieren zu lassen, als ihn in der eigenen DLL-Prozedur zu reservieren. Außerdem sollten die exportierten Prozeduren/Funktionen immer die "stdcall"-Konvention benutzen, die die Parameter auf dem Stack übergibt. Denn diese Konvention wird eigentlich überall unterstützt, Delphis eigene "register"-Konvention dagegen nicht (übergibt wenn möglich die ersten drei Parameter in den Registern eax, edx, ecx und benutzt dann erst den Stack).


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
// in der DLL:
function Bla(Param1, Param2: Integer): Integer; stdcall;
begin
//...
end;

// im Programm, das die DLL importiert:
function Bla(Param1, Param2: Integer): Integer; stdcallexternal 'mydll.dll';