AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

String mit .dll ohne borlndmm.dll

Ein Thema von berens · begonnen am 31. Okt 2023 · letzter Beitrag vom 31. Okt 2023
Antwort Antwort
berens

Registriert seit: 3. Sep 2004
434 Beiträge
 
Delphi 10.4 Sydney
 
#1

String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 15:41
Delphi-Version: 10.4 Sydney
Hallo zusammen,
eigentlich muss ich mich ja schämen mit > 20 Jahren Delphi-Erfahrung so einen Thread zu schreiben, aber jeder hat bei irgendeinem Thema seine Schwachstellen, und zu meinen gehören eindeutig Pointer.

Situation und Hintergrund (für das eigentliche Thema nicht direkt relevant):
Ich verwende JWSCL um eine Access-Control-List zu erstellen, welche Active-Directory-Gruppe bei mir in der Software "was" machen darf. Der Editor kommt von Windows, die Rechte kann ich individuell benennen, die ACL liegt mir als einfach weiterzuverwendender String vor, und die Prüfung darf der-und-der das-und-das ist echt super einfach. Dazu habe ich auch ein Tutorial geschrieben ( https://www.delphipraxis.net/184812-...verwenden.html ).
Das Problem ist, dass JWSCL viel viel viel zu Umfangreich ist, und ich es mit den aktuellen Delphi Versionen immer weniger am Laufen halten kann, weil bei den einzelnen Prozeduraufrufen dann irgendwelche Variablentypen etc. nicht passen - bei Prozeduren/Funktionen, die NICHTS mit der o.g. Aufgabe zu tun haben. Leider kann ich auch nicht die "nur ACL relevanten" Sachen rausziehen, weil die Abhängigkeiten in dem JWSCL Projekt so unfassbar tief/groß sind, dass das de facto nicht möglich ist.
Mit Delphi 10.4 habe ich alleine nach einer Neuinstallion wieder 2 Tage gebraucht, bis ich mein leeres JWSCL-Beispielprojekt wieder compilieren konnte, weil die Bibliotheksreihenfolge falsch war etc.etc.
Die Idee besteht also darin, jetzt einmal eine .dll zu erstellen, und auch in zukunft nur mit dieser zu arbeiten. Rein geht string, raus kommt Bool: Benutzer darf? Ja oder Nein. Eigentlich ganz einfach. Ja, das ganze ist dann nicht zukunftssicher etc, aber das nun mal außen vorlassen. Es geht drum, dass ich alles, was mit JWSCL zu tun hat, aus meinem Kernprojekt raushaben möchte!

Problemstellung generell
Ich möchte die BORLNDMM.DLL nicht mitliefern. Einfach ... weil halt. Im Hauptprojekt soll es dann in einer eigenen Unit nur diese einfachen Prozeduren geben wie: BearbeiteACL(var _ACL: string) und DarfBenutzer(_ACL: string; _BenoetigtesRecht: integer): Boolean. Keine Einbindung der JWSCL etc.
In der .dll gibt es dann die Pendants, die das tatsächlich mit JWSCL umsetzen, und die Ergebnisse zurückliefern. Das ist generell kein Problem und auch hier nicht das Thema.

Problemstellung speziell
Ich habe mich schon "ein wenig" zu dem Thema eingelesen, und verstehe dank ChatGPT auch das Problem, warum man nicht "mal eben" einen String an eine .dll übergeben kann, oder warum kein String aus einer .dll (ohne die BORLNDMM.DLL) zurückkommen kann: Die .dll kann bei -nun größeren Strings- nicht mal eben so den Speicherbereich des Hauptprogramms verändern, in dem der String eigentlich gespeichert ist, und wenn ein String als Result aus der .dll zurückgegeben wird, wird der Speicher mit Ende des Prozedur in der .dll eigentlich auch schon wieder freigegeben. So ungefähr oder ähnlich ist das Problem.

Die Lösung mit PAnsiChar ist unkompliziert, weil die .dll nur mitbekommt "An dieser Stelle im RAM beginnt ein AnsiString", da kann gibt es kein Problem mit dem vergrößern/verkleinern des Arbeitsspeichers des Hauptprogramm. Die .dll bekommt einfach einen PAnsiChar für den Input, den Output und eine Bool-Variable als Platzhalter für einen Rückgabewert, ob die Aktion erfolgreich war.

Das Problem ist nun natürlich, dass ich als Programmierer für das Speichermanagement zuständig bin. Falls an dem pAnsiString nämlich kein Null-Terminator hängt, kann es sein, dass ein BufferOverflow etc. stattfindet, und aufeinmal lustige Daten meines -oder eines anderen Programmes- in dem pAnsiString "stehen" oder große Bereiche des RAMs einfach komplett überschrieben werden, weil die Angabe fehlt "wie lang" denn nun wirklich dieser AnsiString ist.

Das folgende Programm habe ich mit viel hin- und her mit ChatGPT zusammengebastelt, und ich bin sehr unzufrieden damit.
Eigentlich sollte folgendes passieren:
1) Aufruf der Prozeduren ganz normal mit "string" Parametern.
2) Umwandeln der Strings in pAnsiChar
3) Aufruf der .dll-Prozeduren mit pAnsiChar
4) Umwandlung innerhalb der .dll wieder in string
5) Verarbeitung der Strings innerhalb der .dll
6) Rückumwandlung der Ergebnisse in pAnsiChar
7) Hauptprogramm wandelt Rückgabewert pAnsiChar in string um, und gibt diesen zurück.

