# Libnodave und Borland Builder 5.0



## Volkmer (21 Februar 2007)

Hallo!
Ich habe gerade angefangen mich mit Libnodave auseinander zu setzen.
Die Dateien habe ich aus dem Internet heruntergeladen und installiert.
* Aus dem DOS-Fenster lassen sich die Anwendungen problemlos starten.
* das Excel VBA Beispiel klappt.
* Ich habe das Modul12 in ein VB6 Programm eingebunden. Auch hier klappt der Zugriff auf die SPS mit dem IBH NetlINK über ein GUI.

Jetzt möchte ich eigentlich meine Standard-Programmierumgebung nutzen, den Borland Builder Version 5.0.

Hat jemand ein BeispielProjekt, wie die Funktionen korrekt definiert werden müssen?
Die DLL habe ich wie folgt eingebunden:

//---------------------------------------------------------------------------
#define THEDLL "Libnodave.dll"
HINSTANCE hDLL;

loaddll(char *name)
{
  char s[256];
  hDLL=LoadLibrary(name);
  if (hDLL == NULL)
    {
      wsprintf(s,"%s nicht gefunden!",name);
      MessageBox(GetFocus(),s,"Error",MB_OK|MB_ICONSTOP);
      return 0;
    }
  return 0;
}

Wird ohne Fehler kompiliert!

Beispiel für einen Funktionsaufruf in Borland: Ist das Richtig??
daveInternalStrerror(long en)
{typedef long (CALLBACK* LP2INT) (long ); LP2INT p;
  p = (LP2INT) GetProcAddress(hDLL,"daveInternalStrerror"); return p(en);
}

so sieht es korrekt in VB aus:
private Function daveInternalStrerror Lib "libnodave.dll" Alias "daveStrerror" (ByVal en As Long) As Long

Mit freundlichem Gruß
V.Jähn


----------



## Zottel (22 Februar 2007)

Volkmer schrieb:


> Jetzt möchte ich eigentlich meine Standard-Programmierumgebung nutzen, den Borland Builder Version 5.0.


Den kenne ich nicht, aber es sieht nach C aus, ja?


> Die DLL habe ich wie folgt eingebunden:
> ...
> Wird ohne Fehler kompiliert!


Schön, hat aber ja noch gar nichts mit Libnodave zu tun.


> daveInternalStrerror(long en)
> {typedef long (CALLBACK* LP2INT) (long ); LP2INT p;
> p = (LP2INT) GetProcAddress(hDLL,"daveInternalStrerror"); return p(en);
> }


Warum zum Teufel willst du dir die Mühe machen, die Funktionen über LoadLibrary und GetProcAddress einzubinden und alle Prototypen neu zu definieren? Ich kenne nur einen vernünftigen Grund um so etwas zu tun: Wenn du mehrere "Plug-In" -Dll hättest mit identischer Schnittstelle hättest und zur Laufzeit auswählen wolltest, ob du nun Libnodave oder eine andere Bibliothek, für sagen wir, Allen Bradley, nimmst.
Da das aber wohl nicht der Fall ist: Wenn du die Bibliothek auf "normale" Weise einbindest, stehen die Funktionsprototypen in nodave.h. 
#define BCCWIN
#define DAVE_LITTLE_ENDIAN
#include "nodave.h"
oder
#include "nodavesimple.h"


----------



## Volkmer (22 Februar 2007)

Hallo!

Mit dem Neudefinieren der Funktionen konnte ich mittlerweile die wesentlichen Funktionen ausführen (starten, stoppen der CPU, lesen und schreiben von Bytes - mit Ausnahme von Real/Float-Variablen).

Wenn ich die Header Dateien einbinde, bekomme ich nach der Compiliereung die Fehlermeldungen

 [Linker Fehler]Unresoved ecternal '__stdcall dave.....
..............

für alle aufgerufenen Funktionen.

Ich habe die notwendigen Header-Dateien in das Programmverzeichnis kopiert, ebenso die dll.

der Programmkopf lautet, wie folgt
#include <vcl.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
#include <functional>
#include "windows.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BCCWIN
#define DAVE_LITTLE_ENDIAN
#include "Unit1.h"
#include "nodave.h"
#include "openSocket.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

Ich verwende Borland C++ Builder Version 5.0

An dem automatisch?? generierten Makefile habe ich keine Änderungen vorgenommen.

Mit freundlichem Gruß
V.Jähn


----------



## Ralle (23 Februar 2007)

Kann man nicht auch die Delphi-Komponente nutzen, würde einiges an Arbeit ersparen, die ist allerdings mir Delphi7 geschrieben, ob das mit dem Builder5.0 paßt?


----------



## afk (23 Februar 2007)

Ralle schrieb:


> Kann man nicht auch die Delphi-Komponente nutzen, würde einiges an Arbeit ersparen, die ist allerdings mir Delphi7 geschrieben, ob das mit dem Builder5.0 paßt?


Die Komponente sollte mit allen Delphi-Versionen ab Delphi 6.0 funktionieren (eigentlich schon ab Version 5.0, hab ich aber schon lange nicht mehr getestet), nur mit Delphi 8.0 nicht, mangels Win32-Unterstützung in der Version 8.0.

Mit dem C++ Builder wird sie wohl in keiner Version funktionieren, dafür müßte die Komponente meines Wissens erst nach C++ portiert werden, was aber eigentlich kein Problem sein sollte (außer der Arbeit, die damit verbunden ist ). 


Gruß Axel


----------



## Ralle (23 Februar 2007)

Hm, ich dachte, ich hätte mal gelesen, daß eigentlich eh Delphi unter der Haube von BB5.0 steckt und alle Komponenten dort in Objectpascal vorliegen, man somit auch Delphi-Komponenten und Bibliotheken verwenden kann. Dazu bräuchte man dan wohl die vorkompilierten Dateien *.dcr


----------



## PeterEF (23 Februar 2007)

Ralle schrieb:


> Dazu bräuchte man dan wohl die vorkompilierten Dateien *.dcr


 
Also der BCB 5.0 auf meinem Rechner hier kann Pascal Units (*.PAS) in das aktuelle Projekt mit einbinden und dann natürlich auch übersetzen.


----------



## afk (23 Februar 2007)

Ralle schrieb:


> Hm, ich dachte, ich hätte mal gelesen, daß eigentlich eh Delphi unter der Haube von BB5.0 steckt und alle Komponenten dort in Objectpascal vorliegen, man somit auch Delphi-Komponenten und Bibliotheken verwenden kann. Dazu bräuchte man dan wohl die vorkompilierten Dateien *.dcr


Hab mich jetzt gerade mal schlau-gegoogled:
Offensichtlich kann der C++ Builder auch Delphi-Komponenten einbinden, wenn der Quellcode vorhanden ist und ein paar spezielle Anforderungen eingehalten werden, was bei meiner Komponente für libnodave der Fall ist.

Demnach müßte sich die Komponente auch mit dem C++ Builder kompilieren und einbinden lassen ... wenn das schon mal jemand gemacht hat, würde ich mich über eine kurze Info freuen !


Gruß Axel


----------



## Zottel (23 Februar 2007)

Volkmer schrieb:


> Wenn ich die Header Dateien einbinde, bekomme ich nach der Compiliereung die Fehlermeldungen
> 
> [Linker Fehler]Unresoved ecternal '__stdcall dave.....
> ..............
> ...


Das deutet darauf hin, daß der Compiler eine falsche Vorstellung von den Aufrufkonventionen hat. Warum, weiß ich jetzt nicht. Vermutungen:
1. Es ist noch was mit den "conditional defines" nicht in Ordnung. Aber BCCWIN und DAVE_LITTLE_ENDIAN hast du ja da stehen. Sicherheitshalber sollten sie UNMITTELBAR vor 
#include "nodave.h"
stehen.
2. Es ist was mit "mangled names": C++ kann ja zwei Funktionen gleichen Namens mit verschiedenen Parametern unterscheiden, Beispiel:
int tuwas(int: eineVariable){
}
int tuwas(float: eineVariable){
}
Der Compiler kommt klar, er weiß daß es zwei Funktionen sind, weil er C++ kann. Der Linker hat aber keine Ahnung davon und würde nur zwei Einsprungpunkte gleichen Namens "tuwas" finden. Darum hängt der Kompiler was dran, wie: "tuwas_int", "tuwas_float" oder "tuwas_i4","tuwas_f4" oder was auch immer. Das sind die "mangled names". Wenn dein Compiler nun denkt, die Funktionen aus "nodave.h" seien in C++ geschrieben, gibt er dem Linker mangled names und die gibt es in Libnodave.lib oder Libnodave.dll nun mal nicht . Abhilfe: Dem Compiler sagen, daß es "nur" C ist:



