AGB  ·  Datenschutz  ·  Impressum  







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

Fragen zur API-Entwicklung

Offene Frage von "Sherlock"
Ein Thema von blackdrake · begonnen am 23. Aug 2011 · letzter Beitrag vom 29. Aug 2011
Antwort Antwort
Seite 1 von 2  1 2      
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#1

Fragen zur API-Entwicklung

  Alt 23. Aug 2011, 23:16
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:
library MyDLL;

uses
  MyAPI1 in 'MyAPI1.pas';

function MyFunction: TMyType; cdecl;
begin
  // ...
end;

exports
  MyFunction;

end.
Datei MyAPI1.pas:
Delphi-Quellcode:
unit MyAPI1;

interface

type
  // REDUNDANTER CODE :-(
  TMyType = record
    zahl: Cardinal;
    string: PAnsiChar;
  end

implementation

end.
- Nutzung der DLL -

Datei MyProgram.dpr:
Delphi-Quellcode:
program MyProgram;

uses
  MyAPI2 in 'MyAPI2.pas';

begin
  MyFunction; // auf die DLL wird zugegriffen
end.
Datei MyAPI2.pas:
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.dllname '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
Daniel Marschall

Geändert von blackdrake (24. Aug 2011 um 15:30 Uhr) Grund: [delphi]-tag anstelle [code]
  Mit Zitat antworten Zitat
omata

Registriert seit: 26. Aug 2004
Ort: Nebel auf Amrum
3.154 Beiträge
 
Delphi 7 Enterprise
 
#2

AW: Fragen zur API-Entwicklung

  Alt 23. Aug 2011, 23:23
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.
  Mit Zitat antworten Zitat
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#3

AW: Fragen zur API-Entwicklung

  Alt 23. Aug 2011, 23:32
Hallo omata,

Meinst du sowas?

Delphi-Quellcode:
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.dllname 'MyFunction';
 
end.
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.

Gruß
Daniel
Daniel Marschall

Geändert von blackdrake (24. Aug 2011 um 15:29 Uhr) Grund: [delphi]-tag anstelle [code]
  Mit Zitat antworten Zitat
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#4

AW: Fragen zur API-Entwicklung

  Alt 24. Aug 2011, 00:16
/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:
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.
* = 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?

Gruß
Daniel
Daniel Marschall

Geändert von blackdrake (24. Aug 2011 um 02:14 Uhr)
  Mit Zitat antworten Zitat
omata

Registriert seit: 26. Aug 2004
Ort: Nebel auf Amrum
3.154 Beiträge
 
Delphi 7 Enterprise
 
#5

AW: Fragen zur API-Entwicklung

  Alt 24. Aug 2011, 09:06
Hier mal mein Beispiel...
Angehängte Dateien
Dateityp: zip API.zip (2,3 KB, 6x aufgerufen)
  Mit Zitat antworten Zitat
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#6

AW: Fragen zur API-Entwicklung

  Alt 24. Aug 2011, 15:26
Hallo,

danke für dein Beispiel. So habe ich es im großen und Ganzen auch.

Die Unterscheidung

{$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:
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.dllname 'function_1';
procedure function_2; external 'mydll.dllname 'function_2';
...
procedure function_n; external 'mydll.dllname 'function_n';

end.
API Entwicklung
Delphi-Quellcode:
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.
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)

Gruß
Daniel
Daniel Marschall

Geändert von blackdrake (24. Aug 2011 um 15:29 Uhr) Grund: [delphi]-tag anstelle [code]
  Mit Zitat antworten Zitat
Florian Hämmerle
(Gast)

n/a Beiträge
 
#7

AW: Fragen zur API-Entwicklung

  Alt 24. Aug 2011, 15:43
Hab ich mich auch erst kürzlich gefragt: Link

mfg Florian
  Mit Zitat antworten Zitat
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#8

AW: Fragen zur API-Entwicklung

  Alt 28. Aug 2011, 05:02
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.dllname '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
Daniel Marschall
  Mit Zitat antworten Zitat
Benutzerbild von sx2008
sx2008

Registriert seit: 15. Feb 2008
Ort: Baden-Württemberg
2.332 Beiträge
 
Delphi 2007 Professional
 
#9

AW: Fragen zur API-Entwicklung

  Alt 28. Aug 2011, 12:30
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).
  Mit Zitat antworten Zitat
blackdrake

Registriert seit: 21. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#10

AW: Fragen zur API-Entwicklung

  Alt 28. Aug 2011, 16:59
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?
Daniel Marschall
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


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 11:54 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