Wenn ich jetzt das Thema richtig verstanden habe, muss ich mir als Programmierer schon vorher eine fixe Grenze überlegen, die ein gefüllter String maximal haben darf. Der .dll sollte ich dann (besser als das u.g. Beispiel) die Input-Variable übergeben, zusammen mit der Input-Größe des pAnsiChar, sowie eine Output-Variable (10'000 oder 65'000 Zeichen oder was auch immer) und deren Größe, die bereits initialisiert ist.

In der .dll sollte ich dann Prüfen, ob Input und Output zu den übergebenen Größen passen, dann in Strings umwandeln, und Rückzus, wenn ich das Ergebnis vom OutputString in die Output pAnsiChar-Variable speichere muss ich halt schauen, dass nicht über die X-Zeichen maximale Größe drübergeschrieben wird, und dass am Ende der tatsächlichen Nutzdaten (wahrscheinlich nur so 250 Zeichen?) dann auch wirklich der Null-Terminator steht.

Im Hauptprogramm wird ja dann über den Typcast "reversedStr := string(outputAnsi);" aller unnötiger Ballast entfernt, und die z.B. 65kb Arbeitsspeicher sind nur für die Dauer des .dll-Aufrufs tatsächlich blockiert(?).

Sehe ich das generell richtig, was ist zu beachten, wie würdet ihr das lösen?

Delphi-Quellcode:
unit uHauptprogramm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  procedure ReverseString(input: PAnsiChar; var output: PAnsiChar; var success: Boolean); stdcall; external 'pDLL.dll';
var
  Form1: TForm1;

implementation

{$R *.dfm}


const
  MAX_STRING_LENGTH = 10000;


procedure TForm1.Button1Click(Sender: TObject);
var
  inputStr, reversedStr: string;
  inputAnsi, outputAnsi: PAnsiChar;
  success: Boolean;
begin
  try
    inputStr := LabeledEdit1.Text;

    if Length(inputStr) > MAX_STRING_LENGTH then
    begin
// Writeln('Der eingegebene String ist zu lang.');
      Exit;
    end;

    inputAnsi := PAnsiChar(AnsiString(inputStr));
    GetMem(outputAnsi, MAX_STRING_LENGTH + 1); // Speicherzuweisung für den outputAnsi
    try
      ReverseString(inputAnsi, outputAnsi, success);

      if success then
      begin
        reversedStr := string(outputAnsi); // Konvertierung von PAnsiChar nach string
        LabeledEdit2.Text := reversedStr;
      end
      else
      begin
// Writeln('Ein Fehler ist aufgetreten.');
      end;
    finally
      FreeMem(outputAnsi);
    end;

  except
    on E: Exception do
    begin
// Writeln(E.ClassName, ': ', E.Message);
    end;
  end;
end;
end.

Delphi-Quellcode:
library pDLL;

{ Wichtiger Hinweis zur DLL-Speicherverwaltung: ShareMem muss die erste
  Unit in der USES-Klausel Ihrer Bibliothek UND in der USES-Klausel Ihres Projekts
  (wählen Sie 'Projekt-Quelltext anzeigen') sein, wenn Ihre DLL Prozeduren oder Funktionen
  exportiert, die Strings als Parameter oder Funktionsergebnisse übergeben. Dies
  gilt für alle Strings, die an oder von Ihrer DLL übergeben werden, auch für solche,
  die in Records und Klassen verschachtelt sind. ShareMem ist die Interface-Unit zur
  gemeinsamen BORLNDMM.DLL-Speicherverwaltung, die zusammen mit Ihrer DLL
  weitergegeben werden muss. Übergeben Sie String-Informationen mit PChar- oder ShortString-Parametern, um die Verwendung von BORLNDMM.DLL zu vermeiden.
}


uses
  AnsiStrings;

