Programmierung

Hast du Töne…!?

Sound-DLL

CONZEPT 16 bietet dem Software-Entwickler Befehle, die sich weit von der reinen Datenbank-Funktionalität abheben. So bietet z.B. die fest integrierte PDF-API einen Befehlssatz für die Erstellung und Verarbeitung von PDF-Dokumenten. Ein zweites Beispiel stellen die Chart-Befehle zur Erstellung von Diagrammen dar. Aber auch Aufgaben, die nicht unmittelbar mit CONZEPT 16 gelöst werden können, sind realisierbar. Das zeigt der vorliegende Artikel an einem Beispiel.

Grundlage

Die CONZEPT 16-DLL-Schnittstelle bietet dem Entwickler die Möglichkeit, eine extern vorliegende Laufzeitbibliothek (DLL) zur Laufzeit zu laden und Funktionen in der DLL durchzuführen. Da die DLL in den Adressraum des Client-Prozesses geladen wird, sind Funktionsaufrufe in der DLL und die Übergabe von Argumenten an die DLL sehr schnell.

Rüstzeug

Zum Erstellen einer DLL wird ein geeignetes Entwicklungs-Werkzeug benötigt. Das vorliegende Beispiel wurde mit dem Microsoft C/C++-Compiler und Visual Studio 2008 erstellt. Falls Sie das Beispiel modifizieren möchten, benötigen Sie die Entwicklungsumgebung ebenfalls. Für den nicht-kommerziellen Bereich und zum Testen stellt Microsoft eine kostenlose Express-Edition der Entwicklungsumgebung bereit. Wenn Sie das Beispiel nur Ausführen möchten, genügt es die ZIP-Datei am Ende des Artikels herunterzuladen.
CONZEPT 16-seitig benötigen Sie lediglich zwei Dateien: c16.h und c16_dll.h. Hierbei handelt es sich um C/C++-Header-Dateien, die im Lieferumfang von CONZEPT 16 enthalten sind. Die Datei c16.h enthält allgemeine CONZEPT 16-Definitionen, wie z.B. Fehlercodes und die CONZEPT 16-Datentypen. Die Datei c16_dll.h definiert die Schnittstellen-Funktionalität.
Den Kern der DLL-Schnittstelle bildet der sogenannte ‘Call Control Block’. Dieser enthält ein Instanz-Handle, dass von CONZEPT 16 übergeben wird, sowie Funktionszeiger, die von CONZEPT 16 gesetzt werden. Dadurch ist der Aufruf von Funktionen im Client möglich.

Hört, hört!

Zwar sind die grafischen Möglichkeiten des CONZEPT 16-Clients schon recht ordentlich, wenn es jedoch um die Wiedergabe von Audioinhalten geht, dann ist der Befehlsumfang bereits mit dem Befehl SysBeep() erschöpft. Damit wir dennoch die Möglichkeit erhalten, System-Sounds oder Wave-Dateien abzuspielen, stellt die CONZEPT 16 DLL-Schnittstelle das ideale Werkzeug dar.
Zum Abspielen von Wave-Dateien bietet uns die Win32-API die Funktion PlaySound() an.

BOOL PlaySound(
  LPCTSTR pszSound,
  HMODULE hmod,
  DWORD fdwSound
);

Die Funktion spielt Wave-Dateien oder Windows-System Sounds ab. Außerdem kann sie dazu verwendet werden die aktuelle Wiedergabe zu stoppen.

// Wave-Datei abspielen
PlaySound("c:\windows\media\chord.wav",NULL,SND_FILENAME | SND_ASYNC);

// System-Sound abspielen
PlaySound(SYSTEM_SOUND_EXCLAMATION,NULL,SND_ALIAS_ID | SND_ASYNC);

// Sound-Wiedergabe stoppen
PlaySound(NULL,0,0);
Eine Sound-Bibliothek

Die DLL soll dem CONZEPT 16-Entwickler drei neue Funktionen zur Verfügung stellen:

  • Abspielen eines System-Sounds
  • Abspielen einer Wave-Datei
  • Stoppen der Wiedergabe

Hierfür definieren wir zunächst in CONZEPT 16 die folgenden Kommandos:

define
{
  DLL_COMMAND_PLAY_SYSTEM_SOUND   : 1
  DLL_COMMAND_PLAY_WAVEFORM_SOUND : 2
  DLL_COMMAND_STOP_PLAYING        : 3
}

Zum Abspielen einer Sounddatei kann ein CONZEPT 16-Entwickler dann z.B. einfach folgenden Code implementieren:

DllCall(tDll,DLL_COMMAND_PLAY_WAVEFORM_SOUND,'mysound.wav');

Den DLL-Deskriptor stellt die Funktion DllLoad() zur Verfügung.

tDll # DllLoad('c16_dll_example.dll');

Der Befehl lädt die DLL in den Adressraum des Clients. Wird die DLL nicht mehr benötigt, muss DllUnload() aufgerufen werden.

DllUnLoad(tDll);

Windows pflegt für jede geladene DLL einen internen Referenzzähler. Mehrere Aufrufe von DllLoad() derselben DLL führen nicht dazu, dass eine weitere Instanz der DLL geladen wird. Windows erhöht lediglich seinen Referenzzähler. Aus diesem Grund muss für jede erfolgreich geladene DLL auch wieder ein entsprechender DllUnload()-Aufruf stattfinden, was zum dekrementieren des Referenzzählers führt. Die DLL wird erst wieder entladen, wenn der interne Referenzzähler den Wert null erreicht hat.

Die Einstiegsfunktion

Damit CONZEPT 16 erfolgreich mit der DLL zusammenarbeitet, muss diese eine Funktion namens C16_DLLCALL() exportieren. Dies wird bei Visual Studio am einfachsten durch die Definition einer Export-Datei erreicht.

LIBRARY	"c16_dll_example"
EXPORTS
  C16_DLLCALL @ 1

Die Anweisungen in der Datei (c16_dll_example.def) teilen dem Linker mit, dass die Funktion C16_DLLCALL() aus der DLL exportiert werden soll.

Diese Funktion wird immer dann von CONZEPT 16 aufgerufen, wenn ein DllCall()-Aufruf seitens CONZEPT 16 stattfindet. Sie bildet sozusagen den Einstiegspunkt für die DLL. Der folgende C-Code zeigt die Implementation für unsere Beispiel-DLL.

vERROR C16API C16_DLLCALL
(
  vC16_CCB*     aCCB  // Call Control Block (in/out)
)
{
  vLONGs	tCommandID;

  if (!GetArgInt(aCCB,1,tCommandID))
    return(DLL_ERROR_COMMAND_FETCH);

  switch (tCommandID)
  {
    case DLL_COMMAND_PLAY_SYSTEM_SOUND :
      return(PlaySystemSound(aCCB));

    case DLL_COMMAND_PLAY_WAVEFORM_SOUND :
      return(PlayWaveformSound(aCCB));

    case DLL_COMMAND_STOP_PLAYING :
      return(StopPlaying(aCCB));

    default :
      return(DLL_ERROR_COMMAND_UNKNOWN);
  }
}

Da diese Funktion von CONZEPT 16 aufgerufen wird, ist die richtige Aufrufkonvention wichtig. Diese definiert, in welcher Reihenfolge Argumente übergeben werden, und ob diese vom Aufrufer (CONZEPT 16) oder vom Aufgerufenen (DLL) wieder vom Stack entfernt werden müssen.

CONZEPT 16 verwendet die ‘stdcall’-Aufrufkonvention. Diese ist unter Windows sehr gebräuchlich und wird auch von der Windows eigenen Win32-API selbst verwendet. Bei dieser Konvention werden die Argumente von rechts nach links auf den Stack gelegt und der Aufrufer ist für die Bereinigung des Stacks verantwortlich.

Betrachtet man den Funktionskopf von C16_DLLCALL(), fällt der Blick auf C16API. Durch die Angabe dieses Makros wird die korrekte Aufrufkonvention gesetzt. Das Makro selbst ist in der Datei c16_dll.h definiert.

Die Implementation der Funktion gestaltet sich recht einfach. Abhängig vom übergebenen Kommando wird die entsprechende Soundfunktion durchgeführt. Das Kommando sowie die anderen von CONZEPT 16 – über DllCall() – angegebenen Argumente befinden sich im ‘Call Control Block’ (CCB), der über den Zeiger aCCB an C16_DLLCALL() übergeben wird.

Wie z.B. das Kommando aus dem CCB ermittelt wird, erklärt ein Blick in die Funktion GetArgInt(), welche ebenfalls Bestandteil des Beispiels ist.

vBOOL GetArgInt
(
  const vC16_CCB*    aCCB,       // Call Control Block (in)
  const vINT         aPosition,  // Nummer des Argumentes (in)
  vLONGs&            aValue      // Wert (out)
)
{
  vINT               tType;
  vERROR             tError =
    aCCB->C16_ArgInfo(aCCB->InstHdl,aPosition,&tType,NULL,NULL,NULL,NULL);

  if (tError != C16ERR_OK || tType != _TypeInt)
    return(FALSE);

  tError = aCCB->C16_ArgRead(aCCB->InstHdl,aPosition,0,&aValue);
  return(tError == C16ERR_OK);
}

Dort wird durch den Aufruf von C16_ArgInfo() ermittelt, ob es sich beim Argument tatsächlich um einen Integer-Wert handelt. Ist dies der Fall, wird anschließend mit C16_ArgRead() die Kommando-ID als Integer-Wert ausgelesen. C16_ArgInfo() und C16_ArgRead() sind Teil des CCB.

Dieser enthält neben diesen zwei Funktionen noch viele weitere. So kann beispielsweise mit C16_RecRead() ein Datensatz vom Client gelesen werden. Hier lohnt sich in jedem Fall ein Blick in die Datei c16_dll.h oder in die CONZEPT 16-Dokumentation.

Das Beispiel

Das Beispiel benötigt die CONZEPT 16 Version 5.7.01. Zum Testen gehen Sie bitte wie folgt vor:

  • Entpacken Sie den Inhalt
  • Tragen Sie die Datenbank (c16_dll.ca1 beim Server ein)
  • Kopieren Sie die Datei c16_dll_example.dll ins Client-Verzeichnis
  • Starten Sie den Client mit der Datenbank und dem Benutzer START.

Download

Beispiel und Visual Studio-Projekt c16_dll.zip (6.68 MB)
Sie müssen angemeldet sein, um die Datei herunterladen zu können.
Keine Kommentare

Kommentar abgeben