> extern "C" {
> #define BCCWIN
> #define DAVE_LITTLE_ENDIAN
> #include "nodave.h"
> ...


Allerdings meine ich, daß dafür schon "conditional defines" in nodave.h enthalten sind. Aber Probieren geht über Studieren.


----------



## Volkmer (23 Februar 2007)

Hallo!
Mittlerweile habe ich die DLL einbinden können! Man musste wie folgt vorgehen:

Mittels des Programms implib (im Lieferumfang des Builders) muss eine Library gebildet werden.

 => implib libnodave.lib libnodave.dll

=> Anschließend muss auch diese Library mit in das Projekt eingebunden werden!

Zum Schluss alles compilieren!

Die meisten Funktionen laufen nun - auch das Lesen von Float-Variabeln.

Allerdings habe ich nun ein Verständnisproblem mit den daveput-Funktionen.

Leider sind die Funktionen in den Dokumenten nur spärlich beschrieben.

Mir gelingt es immer nur eine Variablen in den Buffer zu schreiben. 

Mit freundlichem Gruß
V.Jähn

PS.: Ein Dank noch an Zottel für das Entwickeln und Bereitstellen der DLL!


----------



## Zottel (23 Februar 2007)

Volkmer schrieb:


> Hallo!
> Mittlerweile habe ich die DLL einbinden können! Man musste wie folgt vorgehen:
> 
> Mittels des Programms implib (im Lieferumfang des Builders) muss eine Library gebildet werden.
> ...


Ach so, da hat Borland immer noch ein anderes lib-Format. Und damit man es gleich jeder merkt, trägt es dieselbe Erweiterung .lib.


Volkmer schrieb:


> Allerdings habe ich nun ein Verständnisproblem mit den daveput-Funktionen.
> 
> Mir gelingt es immer nur eine Variablen in den Buffer zu schreiben.


Wie machst du das denn? Für einen C-Programmierer sollte ein Blick in den nodave.c reichen, um zu verstehen was davePutXX macht?
Ok, du hast einen Puffer. Das sind lauter Bytes, weil weder daveWriteBytes noch der Speicher einer S7 irgendeine Ahnung von Mehrbyte-Variablen haben:

```
unsigned char [222] meinPuffer;
// Darauf zeigt ein Zeiger:
uc * einZeiger; 
einZeiger=meinPuffer;
// oder, gleichbedeutend:
uc * einZeiger; einZeiger=&meinPuffer[0];
// Nun hast du eine 3 PC-Variablen vom typ  Byte:
unsigned char a,b,c;
// Mit davePut8() kopierst du a in den Puffer
davePut8(einZeiger, a)
// meinPuffer[0] enthält nun den Wert von a;
// Mit davePut8() kopierst du b in den Puffer
davePut8(einZeiger, b)
// und Scheisse, meinPuffer[0] enthält nun den Wert von b, a ist
// ueberschrieben;

/** Deshalb nun richtig: */
uc * einAndererZeiger;
// Mit davePut8() kopierst du wieder a in den Puffer, aber diesmal läßt du
// dir einen Zeiger zurückgeben:
einAndererZeiger=davePut8(einZeiger, a);
// meinPuffer[0] enthält nun den Wert von a;
// einAndererZeiger zeigt nun auf die nächste freie
// Position im Puffer, also auf meinPuffer[1]
// Mit davePut8() kopierst du wieder b in den Puffer, aber an der freien
// Position und wieder läßt du dir einen Zeiger zurückgeben:
einAndererZeiger=davePut8(einAndererZeiger, b)
//  meinPuffer[1] enthält nun den Wert von b, a ist
// in meinPuffer[0] erhalten geblieben
// einAndererZeiger zeigt nun auf die nächste freie
// Position im Puffer, also auf meinPuffer[2]
```
Das könntest du hier einfacher haben: meinPuffer[1]=b;meinPuffer[0]=a;
Wenn du aber eine Variable vom Typ short int hast (INT in der S7), gibt es ein Problem: Die Zahl 4711 (0x1267 hexadezimal) steht im Speicher einer S7 als 0x12 0x67, aber im Speicher eines PC als 0x67 0x12. Damit libnodave und seine Anwendungen Daten der S7 ohne Änderung am SPS-Programm lesen/schreiben können, muß das Tauschen der Bytes auf PC-Seite erfolgen. Für dein short int gibt es dafür davePut16:


```
//  Vom vorigen Beispiel enthält meinPuffer[1] noch den Wert von b, a ist
// in meinPuffer[0] erhalten geblieben. einAndererZeiger zeigt auf die 
// nächste freie Position im Puffer, also auf meinPuffer[2]. Dorthin willst
// du nun die Variable eau schreiben:
short int eau;
eau=4711;
einAndererZeiger=davePut16(einAndererZeiger, eau);
//Dein Puffer sieht nun so aus: meinPuffer[2]=0x12, meinPuffer[3]=0x67.
// Du schreibst das Ganze, also 4 Byte in die S7 in DB3 ab DBB14:
daveWriteBytes(meineVerbindung, daveDB, 3, 14, 4, meinPuffer);
in der S7 steht nun:
DB3.DBB14: Wert von a
DB3.DBB15: Wert von b
DB3.DBW16: 4711 (=16#1267)
```



> PS.: Ein Dank noch an Zottel für das Entwickeln und Bereitstellen der DLL!


Bitte. Spenden werden auch gerne genommen.


----------



## Zottel (23 Februar 2007)

Irgendwie geht bei mir "Ändern" gar nicht. Wollte noch ergänzen, das der Zeiger einAndererZeiger nach davePut16 um zwei Bytes weiterrückt, also auf meinPuffer[4] und damit wieder auf die nächste freie Speicherstelle zeigt.


----------