{$R *.res}

const
  MAX_STRING_LENGTH = 10000;

function MyStrReverse(const S: AnsiString): AnsiString;
var
  P1, P2: PAnsiChar;
  Temp: AnsiChar;
  Len: Integer;
begin
  Len := Length(S);
  SetLength(Result, Len);

  P1 := PAnsiChar(S);
  P2 := @Result[Len];
  while Len > 0 do
  begin
    Temp := P1^;
    P1^ := P2^;
    P2^ := Temp;

    Inc(P1);
    Dec(P2);
    Dec(Len);
  end;
end;


procedure ReverseString(input: PAnsiChar; var output: PAnsiChar; var success: Boolean); stdcall; export;
var
  inputStr, reversedStr: AnsiString;
begin
  success := False;

  // Sicherstellen, dass der Zeiger gültig ist
  if input = nil then
    Exit;

  try
    inputStr := AnsiString(input);

    // Überprüfen, ob die Eingabe sicher zu handhaben ist
    if Length(inputStr) > MAX_STRING_LENGTH then
      Exit;

    reversedStr := MyStrReverse(inputStr);

    // Speicher für den Ausgabe-String zuweisen
    GetMem(output, Length(reversedStr) + 1);
    ansistrings.StrPCopy(output, reversedStr);

    success := True;
  except
    // Hier könnte ein Fehlerprotokoll hinzugefügt werden
  end;
end;

exports
  ReverseString;

begin
end.

Achtung: Ja, der Code hat noch viele Probleme die mir bekannt sind, es geht mehr um's Prinzip als jetzt in dieser Demo um die 100% richtige Speicherverwaltung.

Alternativ: Einfachere Möglichkeit zum Transfer von Strings in .dlls?

Zitat:
Memory Leak: In Ihrer DLL-Prozedur ReverseString haben Sie mit GetMem Speicher für output zugewiesen, aber dieser Speicher wird nie freigegeben. Dies führt zu einem Memory-Leak. Sie sollten in Ihrer Hauptanwendung diesen Speicher mit FreeMem wieder freigeben, nachdem Sie die Rückgabe aus der DLL verarbeitet haben.

Möglicher Zugriff auf ungültigen Speicher: In der Hauptanwendung haben Sie GetMem für outputAnsi aufgerufen und dann diesen Zeiger an die DLL weitergegeben. In der DLL haben Sie dann erneut Speicher für output zugewiesen. Das führt dazu, dass der ursprüngliche in der Hauptanwendung zugewiesene Speicher "verloren" geht und nie freigegeben wird.
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#2

AW: String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 16:04
Die borlndmm.dll wird sowieso schon seit Jahrzehnten nicht mehr benötigt.
Delphi-Referenz durchsuchenSimpleShareMem anstatt Delphi-Referenz durchsuchenShareMem.
https://docwiki.embarcadero.com/RADS...g_von_Speicher
DLLs oder EXE ... der, welcher im gemeinsamen Prozess zuerst da ist, dessen Speichermanager wird verwendet.
(bzw. es wird geguckt ob schon wer Anderes da ist, also ob jeweils der Fremde oder der eigene Manager genutzt wird)

WideString (BSTR des OleAut) und ShortString (ein Record) können problemlos geshared werden, da sie nicht den Speichermanager des Delphi benutzen.

PAnsiChar als Parameter (zum Lesen) ist kein Problem.
Als Result muß man dafür sorgen, dass der Speicher (also z.B. der Quell-String) auch nach Funktionsende noch erhalten bleibt, also außerhalb der Funktion gespeichert.

Probematisch ist ein PChar zum Schreiben, denn da muß auf der anderen Seite ausreichend Speicher bereits reserviert sein, bzw. es gibt eine Funktion zum Reservieren.
$2B or not $2B

Geändert von himitsu (31. Okt 2023 um 16:23 Uhr)
  Mit Zitat antworten Zitat
peterbelow

Registriert seit: 12. Jan 2019
Ort: Hessen
704 Beiträge
 
Delphi 12 Athens
 
#3

AW: String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 16:10
Verwende für die DLL-Routinen ausschließlich Parameter vom Typ Widestring um Text zu übergeben oder zurückzubekommen. Widestring ist ein wrapper um den OLE BSTR Typ und Delphi verwaltet den Speicher dafür automatisch unter Verwendung der entsprechenden COM APIs und da verwenden Host app und DLL auch den gleichen Allocator. Mit pChar/pAnsichar kann man zwar Text an eine DLL übergeben aber nicht so einfach Text zurückgeben. Dafür müßte die Host app der DLL nicht nur einen Speicherblock zum füllen übergeben sondern auch dessen Größe, so dass die DLL weis, wieviel hineinpasst. Falls keine sinnvolle maximale Größe für den Block existiert muß die DLL die benötigte Größe zurückgeben, damit der Host für einen zweiten Aufruf den Block entsprechend anlegen kann. Viele Windows-APIs sind so gestrickt wenn sie Daten variabler Größe zurückgeben müssen.
Peter Below
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
434 Beiträge
 
