Forum: PC-Programmierung Bestehende Library an C# anbinden


von Stefan (Gast)


Lesenswert?

Servus,

ich habe ein bestehendes Programm in C# in Visual Studio. Muss nun in 
der Praktika-arbeit ein Kraftmesser mit der Zur Verfügung stehender 
H-und DLL Datei 
(http://www.me-systeme.de/software/programmierung/megsv.html) anbinden. 
DLL anbinden geht nicht, gibt ein Fehler raus. Laut Bedienungsanleitung 
muss ich Header-Datei MEGSV.H mit dem Programm verbinden.
1
Die Anbindung an die Programmiersprachen C und C++ erfolgt mit der Header->Datei
2
MEGSV.H sowie einer Link-Library, die zum Linken als Bibliothek mit angegeben wird und
3
die Verbindung zur DLL herstellt. Link-Libraries für viele Compiler und Entwicklungsumgebungen
4
können mit Hilfe dazugehöriger HIlfsprogramme direkt aus der DLL erstellt
5
werden. Für Microsoft® VisualC++® 6.0 (Eingetragene Warenzeichen der Microsoft Corp.)
6
liegt eine derartige Bibliothek bereits bei (MEGSV.LIB). Die Anwendung kann dem
7
Demoprogramm und der zugehörigen Stapelverarbeitungsdatei DEMOMSCV.BAT
8
entnommen werden.

Ich verstehe leider aus dieser Bedienungsanleitung oben nur Bahnhof. 
Kann mir einer ein Tipp geben wie ich die Bibliothek anbinden kann?

Gruss Stefan

von Peter II (Gast)


Lesenswert?

Stefan schrieb:
> Ich verstehe leider aus dieser Bedienungsanleitung oben nur Bahnhof.

naja, da geht es auch um C und nicht um .net.


du musst die dll aufrufe aus der Header Datei in C# umwandeln.

https://msdn.microsoft.com/de-de/library/cc431203.aspx

Dafür sollte man aber zumindest C(++) können, damit man die Headerdatei 
auch versteht.

von Wurfbrot (Gast)


Lesenswert?

Da gibt es zwei Möglichkeiten: P/Invoke (siehe Link von Peter) oder ein 
C++/CLI-Wrapper.

Ich würde in diesem Fall P/Invoke verwenden, da die meisten Signaturen 
sehr einfach sind:

int WINAPI GSVresetStatus(int no);
long WINAPI GSVgetScale(int no);
int WINAPI GSVsetBridgeInternal(int no, int bi);
int WINAPI GSVreadSamplingRate(int no, double *freq, int *factor);
...

Sporadisch noch ein 'void *buffer' als Parameter.

Teilweise könnte man einfach mit Copy & Paste arbeiten oder ein kleines 
Script (z.B. mit Python) schreiben, da ziemlich viele Funktionen jeweils 
die gleiche Signatur haben.
Du könntest eine Datei erstellen, in der du die C-Prototypen 
entsprechend sortierst; dann geht es evtl. schneller. Natürlich erst 
einmal in P/Invoke einlesen und es mit ein paar Funktionen testen 
(initialisieren und dann GSVversion z.B.).

von Stefan (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

danke für die schnelle Antwort. An sich ist das Tutorial klar. Jedoch 
wenn ich dem Folge, bekomme ich beim Aufruf der Funktion GSVactivate(9, 
100) einen Fehler. Dabei habe ich versucht die DLL und die H Datei 
einzulesen. Bede geben den gleichen Fehler.
1
{"An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)"}

Dies ist mein Code:
1
using System.Runtime.InteropServices; // for importing of the C++ files
2
3
namespace Nmfg
4
{
5
    public partial class Production : MetroForm
6
    {
7
        //[DllImport("D:\\11_SoftEng\\07_MfgSW\\packages\\ME_GSV\\Megsv.h")]
8
       [DllImport("D:\\11_SoftEng\\07_MfgSW\\packages\\ME_GSV\\MEGSV.dll")]
9
        public static extern int GSVactivate(int no, long buffersize);
10
11
        public Production(string projectSelected)
12
        {
13
            InitializeComponent();
14
            connectGSV();
15
        }
16
17
        private void connectGSV()
18
        {
19
            GSVactivate(9, 100);
20
        }
21
    }
22
}

Kann mir einer sagen was ich falsch mache? Die H und DLL Datei habe ich 
angehängt.

Gruss Stefan

von Peter II (Gast)


Lesenswert?

die Meldung deutet auf ein 32 vs 64bit Problem hin.

Dein C# Programm muss passend zur dll sein. Es darf nicht auf "any cpu" 
stehen.
Außerdem sollte keine absoluten Verzeichnisse verwenden, lege die DLL 
einfach ins gleiche Verzeichnis wie die exe und verwende dann nur den 
dll namen.

von Wurfbrot (Gast)


Lesenswert?

@Stefan
Ich habe es eben mal ausprobiert; funktionierte auf Anhieb. Wie Peter 
schon schrieb: Das ist bei dir ein 32-/64-Bit-Problem.

Falls du die 64-Bit-DLL aus megsv_x64.zip verwendest: Vergiss die für 
den Moment (oder überhaupt; 32-Bit-Programme laufen auch unter 
64-Bit-Windows einwandfrei).

Installiere setup-kit150.exe; dabei wird die 32-Bit-Version der DLL im 
Systemverzeichnis abgelegt. Entweder du lässt sie einfach dort oder du 
verschiebst sie in dein Projektverzeichnis, ziehst sie in den Solution 
Explorer und änderst die Option zum Kopieren in den Eigenschaften (dann 
wird die DLL automatisch ins gleiche Verzeichnis kopiert, in dem deine 
exe liegen wird). In beiden Fällen musst du bei DllImport keinen Pfad 
angeben.

Um Peter zu wiederholen: Achte darauf, dass deine Anwendung als 
x86-Assembly erzeugt wird, also nicht x64 oder "Any CPU" (denn das ist 
auf einem 64-Bit-System auch x64), sonst kann es nicht klappen.

Noch ein Punkt, denn du vermutlich übersehen hast: Du musst dir die 
Größe der Typen genau ansehen. Z.B. ist ein 'long' in C# immer 64 Bit 
lang, in der 32-Bit MEGSV.dll aber 32 Bit.

Falsch wäre also
[DllImport("MEGSV.dll")]
public static extern long GSVversion();
in 32 Bit (DLL und dein Programm).

Dann wird ein 64-Bit-Wert erwartet (long in C#), aber nur ein 
32-Bit-Wert geliefert. D.h, dass die DLL zwar geladen wird etc., im 
Ergebnis hier aber zur Hälfte Müll steht und es entsprechend zufällig 
wäre.

In 32 Bit (was ich, wie gesagt, empfehlen würde) wäre das hier korrekt:

[DllImport("MEGSV.dll")]
public static extern int GSVversion();

Entsprechend funktioniert auch dieses kleine Konsolenprogramm (als 
Version wird 1.50 angezeigt):
1
using System;
2
using System.Runtime.InteropServices;
3
4
// Nur für die 32-Bit MEGSV.dll. Im .NET-Projekt muss x86 ausgewählt sein.
5
6
namespace megsv
7
{
8
  public static class MEGSV
9
  {
10
    public static bool IsErrorCode(this int value) => value == GSV_ERROR;
11
12
    [DllImport("MEGSV.dll")]
13
    public static extern int GSVversion();
14
15
    private const int GSV_ERROR = -1;
16
  }
17
18
  class Program
19
  {
20
    static void Main(string[] args)
21
    {
22
      var ver = MEGSV.GSVversion();
23
      if (ver.IsErrorCode())
24
      {
25
        Console.WriteLine("Fehler beim Aufruf einer Funktion.");
26
      }
27
      else
28
      {
29
        var niceVer = ver.Split();
30
        Console.WriteLine($"Version: {niceVer.Item1}.{niceVer.Item2}.");
31
      }
32
    }
33
  }
34
35
  static class Extensions
36
  {
37
    public static Tuple<short, short> Split(this int value)
38
      => Tuple.Create((short)(value >> 16), (short)(value & 0xffff));
39
  }
40
}

Mehr kann ich natürlich ohne die Hardware nicht ausprobieren.

von Peter II (Gast)


Lesenswert?

Wurfbrot schrieb:
> Entsprechend funktioniert auch dieses kleine Konsolenprogramm

bin von deinem Beispiel etwas verwirrt.
1
public static extern int GSVversion();
2
3
4
var ver = MEGSV.GSVversion();

damit ist doch die Variable ver ein int.
1
if (ver.IsErrorCode())

das sollte doch dann gar nicht gehen? Was übersehe ich?

von Wurfbrot (Gast)


Lesenswert?

Peter II schrieb:
> das sollte doch dann gar nicht gehen? Was übersehe ich?

Das ist (wie "Split") eine Erweiterungsmethode.
https://msdn.microsoft.com/de-de/library/bb383977.aspx

Du kannst auch schreiben

[2]
if (MEGSV.IsErrorCode(ver))

und (optional) das "this" in IsErrorCode löschen:
public static bool IsErrorCode(int value) => value == GSV_ERROR;

Oder gleich mit GSV_ERROR vergleichen (muss dann natürlich public sein):

[3]
if (ver == MEGSV.GSV_ERROR)

Persönlich finde ich

[1]
if (ver.IsErrorCode())

aber am eingängigsten; außerdem ist der Wert von GSV_ERROR dann wie in 
[2] nicht öffentlich, was ich bevorzugen würde.
Allerdings sollte man es generell mit Erweiterungen der eingebauten 
numerischen Typen nicht übertreiben.

BTW: Du hast nichts zu der C#-Version geschrieben, darum bin ich von C# 
6 (VS 2015) ausgegangen. Falls du eine (etwas) ältere Version benutzt, 
solltest du aus
1
public static Tuple<short, short> Split(this int value)
2
  => Tuple.Create((short)(value >> 16), (short)(value & 0xffff));
1
public static Tuple<short, short> Split(this int value)
2
{
3
  return Tuple.Create((short)(value >> 16), (short)(value & 0xffff));
4
}

machen und aus
1
Console.WriteLine($"Version: {niceVer.Item1}.{niceVer.Item2}.");
1
Console.WriteLine("Version: {0}.{1}.", niceVer.Item1, niceVer.Item2);

Das war aber ohnehin bloß als Konsolen-Mini-Beispiel gedacht; du kannst 
es ja gestalten, wie du willst. Allerdings würde ich schon eine eigene 
Klasse (wie hier "MEGSV") für die DllImport-Attribute, Deklarationen und 
evtl. ein paar Hilfsmethoden verwenden und diese auf keinen Fall in eine 
Form stopfen ...

Falls dich das "MEGSV." vor den Aufrufen stört (MEGSV.GSVversion() etc.) 
- die Funktionen sind ja ohnehin dadurch "gekennzeichnet", dass ihre 
Namen alle mit GSV beginnen - und du C# 6 verwendest, kannst du dir mal 
"static using" ansehen.

von Peter II (Gast)


Lesenswert?

Wurfbrot schrieb:
> Das ist (wie "Split") eine Erweiterungsmethode.
> https://msdn.microsoft.com/de-de/library/bb383977.aspx

ok, das kannte ich noch nicht. Finde es hier aber nicht sinnvoll.

Das Abfrage eines Fehler wird schnell mal vergessen, GetVersion sollte 
einfach eine Exception werfen, wenn es nicht abgefragt werden kann.

von Wurfbrot (Gast)


Lesenswert?

@Peter
Ich hatte nicht gesehen, dass der Beitrag von dir war ... Wirkt darum 
nun etwas komisch, weil ich in meinem den TO angesprochen habe.

Peter II schrieb:
> Das Abfrage eines Fehler wird schnell mal vergessen, GetVersion sollte
> einfach eine Exception werfen, wenn es nicht abgefragt werden kann.

Ja; dann müsste man Wrapper-Methoden schreiben. Vielleicht in dieser Art 
(oder wie auch immer im Detail):
1
public static class MEGSV
2
{
3
  public static Tuple<short, short> GetVersion()
4
  {
5
    var ver = GSVversion();
6
    if (IsErrorCode(ver))
7
    {
8
      throw new ...Exception(...);
9
    }
10
    else
11
    {
12
      return ver.Split();
13
    }
14
  }
15
16
  [DllImport("MEGSV.dll")]
17
  private static extern int GSVversion();
18
19
  private const int GSV_ERROR = -1;
20
21
  private static bool IsErrorCode(int value) => value == GSV_ERROR;
22
}

von Wurfbrot (Gast)


Lesenswert?

Oder wenn man nicht gerne tippt (und das Ganze mehrfach vorkommt) 
vielleicht so etwas:

1
public class MEGSV
2
{
3
  public Tuple<short, short> GetVersion() => Check(GSVversion()).Split();
4
5
  [DllImport("MEGSV.dll")]
6
  private static extern int GSVversion();
7
8
  private const int GSV_ERROR = -1;
9
  private bool IsErrorCode(int value) => value == GSV_ERROR;
10
11
  private int Check(int value, [CallerMemberName] string caller = "")
12
  {
13
    // Nur als Beispiel; besser eigene Exception-Klasse implementieren.
14
    if (IsErrorCode(value)) throw new Exception("Source: " + caller);
15
    else return value;
16
  }
17
}

von Stefan (Gast)


Lesenswert?

Hallo Wurfbrot, Hallo Peter II,

also das sieht gut aus, danke für eure Hilfe. Ich habe nun paar 
Funktionen ausprobiert und das funktioniert soweit. Bei eine Umwandlung 
von Datentypen habe ich jedoch etwas Probleme. Es geht um diese Funktion 
aus
1
//MEGSV.h
2
int WINAPI GSVread(int no, double *ad);

in meinem Code habe ich geschrieben
1
public static class MEGSV
2
  {
3
    [DllImport("MEGSV.dll")]
4
     public unsafe static extern short GSVread(short no, double *ad);
5
  }
6
7
  private void connectGSV()
8
  {
9
    MessageBox.Show((MEGSV.GSVread(9, 0.1)).ToString()); 
10
  }
1
ad 
2
Zeiger auf Gleitkommavariable doppelter Genauigkeit.
3
Gelesener Meßwert ohne Berücksichtigung von Eingangsempfindlichkeit,
4
Verstärkung und Anzeigenormierung.
5
(Delphi™ und VB: Die Gleitkommavariable wird als Referenz übergeben.)

Programm startet ohne Probleme, zeigt mir jedoch einen Fehler an sobald 
connectGSV() aufgerufen wird.
Den Fehler den ich bekomme ist: {"Attempted to read or write protected 
memory. This is often an indication that other memory is corrupt."}

Gruss und Danke

von Markus V. (valvestino)


Lesenswert?

So wäre es besser:
1
  {
2
    [DllImport("MEGSV.dll")]
3
    public static extern short GSVread(short no, ref double ad);
4
  }
5
6
  private void connectGSV()
7
  {
8
    double value = 0.0d;
9
    MessageBox.Show((MEGSV.GSVread(9, ref value)).ToString()); 
10
  }

Vermutlich reicht statt "ref" auch ein "out". Dann wird die 
Initialisierung von value nicht benötigt.

Grüße
Markus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.