Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung (https://www.delphipraxis.net/193792-delphi-unit-language-binding-fuer-eigene-c-lib-umsetzung.html)

Zacherl 9. Sep 2017 13:09

Delphi-Version: 10 Berlin

Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Hallo zusammen,

ich erstelle grade Delphi Header für eine von mir in C geschriebene Library. Die technische Umsetzung ist kein Problem, allerdings stellen sich mir einige Fragen bezüglich des Code-Designs / der Organisation der Klassen:
  • :?: Was bevorzugt ihr persönlich bezüglich der Kapselung?
    1. Die Low-Level C-API komplett in Delphi Klassen kapseln, um möglichst OOP konform zu sein und bestmöglich alle Delphi Sprachfeatures auszunutzen
    2. Die Low-Level C-API 1 zu 1 übersetzen und dann zusätzlich noch eine Unit mit Wrapper-Klassen erstellen (macht ZLibEx z.b. so)

Dann zur Aufteilung der Klassen. Die C-Lib hat 3 große Module (nennen wir sie A, B, C) und jedes Modul beinhaltet mehrere Typen und Klassen, welche ich in unterschiedliche Header-Files aufgeteilt habe. In C ist das kein Problem, da ich alle diese Header in einem "Main-Header" zusammenfassen kann, so dass der User letztlich nur diese eine Datei inkludieren muss. Bei Delphi ist das natürlich anders.
  • :?: Was ist eure persönliche Präferenz im Bezug auf Aufteilung von Klassen auf Units?
    1. Möglichst nah an der C-Lib halten und Typen/Klassen logisch auf verschiedene Units verteilen (der User muss dann bei Verwendung diverse Units mit in
      Delphi-Quellcode:
      uses
      aufnehmen)
    2. Alles in eine einzelne Unit packen (bzw. höchstens API und Wrapperklassen splitten)
    3. Nach Modulen auf einzelne Units verteilen (wobei dann trotzdem noch 1-2 extra Units inkludiert werden müssen, welche gemeinsame Typen enthalten)

Lasst mal eure Meinung hören!

Viele Grüße
Zacherl

mensch72 9. Sep 2017 17:07

AW: Delphi Header für C-Lib - Umsetzung
 
zu 1:
- fände es gut, wenn die LowLevel Umsetzung sich möglichst nah am 1:1 Prinzip orientiert, denn:
- die CLIB Sachen setzt entweder der ein, der sie von C "so" kennt
- oder jemand ohne viel C-Erfahrung portiert einen C-Source wo ja auch die Urform verwendet wird
- wer in Delphi eine "optische" Kapselung will, kann ja den NameSpace stets mit davor schreiben... ala clib.atoi('123')
- eine Delphi OPP Schicht zur unterstützung von SprachFeatures kann für Step 2 und Leute mit Delphi&C Erfahrung nicht schaden, ist aber dann nicht mehr rückwärtsprotabel, ausser es man erstellt gleiches auch als logisch kompatibel gleich mit in CPP


zu 2:
- für pure 1:1 Portierung wäre es im Prinzip egal, weil wer C Quellen 1:1 umsetzt, sieht dort ja die "Header includes", kaönnte die also 1:1 als "Delphi uses" übernehmen, ABER in C ist die Reihenfolge fast wurcsht und man hat dort oft mehr include als nötig, denn es wird zur Vermeidung von "mehrfachen Defines/Typen/..." einfach in den Headern stets gefragt, ob das Defines des HeaderFiles noch nicht definiert is, dann wird es "einmal" definiert und alles nötige gamacht. Bei den nächsten 100 includes passiert dann nix mehr.. das ist in C & CPP ja so üblich, da streikt aber das Konzept der DelphiUses im InterfaceTeil und kann nur per Trick und viel Mühe im Einzelfall durch "ImplemenationUses" von Kreuzreferenzen befreit werden...

-> also besser alles LowLevel in EINE CLib unit (auch wenn das den DelphiLinker dazu verleitet die EXE mit vielen wohl ungenutzten Sachen etwas aufzublähen.
-> ein CLIB.INC Files, welches im InterfaceTeil bei uses einfach per include eingebunden wird, hält die Option offen, es später doch in mehrere .pas Units aufzuteilen, da aber nur C Kenner wissen, was sich in welcher UNIT/Header, wäre dies Option mehr der Ansatz für C Spezialisten, sich hier ihre eigenen Sachen "zentral" mit einzu binden


CLIB4PAS klingt definitiv interessant!
Hatte vor Jahren mal für 100T Pas-VCL-CodeZeilen sowas rückwärts gemacht, also quasi ne echte CPP VCL gemäß der CPP Header vom C++Builder relalisiert, damit man auch in VC z.B. eine VCL Stringlist, TList,... oder einen VCL (C)AnsiString hatte... das hat mir damals bei der schnellen ersten Version einer PAS->CPP Portierung eines guten PAS BackEnd Frameworks sehr geholfen.
Den so generalisierten Ansatz wie du jetzt hier planst habe ich selbst nie gemacht, beim manuellen C->PAS Convert nutze ich nur eine eigene PAS unit, wo ich eben all die Dinge über die zeit so rein genommen haben, welche ich in meinen C Quellen eben so verwende:)

Zacherl 9. Sep 2017 17:47

AW: Delphi Header für C-Lib - Umsetzung
 
Hey hey, erstmal danke für deine Antwort!

Ich glaube allerdings wir reden ein wenig aneinander vorbei :) Meine Frage bezieht sich auf eine ganz konkrete Library (die ich in C geschrieben habe) und nicht die C-Runtime Lib (habe mal versucht den Titel etwas eindeutiger zu gestalten). Einige Ansätze aus deiner Antwort kann ich allerdings trotzdem übernehmen. Das Verwenden von .inc Files ist auf jeden Fall eine gute Idee, mit der ich mein C-Header-Konzept beibehalten könnte, trotzdem aber auf Delphi Seite nur eine Unit hätte.

Vermutlich werde ich so vorgehen, dass ich eine Low Level Implementierung unter dem Namen
Delphi-Quellcode:
MyLib.API.pas
oder einfach nur
Delphi-Quellcode:
MyLib.pas
erstelle, welche die kompletten Funktionen und Datentypen der C-Lib bereitstellt. Zusätzlich kann ich dann noch die Units
Delphi-Quellcode:
MyLib.ModuleA.pas
,
Delphi-Quellcode:
MyLib.ModuleB.pas
und
Delphi-Quellcode:
MyLib.ModuleC.pas
erstellen, welche dann jeweils die Kapselung der 3 Module in richtigen (Wrapper-)Klassen bereitstellt.

himitsu 9. Sep 2017 20:18

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Jupp, im Prinzip stimme ich mit dem Überein, was schon mein Vorredner sagte.


Theoretisch kann man Methden von Klassen auch direkt external mit Prozeduren verlinken,

aber in der Prakis hat es sich bewährt erstmal die API möglichst 1:1 nach Pascal zu übersetzen,
somit kann diese Schnittstelle erstmal genauso verwendet werden, wie es die Dokumentation und die vielen C-Beispiele zeigen (wenn man eine bekannte C-Lib übersetzt).
Allerdings passe ich die Namen der Parameter öfters mal an (mehr Delphi-Style und entferne Prefixe).
Auch Typen passe ich dem Delphi-Style an, so lange es möglich ist, wie z.B. Integer statt INT oder VAR statt Pointer.
Sogar Const-String statt PChar ist oftmals möglich, bei IN-Parametern, aber lohnt sich nur, wenn sowas bei allen Prozeduren möglich ist, da mit sich möglichst alles an einen "einheitlichen" Style hält.

Wenn direkt als Klasse, dann aber möglichst nah an der originalen API. (Benamung)

Wie gesagt, so kann man die API "auch" im Original-Style verwenden
und vorallem ist es so einfacher später neue Verionen der API einfließen zu lassen.



Falls ich Zeit hab, dann würde ich das Ganze dann nochmal in "schönere" Klassen/Interfaces mit "verständlicheren" Methodennamen kapseln (Wrapper),
bzw. kleinere spezialisiertere, minimalistische und einfache Komponenten.

Rollo62 10. Sep 2017 06:22

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Ich würde es auch eher 1:1 umsetzen, allein schon wegen der Wartbarkeit und lesbarkeit.

Allerhöchstens wenn A, B, C trivial sind würde ich das in eine Unit nehmen, aber das wäre meiner Meinung nach etwas schlechter Stil, weil A, B, C sicherlich auch verschiedene Aufgaben haben.
Diese Aufgabentrennung sollte man am Besten auch in Delphi 1:1 abbilden.

Rollo

Zacherl 10. Sep 2017 15:10

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Zitat:

Zitat von himitsu (Beitrag 1380698)
in der Prakis hat es sich bewährt erstmal die API möglichst 1:1 nach Pascal zu übersetzen

Alles klar :thumb:

Zitat:

Zitat von himitsu (Beitrag 1380698)
Allerdings passe ich die Namen der Parameter öfters mal an (mehr Delphi-Style und entferne Prefixe).
Auch Typen passe ich dem Delphi-Style an, so lange es möglich ist, wie z.B. Integer statt INT oder VAR statt Pointer.

Perfekt. Das habe ich auch bereits so gemacht. Typen mit
Delphi-Quellcode:
T
geprefixt, CamelCase Schreibweise und
Delphi-Quellcode:
var
bzw.
Delphi-Quellcode:
const
für Pointer. Zusätzlich habe ich für einige Enums noch Helper Methoden wie z.b.
Delphi-Quellcode:
ToString
implementiert (dafür hat die C-Lib jeweils eine exportierte Funktion).

Zitat:

Zitat von Rollo62 (Beitrag 1380704)
Ich würde es auch eher 1:1 umsetzen, allein schon wegen der Wartbarkeit und lesbarkeit.

Allerhöchstens wenn A, B, C trivial sind würde ich das in eine Unit nehmen, aber das wäre meiner Meinung nach etwas schlechter Stil, weil A, B, C sicherlich auch verschiedene Aufgaben haben.
Diese Aufgabentrennung sollte man am Besten auch in Delphi 1:1 abbilden.

Ich werde jetzt wohl den Kompromiss wählen und für jeden C-Header eine .inc Datei erstellen, die ich dann später in einer einzelnen API Unit zusammenfasse. Dann habe ich die Sachen ordentlich getrennt und man muss hinterher trotzdem nich 15 (Sub-)Units einbinden, damit alle Typen der Lib zur Verfügung stehen. Für die Kapselung in Klassen erstelle ich dann nochmal 3 weitere Units mit den entsprechenden Modulen A, B und C. Dann muss der User unter Delphi immer die API Unit verwenden und je nach gewünschtem Modulen noch die entsprechende Unit mit den Wrapperklassen einbinden.

Danke euch allen!

:?: Noch irgendwelche Tipps zum Lösen von Namenskonflikten die auftreten, wenn ich zusätzliche Wrapperklassen schreibe? Habe in C ja meine Context-Structs wie z.b.
Delphi-Quellcode:
MyDecoder
, die ich dann erstmal 1:1 nach Delphi als
Delphi-Quellcode:
TMyDecoder = record
umsetze. Die Wrapperklasse kann ich jetzt natürlich nicht auch
Delphi-Quellcode:
TMyDecoder
nennen. Hier lieben den Namen der Wrapperklasse mutieren (
Delphi-Quellcode:
TMyDecoderWrapper
oder sowas) statt Den des Records? Oder doch liebe den Record umbenennen in z.b.
Delphi-Quellcode:
TMyDecoderStruct
und dafür die High-Level Klasse bei
Delphi-Quellcode:
TMyDecoder
belassen?

:?: Achso und würdet ihr Klassen, Typen, Records, Konstanten, etc. mit dem Namen der Lib prefixen? Mangels Namespaces mache ich das eigentlich ganz gerne in Delphi (auch wenn man Mehrdeutigkeiten natürlich mit explizitem Angeben der Unit lösen kann - was aber auch irgendwie nichts Ganzes und Halbes ist).

mensch72 10. Sep 2017 16:22

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Zur Namensgebung und als Alternative, wo man in C das simpel per Groß/Klein-Schreibung macht:

TMyTool verwende ich in Delphi nur für "echte" Klassen/Objekte, also alles was man per CreateAufruf erzeugen muss
RMyTool verwende ich in Delphi für Records, also C Structs, welche sich auch statisch ohne "Create" nutzen lassen
PMyTool definiere ich immer zusammen mit RMyTool als typisierten Pointer von RMyTool

Mit Operatoren und Propertys für Records also "R..." habe seit D2007 sehr gute Erfahrungen mit allen weiteren Delphi Varianten.
Ich nutze insbesondere die "default" Property oft als typisiertes virtual Array/List... funktioniert seit D2007 super und ersetzt mit die doofen TList<XY> Generics die zwar schön einfach zu tippen sind, aber bei mir schlicht zu langsam und zu instabil sind.


Präfixe:
- auch wenn OldScool: ich mache es auch heute noch
- allgemeines wie ein GetValue, ein UpdateStorage oder UpdateGUI sind aus meiner Sicht "ohne Kontext" sehr schlecht im Code zu verstehen... daher lieber mylibGetValue, mylibUpdateXXX
- ich ziehe das trotz der paar Zeichen Zusatztiparbeit so durch, hat den positiven neben Effekt, das fremde in der Anwendung auch stets sofort sehen&wissen in woher das kommt und bei bedarf dort finden, wozu das ist und was es macht

Wer nur mit allgemeingültigen Interfaces ala "WriteData" arbeiten will, der soll es tun und kann es sich selbst HighLevel so definieren. Ich bevorzuge bei LowLevel und ExternalModule einer kurze 1:1 PräfixVariante, welche bei mir auch so schon im C Source vorhanden ist:)

Ghostwalker 11. Sep 2017 06:18

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Zu a:

Ich würde sie erstmal 1 zu 1 umsetzen und dann darauf aufbauend eine entsprechende Klassenhirarchie aufbauen.

Eine Funktions-Lib in eine Klassenhirarchie zu packen, nur damits OOP-Konform ist, macht keinen Sinn und erzeugt i.d.R. nur Overhead.


Zu b:

Hier würd ich zu 1. tendieren. Damit erleichtert man die Orientierung, sowohl im Source, als auch ggf. in der Dokumentation.

Zacherl 11. Sep 2017 16:28

AW: Delphi Unit (Language Binding) für eigene C-Lib - Umsetzung
 
Zitat:

Zitat von mensch72 (Beitrag 1380720)
TMyTool verwende ich in Delphi nur für "echte" Klassen/Objekte, also alles was man per CreateAufruf erzeugen muss
RMyTool verwende ich in Delphi für Records, also C Structs, welche sich auch statisch ohne "Create" nutzen lassen
PMyTool definiere ich immer zusammen mit RMyTool als typisierten Pointer von RMyTool

Ansich eine gute Konvention, allerdings nicht ganz konform mit dem Pascal Style Guide, welcher das
Delphi-Quellcode:
T
Prefix für sämtliche Typen vorsieht. Allerdings sehe ich hier zumindest bezüglich der CamelCase Bezeichner sogar explizit eine Ausnahme für Header Translations. Muss ich mich mal umsehen, wie die breite Masse das hier so handhabt. In den JEDI API Headern meine ich mich erinnern zu können, dass Typen immer erst mit ihrem originalen Namen (ohne
Delphi-Quellcode:
T
Prefix und vollständig in Capslock) deklariert wurden und danach nochmal ein Alias mit
Delphi-Quellcode:
T
Prefix und CamelCase angelegt wurde.

Zitat:

Zitat von mensch72 (Beitrag 1380720)
Präfixe: - auch wenn OldScool: ich mache es auch heute noch

Schön zu sehen, dass ich hier nicht alleine stehe :-D

Zitat:

Zitat von Ghostwalker (Beitrag 1380744)
Ich würde sie erstmal 1 zu 1 umsetzen und dann darauf aufbauend eine entsprechende Klassenhirarchie aufbauen.

Eine Funktions-Lib in eine Klassenhirarchie zu packen, nur damits OOP-Konform ist, macht keinen Sinn und erzeugt i.d.R. nur Overhead.

:thumb: Habe jetzt mal mit der 1:1 Übersetzung angefangen. Die Sache mit den *.inc Files ist leider doch nicht so wirklich praktikabel, weshalb ich die 1:1 Übersetzung vorerst komplett in eine Unit packe. Bezüglich der Funktions-Libs hast du recht. In meinem Falle beziehen sich die Funktionen aber immer auf eines der der 3 Module (
Delphi-Quellcode:
ModuleAInit, ModuleAMachWas, ModuleASetThis, ModuleAGetThat
, etc), sind also perfekt in Klassen zusammenfassbar.


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:36 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