Delphi 10.4 Sydney
 
#4

AW: String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 16:29
Vielen Dank für den Hinweis mit SimpleShareMem, das vereinfacht ja letztendlich ... Alles!

Natürlich direkt beim ersten Start den Laufzeitfehler bekommen "The memory manager cannot be changed after it has been used." *obwohl* ich das alte ShareMem etc. nirgendwo mehr aufrufe. Bevor ich jetzt bei jedem kleinen Demo-Projekt wieder akribisch drauf achten muss, SimpleShareMem als allerallerallererste Unit des Projektes einzubinden, verwende ich lieber direkt die späte Bindung.

Da ich dem Wahrscheinlichkeits-Papagei nicht zu 100% traue, würdest du bitte einen kurzen Blick auf den überarbeiteten Code werfen? Ist der in dieser Form "sicher" im Sinne von gut programmiert und keine zu erwartenden Exceptions und Fehlverhalten wegen irgendwelchen Speicherproblemen?

(Den Inhalt von function MyStrReverse(const S: String): String; einfach komplett ignorieren, ist ja nur ein Dummy. Danke!)

Delphi-Quellcode:
unit uHauptprogramm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, System.SimpleShareMem;

type
  TForm1 = class(TForm)
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

type
  TReverseString = procedure(const input: widestring; var output: widestring; var success: Boolean); stdcall;

var
  Form1: TForm1;

implementation


{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  inputStr, reversedStr: widestring;
  success: Boolean;
  hDLL: THandle;
  DynReverseString: TReverseString;
begin
  try
    hDLL := LoadLibrary('pDLL.dll');
    if hDLL = 0 then
      raise Exception.Create('Konnte pDLL.dll nicht laden!');

    try
      @DynReverseString := GetProcAddress(hDLL, 'ReverseString');
      if not Assigned(DynReverseString) then
        raise Exception.Create('Konnte die Funktion "ReverseString" nicht finden!');

      inputStr := LabeledEdit1.Text;
      reversedStr := '';

      DynReverseString(inputStr, reversedStr, success);

      if success then
      begin
        LabeledEdit2.Text := reversedStr;
      end;
    finally
      FreeLibrary(hDLL);
    end;
  except
    on E: Exception do
    begin
      ShowMessage(E.ClassName + ': ' + E.Message);
    end;
  end;
end;

end.

Delphi-Quellcode:
library pDLL;

uses
  System.SimpleShareMem;

{$R *.res}

function MyStrReverse(const S: widestring): widestring;
var
  P1, P2: PWideChar;
  Temp: WideChar;
  Len: Integer;
begin
  Len := Length(S);
  SetLength(Result, Len);

  P1 := PWideChar(S);
  P2 := @Result[Len];
  while Len > 0 do
  begin
    Temp := P1^;
    P1^ := P2^;
    P2^ := Temp;

    Inc(P1);
    Dec(P2);
    Dec(Len);
  end;
end;


procedure ReverseString(const input: widestring; var output: widestring; var success: Boolean); stdcall; export;
begin
  success := False;

  try
    output := MyStrReverse(input);

    success := True;
  except
    // Hier könnte ein Fehlerprotokoll hinzugefügt werden
  end;
end;

exports
  ReverseString;

begin
end.
Dank dir!

Edit: Überall "widestring" eingestetzt. Da string ja von widestring erbt, sollte das theoretisch unerheblich gewesen sein...
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit

Geändert von berens (31. Okt 2023 um 16:31 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#5

AW: String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 17:05
Da string ja von widestring erbt
Nein, tut es nicht.

String, also AnsiString (inkl. dessen Nachfahren ala UTF8String oder RawByteString) und UnicodeString sind LongStrings.
$2B or not $2B
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
434 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: String mit .dll ohne borlndmm.dll

  Alt 31. Okt 2023, 17:23
Upsi, das longint was von integer erbt, nicht widestring von string
Danke für die Korrektur.

Aber ansonsten passt der Rest, oder?
Innerhalb der jeweiligen Prozeduren sollte es ja im Rahmen dessen, was ich benötige, keine Probleme geben, wenn ich z.B. übergebene Strings über impliziten oder expliziten Typcast in widestring umwandle/zwischenspeichere und später wieder zurück?
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:29 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz