![]() |
Fragen zur API-Entwicklung
Hallo zusammen,
ich möchte gerne eine kleine API entwickeln und möchte gerne alles richtig und sauber machen und frage daher hier um Hinweise. Wert legen möchte ich auch auf gute Verträglichkeit mit C/C++ (z.B. Nutzung von C-Strings anstelle von PASCAL-Strings). Ich habe hierfür 3 Fragen: 1) Redundanter Code bei den API "Headers" Bei C ist es üblich, Header (*.h) und Code (*.c) in zwei Dateien zu trennen. In Delphi hat man hierfür 2 Abschnitte in einer einzigen PAS Datei. Ich habe gemerkt, dass durch diese Praxis große Teile des Codes redundant sind und ich diese immer synchron halten muss. Als Beispiel erstelle ich eine DLL "MyDLL" mit einer Funktion "MyFunction", die ein record/struct "TMyType" zurückliefert. Ich muss den jenigen, die meine DLL nutzen wollen, die Deklaration von "TMyType" zur Verfügung stellen. Problematisch ist, dass ich bei der Entwicklung der DLL diesen TMyType definieren muss, allerdings auch bei der Nutzung. - Entwicklung der DLL - Datei MyDLL.dpr:
Delphi-Quellcode:
Datei MyAPI1.pas:
library MyDLL;
uses MyAPI1 in 'MyAPI1.pas'; function MyFunction: TMyType; cdecl; begin // ... end; exports MyFunction; end.
Delphi-Quellcode:
- Nutzung der DLL -
unit MyAPI1;
interface type // REDUNDANTER CODE :-( TMyType = record zahl: Cardinal; string: PAnsiChar; end implementation end. Datei MyProgram.dpr:
Delphi-Quellcode:
Datei MyAPI2.pas:
program MyProgram;
uses MyAPI2 in 'MyAPI2.pas'; begin MyFunction; // auf die DLL wird zugegriffen end.
Delphi-Quellcode:
unit MyAPI2;
interface type // REDUNDANTER CODE :-( TMyType = record zahl: Cardinal; string: PAnsiChar; end {$EXTERNALSYM MyFunction} function MyFunction: TMyType; cdecl; implementation function MyFunction; external 'MyDll.dll' name 'MyFunction'; end. Hier wird deutlich, dass MyAPI1.pas und MyAPI2.pas die SELBEN Inhalte haben, mit Unterschied dass MyAPI2.pas (zur Benutzung der DLL) zusätzlich noch die Importe zur Verfügung stellt. Wie kann ich verhindern, dass MyAPI1.pas ("Entwickler-Deklarationen") und MyAPI2.pas ("Benutzer-Deklarationen") so extreme Coderedundanzen aufweisen? (Ich wünschte, Delphi hätte so eine interface/implementation Trennung wie in C...) (FYI: Ich entwickle die API gleichzeitig zu einer Client-Applikation, die diese DLL/API nutzt. Daher habe ich das unmittelbar das Problem, MyAPI1.pas und MyAPI2.pas ständig per Copy-Paste synchron zu halten, was keine gute Programmierpraxis sein kann...) 2) C++ Interoperatibilität Sollte ich, wenn ich einfache Handhabung/Kompatibilität mit C/C++ wünsche, "cdecl" oder "stdcall" nutzen? Sollte ich "packed record" oder "record" nutzen, damit es mit "struct" am besten Kompatibel ist? 3) Versionsinfo: Sprachneutralität Ich nutze Turbo Delphi (da ich OpenSource entwickle) und habe festgestellt, dass jede DLL mit der Locality "DE-DE" erstellt wird. Es scheint keine Möglichkeit zu geben, die VersionsInfo auf "Sprachneutral" (VALUE "Translation", 0x0000 0x04E4) anstelle "Deutsch (Deutschland)" (VALUE "Translation", 0x0407 0x04E4) umzustellen. Sobald ich die *.res in einem ResourceEditor auf 0000/04E4 ändere, erkennt Delphi die Versionsinformation nicht mehr an. Es wäre schon schön, wenn die DLL als Sprachneutral dargestellt würde, denn sie stellt in keinster Form eine GUI oder Dialoge zur Verfügung. Ich freue mich über Antworten und Hinweise um die Best-Practise für API-Entwicklung unter Delphi zu finden. Gruß Daniel Marschall |
AW: Fragen zur API-Entwicklung
Wieso erstellt du nicht eine Datei mit den Typdeklarationen und bindest diese dann an deinen beiden Beispielstellen ein?
Alternative: Schau dir mal den Compilerschalter $I an. |
AW: Fragen zur API-Entwicklung
Hallo omata,
Meinst du sowas?
Delphi-Quellcode:
Das müsste zwar funktionieren, aber es ist ja nicht sonderlich "schön", oder? Die Leute würden sich dann wundern, wieso ich die API in 2 PAS-Dateien verschachelt habe. Außerdem ist es ja schon verwunderlich wieso man API1.pas und API2.pas braucht... Mein Vorbild ist beispielsweise die Windows.h bzw. Windows.pas, die ja alles in 1 H/PAS Datei enthält.
unit MyAPI2;
interface uses MyAPI1; // enthält Deklaration von TMyType // Oder: // {$I MyAPI1.inc} {$EXTERNALSYM MyFunction} function MyFunction: TMyType; cdecl; implementation function MyFunction; external 'MyDll.dll' name 'MyFunction'; end. Gruß Daniel |
AW: Fragen zur API-Entwicklung
/Update:
Ich habe nun gemäß deines Vorschlags folgende Struktur verwendet. Das ist wohl die Best-Practise, die man mit Delphi hinbekommen kann: Die Alternative mit *.inc ist nicht so gut, da man dann auf das syntaxhighlighting verzichten müsste.
Code:
* = Schade ist, dass die kompletten Funktionsdeklarationen nun in myapi.pas und myapi_impl.pas immer noch redundant sind. In C könnte ich die Funktions-Köpfe einfach in die *.h schreiben und sie später in *.c entweder implementieren oder per Library-Verweis nutzen. Kann man da noch was rausholen?
Gemeinsame Typdeklarationen - myapi_h.pas:
interface type declarations... implementation nichts API Entwicklung - myapi_impl.pas: interface uses myapi_h.pas; Funktionen vollständig deklarieren (REDUNDANT*) implementation Funktionen implementieren API Entwicklung - myapi.dpr (dll): uses myapi_impl.pas exports funktionsnamen; begin end. API Benutzung - myapi.pas: interface uses myapi_h.pas; Funktionen vollständig deklarieren (REDUNDANT*) Zusätzlich: {$EXTERNALSYM} für alle funktionen implementation Funktionen importieren aus DLL API Benutzung - myprogram.dpr (exe): uses myapi.pas begin // Nutzen der Funktionen end. Gruß Daniel |
AW: Fragen zur API-Entwicklung
Liste der Anhänge anzeigen (Anzahl: 1)
Hier mal mein Beispiel...
|
AW: Fragen zur API-Entwicklung
Hallo,
danke für dein Beispiel. So habe ich es im großen und Ganzen auch. Die Unterscheidung
Delphi-Quellcode:
{$IFNDEF LINUX} stdcall {$ELSE} cdecl {$ENDIF};
scheint nützlich zu sein. Ich schaue mir das mal genauer an. Allerdings sind bei dir die Funktionsköpfe auch redundant. Ich würde die am liebsten nach C-Manier in die "MyAPI_H" Datei pflanzen (mit einer Art Unit-Übergreifenden "forward"), aber dann möchte Delphi, dass ich sie auch gleich definiere. API Benutzung
Delphi-Quellcode:
API Entwicklung
unit MyAPI;
interface uses MyAPI_H; {$EXTERNALSYM function_1} {$EXTERNALSYM function_2} ... {$EXTERNALSYM function_n} // Redundante Information: Funktionsköpfe // Nach C-Manier gehören die in die "H"-Datei procedure function_1(args: cardinal); stdcall; procedure function_2(args: cardinal); stdcall; ... procedure function_n(args: cardinal); stdcall; implementation procedure function_1; external 'mydll.dll' name 'function_1'; procedure function_2; external 'mydll.dll' name 'function_2'; ... procedure function_n; external 'mydll.dll' name 'function_n'; end.
Delphi-Quellcode:
Ich denke, dafür bietet die Pascal-Strukturierung keine Lösung (mit Ausnahme einer *.inc, was ich nicht so toll finde wegen fehlendem Syntax-Highlighting)
unit MyAPI_Impl;
interface uses MyAPI_H; // Redundante Information: Funktionsköpfe // Nach C-Manier gehören die in die "H"-Datei procedure function_1(args: cardinal); stdcall; procedure function_2(args: cardinal); stdcall; ... procedure function_n(args: cardinal); stdcall; implementation // Implementierung end. Gruß Daniel |
AW: Fragen zur API-Entwicklung
|
AW: Fragen zur API-Entwicklung
Die Lösungen haben leider doch nicht funktioniert.
Meine vorherige Lösung hatte den Schwachpunkt, dass man im Anwendungsfall "MyAPI.pas" (für Funktionen) UND "MyAPI_H" (für Typen) einbinden muss. Das ist eine Unit zu viel. Im Gegensatz zu C scheint Delphi sehr unflexibel zu sein, da die Implementierung immer an das Interface gekoppelt ist... Die Include-Lösung ist noch katastrophaler. Delphi prüft nämlich zuerst die Funktionen und bindet danach erst die Include ein. Er sagt also, dass er "TMyRecord" bei "function x: TMyRecord" nicht kennt, obwohl der Typ in der Include-Datei drin steht. Schreibt man vor dem {$I} noch ein "type", kommt Delphi ebenfalls durcheinander weil es zu Syntaxfehlern kommt. (Die Include enthält types und consts)... Ich habe nach extrem langer Zeit die Lösung gefunden: Die Unit MyAPI.pas enthält alles was benötigt wird. Alle Typen sowie die DLL-Importe. Diese PAS wird im Anwendungsfall verwendet - logisch. Für die DLL-Entwicklung wird MyAPI_Impl.pas entworfen. Es use'd die MyAPI.pas . Anschließend werden die Funktionen, die vorher aus der DLL importiert wurden nochmal definiert und implementiert. Aus irgendeinem Grund meckert Delphi NICHT wegen einer doppelten Deklaration!!! (Stand: Turbo Delphi, unbekannt ob Verhalten auch in Delphi 7) Durch das Smart-Linking wird die DLL auch nicht sich selbst importieren (Import Table), da auf die Import-Funktionen nicht zugegriffen wird. Stattdessen werden die Funktionen exportiert, die implementiert wurden. Kurz: Bei der API-Entwicklung importiert man die Funktionen _UND_ implementiert sie nochmal. Delphi "entscheidet" sich dann für die Implementierung und gibt keinen Konflikt aus; auch kein overload ist nötig.
Delphi-Quellcode:
---
unit MyAPI;
interface type TMyRecord = record foo: PAnsiChar; end; function myfunc: TMyRecord; stdcall; implementation // Die DLL importiert sich dank Smart-Linking nicht selbst function myfunc: TMyRecord; stdcall; external 'MyDLL.dll' name 'myfunc'; end.
Delphi-Quellcode:
---
unit MyAPI_Impl;
interface uses MyAPI; // Seltsam, seltsam... Kein Namenskonflikt mit MyAPI.myfunc (imported)! function myfunc: TMyRecord; stdcall; implementation function myfunc: TMyRecord; begin result.foo := 'bar'; end; end.
Delphi-Quellcode:
---
library MyDLL;
uses MyAPI_Impl in 'MyAPI_Impl.pas'; exports myfunc; begin end.
Delphi-Quellcode:
program MyProg;
uses MyAPI in 'MyAPI.pas'; var x: TMyRecord; begin x := myfunc; end. Es ist seltsam, dass dieses merkwürdige Verhalten (Symbolkonflikt, der keiner ist) nicht dokumentiert ist. Auch ist es seltsam, dass noch niemand zuvor das Problem der Coderedundanz bei DLL-Entwicklung mit gleichzeitiger Nutzung gestoßen ist. ===> Kann bitte jemand bestätigen, dass dieses Verhalten auch in Delphi 6 und 7 existiert? Ich möchte den Code wegen OpenSource gerne bis mindestens D6 kompatibel halten. Offen sind noch meine Fragen 2 und 3. Weiß denn niemand Rat? Gruß Daniel Marschall |
AW: Fragen zur API-Entwicklung
Jeder einigermassen moderne Compiler kann heutzutage COM/ActiveX-Bibliotheken einbinden.
Wenn du eine Schnittstelle (API) als Typbibliothek auslieferst, dann sind sämtliche Metadaten bekannt. Ein fremder Compiler/IDE liest einfach nur die Typbibliothek ein und kennt anschliesend alle Funktionen, Parameter, Konstanten, Strukturen und Schnittstellen. COM/ActiveX ist ab Delphi 5 aufwärts sinnvoll einsetzbar (mit Einschränkungen D4). |
AW: Fragen zur API-Entwicklung
Was hat der Einsatz von COM/ActiveX konkret mit dem Problem der mangelhaften Delphi-Quelltextstruktur im Gegensatz zu C-Sprachen zu tun? Ich habe COM schnmal recherchiert und habe es nicht wirklich begriffen, auch habe ich nie eine Schulung diesbezüglich gehabt. Was ist böse daran, eine DLL-Funktion mit primitiven Datentypen (PAnsiChar, Cardinal, ...) zu deklarieren die einfach nur funktioniert, ohne Microsoft-abhängige Technologien zu nutzen, die das ganze mit Client/Server ver-kompliziert?
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:11 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz