Forum: PC-Programmierung C++ Dll in C# nutzen


von Quereinsteiger (Gast)


Lesenswert?

Hallo zusammen,

im Rahmen einer Studienarbeit schreibe ich ein Programm mit 
Benutzeroberfläche in C# zur Steuerung eines Prüfstandes. Dazu muss ich 
3 Werte an ein anderes Programm, welches den Prüfstand steuert 
übergeben. Momentan schreibe ich die Werte in eine txt Datei und starte 
eine von mir geschriebene Konsolenanwendung, die die txt ausliest und 
über die Funktionen einer Dll die übergabe realisiert. Das funktioniert 
auch alles ganz wunderbar.
Mein Betreuer meinte das ist keine saubere Lösung (womit er ja auch 
irgendwie recht hat) und ich soll probieren aus meinem C# Programm 
direkt die Werte per Funktionsaufruf zu übergeben.
Dazu habe ich dieses kleine Tutorial gefunden aber es noch nicht 
Umsetzten können. http://www.functionx.com/csharp2/libraries/cppcli.htm

Meine eigentliche Frage: Gibt es noch eine Alternative ? Ist die Lösung 
mit dem schreiben in die txt Datei der Werte wirklich so viel langsamer 
?

Bin für Anregungen und Vorschläge dankbar!

von Peter II (Gast)


Lesenswert?

Quereinsteiger schrieb:
> Meine eigentliche Frage: Gibt es noch eine Alternative ? Ist die Lösung
> mit dem schreiben in die txt Datei der Werte wirklich so viel langsamer

ja ist sie, braucht viel mehr ringsrum - was ist wenn du keine 
Schreibrechte hast, was ist wenn die datei gleichzeitig von mehre 
Nutzern angelegt wird? Das geht zwar ob schön ist es nicht.

C++ dll hat den nachteil das die funktionen sehr merkwürdige namen 
bekommen. Sicher das es c++? Dann eine normale dll kann man noch einfach 
mit 3-4 Zeilen c# aufrufen.

von Sven H. (dsb_sven)


Lesenswert?

Für normal sieht das etwa so aus:
1
 [DllImport("Deine.dll")]
2
        public static extern rückgabetyp Funktionsname(Parametertyp parameter...)
3
4
// Aufruf dann einfach wie jede andere statische Funktion.

von Quereinsteiger (Gast)


Lesenswert?

Ja das ist C++.

Die Funktion die ich aufrufen will ist in der header Datei so 
deklariert:

typedef long (*PUTPARAMETER)(const char* , double*);

Aufrufen tue ich in meiner Konsolenanwendung die Funktion so:

PutParameter("Rn2/Mdl/Ini/Pen/PosX", &Cxwert);
PutParameter("Rn2/Mdl/Ini/Pen/PosY", &Cywert);


Würde das dann inetwa so aus sehen ?

 [DllImport("Ca.dll")]
 public static extern long ??? (???, ???)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Quereinsteiger schrieb:
> Die Funktion die ich aufrufen will ist in der header Datei so
> deklariert:
>
> typedef long (*PUTPARAMETER)(const char* , double*);

Das ist ein Funktionspointer. Bist Du Dir da sicher?

Du kannst eine "C++-Funktion" nur aufrufen, wenn sie eine statische 
Memberfunktion oder gar keine Memberfunktion ist. Andernfalls fehlt Dir 
die erforderliche Laufzeitumgebung in Form des zugehörigen Objektes.

Die Namen der Funktionen in der DLL kannst Du mit
1
dumbin /exports meine.dll
herausfinden, vorausgesetzt, ein MS-C/C++-Compiler ist installiert.

von Peter II (Gast)


Lesenswert?

Quereinsteiger schrieb:
> Würde das dann inetwa so aus sehen ?

 [DllImport("Ca.dll")]
 public static extern long PutParameter(String s, out double d)

so in der art

von Quereinsteiger (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Das ist ein Funktionspointer. Bist Du Dir da sicher?

Ja ganz sicher bin ich mir da nicht. Mit Pointern hatte ich in C++/C# so 
noch nichts am Hut.

In der Header Datei die zu der dll gehört steht noch das zu der 
Funktion.

long PutParameter(const char *pName, double *pVal);

Also meint ihr das würde funktionieren ? Dann häng ich mich da nochmal 
rein und gucke ob ich das so zum laufen bekomme.

von Thomas L. (tom)


Lesenswert?

Ja das funktioniert einwandfrei. Gibt sogar Tools die dir aus einer DLL 
eine Wrapper Klasse generieren können (funktioniert aber nur wenn die 
DLL eine Typelib exportiert).

Das ganze nennt sich P/Invoke. Schau dir mal den P/Invoke Interop 
Assistant an (frei verfügbar auf codeplex).

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Quereinsteiger schrieb:
> In der Header Datei die zu der dll gehört steht noch das zu der
> Funktion.
>
> long PutParameter(const char *pName, double *pVal);

Wenn das nicht als Bestandteil einer Klassendefinition in der 
Headerdatei drinsteht, dann musst Du nur noch den "dekorierten Namen" 
der Funktion herausfinden - sofern sie überhaupt einen hat (beim 
Erstellen von DLLs können Funktionen auch andere als die vom Compiler 
vorgegebenen Namen zugewiesen werden).
Wie Du herausfinden kannst, wie die Namen der von der DLL exportierten 
Funktionen heißen, habe ich bereits geschildert.

von Archie F. (archie)


Lesenswert?

Man kann relativ einfach selbst eine Wrapper Classe schreiben. Ich habe 
mal eine C++ DLL im C# Projekt verwendet. Habe dafür eine Klasse 
erstellt die auf die Dll Funktionen zugreift. Soweit ich noch weiß, 
musste man im Projekt die "unsafe" Programmierung zulassen, da C# 
unmanaged Programmierung nicht zulässt. Knifflig wird es, wenn die C++ 
Funktionen Pointer zurückgeben.

Hier ist eine gekürzte Version:

//Wrapper Class:
[c]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MYFUNCTION_Csharp
{
    class MyFunction
    {

     [StructLayout(LayoutKind.Explicit)]
        public struct System
        {
            [FieldOffset(0)]
            public ushort Model;
            [FieldOffset(2)]
            public ushort Option;
        }

        [StructLayout(LayoutKind.Explicit)]
        public class InfoIn
        {
            [FieldOffset(0)]
            public int SrcID;
            [FieldOffset(4)]
            public int DataType;
        }

        [DllImport("irbgrab.dll", CharSet = CharSet.Auto)]
        public static extern int SetParam
                                    (int SrcID, int param, double 
Value);

    }
}
[c/]

Verwendung im Programm:
MyFunction.SetParam(123, Convert.ToInt32(textBox_SourceNum.Text));



Vielleicht hilft es dir weiter.

von Sven H. (dsb_sven)


Lesenswert?

Das mit dem unsafe ist nur selten wirklich nötig. Pointer z.B. kann man 
mit IntPtr ganz gut abbilden.

Die Laufzeitumgebung setzt den dann in einen "echten" Pointer.

Alternativ dazu kannst du Variablen auch mit dem Schlüsselwort "ref" 
übergeben. Diese werden der c++ dll auch als Adressen gemarschallt.

Funktionspointer, wie z.B. für Callback Funktionen werden mit Delegaten 
realisiert.

Hier mal ein Beispiel, dass ich schon seit längerem im Einsatz habe:
1
[DllImport("NiCan.dll")]
2
public static extern uint ncCreateNotification(uint ObjHandler,
3
    uint DesiredState,
4
    uint Timeout,
5
    ref byte RefData,
6
    CallbackDef Callback);
7
8
public delegate uint CallbackDef(uint ObjHandle, uint State, uint Status, ref uint RefData);

Dabei stellt RefData dann schlussendlich einen Pointer auf einen 
benutzerspezifischen Datenhaufen aus Bytes dar.

Eventuell hilft auch [In, Out] so wie hier:
1
DllImport("NiCan.dll")]
2
public static extern void ncStatusToString(uint Status, uint SizeofString, [In, Out]char[] String);


(Alle Beispiele sind aus meiner Wrapperklasse für die National 
Instruments CAN Karten DLL für Visual C Anwendungen.)

von Quereinsteiger (Gast)


Lesenswert?

Erstmal vielen Dank für eure Hilfe!

Ich hab die .dll in meine Resources eingefügt und so importiert.

[DllImport("Ca.dll")]
public static extern long PutParameter(string s, double val);


Rufe die Funktion auf über:


PutParameter("Rns/Mdl/Ini/Pen/PosX", xwert);
PutParameter("Rns/Mdl/Ini/Pen/PosY", ywert);

Kompilieren tut er ohne Probleme. Wenn ich das dann am Prüfstand teste, 
meldet er mir einen Fehler "Error load on Library" beim Versuch Daten zu 
übergeben.

Bei meiner C++ Konsolenanwendung definiere ich zusätzlich

#define HOST  "172.30.11.34"
#define HOST1 "deaktiv"

und rufe dann die Funktion "LoadCaDriver()" auf. Und öffne das 
eigentliche Steuerungsprogramm mit ControlOpen(HOST1,0,1);

Habe jetzt in meinem C# Programm Host und Host 1 als String deklariert 
und versuche die beiden Funktionen auch aufzurufen. Aber da spielt mein 
Compiler nicht mit und meldet Fehler.

Was mich noch stutzig macht: Using Ca.dll; wird nicht akzeptiert. Das 
sollte er doch eigendlich kennen oder täusch ich mich da ?

Ich bleib dran und berichte euch ob es klappt :)

von Peter II (Gast)


Lesenswert?

Zeig uns Bitte mal das komplette konsolenprogramm, sonst wird das 
vermutlich nichts.

von Quereinsteiger (Gast)


Lesenswert?

1
 
2
#include "stdafx.h"
3
#include <iostream>
4
#include <stdio.h>
5
#include <fstream>
6
#include <string>
7
#include <cstdlib>
8
#include "Ca.h"
9
#include <stdlib.h>
10
11
#define HOST  "172.30.11.34"
12
#define HOST1 "deaktiv"
13
14
15
using namespace std;
16
17
18
int main(int argc, char *argv[])
19
{
20
  
21
  int y=0, x=0;
22
  string xwert, ywert, s;
23
  double Cxwert, Cywert;
24
  long ret;
25
  double dVal = 0;
26
27
28
29
  if((ret = LoadCaDriver()) < 0)
30
  {
31
    cout << "Fehler beim laden der Ca.DLL  ret=" << ret << endl;
32
    return -1;
33
  }
34
  
35
  ret = ControlOpen(HOST1,0,1);       // Verbinden Db server
36
    printf("ControlOpen(%s [%d])\n", HOST, ret );
37
  if(ret<0) 
38
    return ret;
39
  
40
    
41
  ifstream f;                     
42
    f.open("C:/RNS.txt", ios::in);          // Öffne Datei aus Parameter, ios::in -> nur lesen
43
      getline(f, s);                  
44
          xwert = ( s );
45
    getline(f, s);                  
46
          ywert = ( s );
47
    f.close();                    
48
                    
49
    if((xwert,",")!=0) { while(xwert[x]!=',') x++; xwert[x]='.'; }  // Umwandlung von Komma in Punkt zur Umwandlung in double
50
    
51
    if((ywert,",")!=0) {while(ywert[y]!=',') y++; ywert[y]='.';  }
52
          
53
    Cxwert = atof(xwert.data());
54
    Cywert = atof(ywert.data());
55
56
    cout << "der Cxwert ist:" << Cxwert << endl;
57
    cout << "der Cywert ist:" << Cywert << endl;
58
    
59
60
    ret = GetParameter("Rns/Mdl/Ini/Pen/Cntl", &dVal);
61
    
62
    
63
    cout << "GetParameter(\"Rns/Mdl/Ini/Pen/Cntl\"  ret= " << ret << endl;
64
  
65
66
    dVal = 1;
67
    ret = PutParameter("Rns/Mdl/Ini/Pen/PosX", &Cxwert);   
68
    ret = PutParameter("Rns/Mdl/Ini/Pen/PosY", &Cywert);  
69
    ret = PutParameter("Rns/Mdl/Inp/Pen/Cntl", &dVal);   // dVal = 1 Koordinaten anfahren
70
    
71
    Sleep(20);
72
    dVal = 0;
73
    ret = PutParameter("Rns/Mdl/Inp/Pen/Cntl", &dVal);  
74
      
75
        
76
        
77
    return 0;
78
}

von Thomas L. (tom)


Lesenswert?

Du hast mal mehrere Probleme.

Punkt 1: Du darfst kein kein using ca.dll machen. Das wird nicht 
funktionieren. Stattdessen sagst du Visual Studio er soll die ca.dll an 
die gleiche Stelle kopieren wie die exe die du erstellst, dann findet er 
die DLL.
1
public static extern long PutParameter(string s, double val);
Ist wahrscheinlich auch falsch. Du kannst keinen String übergeben, 
stattdessen übergibst du einen Pointer auf das erste Zeichen. Um das 
zuVereinfachen gibt es die Marshal Klasse. Ebenfalls übergibst du den 
double nicht als wert, sondern als Pointer, sieht dann etwa so aus (nur 
aus demKopf jetzt, die genaue Schreibweise musst du dir selber suchen)
1
public static extern long PutParameter([MarshalAs(UnmanagedType.LPTStr)] string s, ref double val);
Damit wird dafür gesorgt dass du zwar einen String angeben kannst, 
jedoch im Endeffekt der Pointer übergeben wird. 'val' wird ebenfalls als 
Referenz übergeben.

von Peter II (Gast)


Lesenswert?

Thomas Linder schrieb:
> public static extern long PutParameter(string s, double val);
> Ist wahrscheinlich auch falsch. Du kannst keinen String übergeben,
> stattdessen übergibst du einen Pointer auf das erste Zeichen.
das mit dem string passt schon, das ist so üblich. Man kann sogar noch 
als option mitgeben ob das als widecare oder nicht übergeben werden 
soll. Da ref hatte gefehlt.

von Peter II (Gast)


Lesenswert?


von Sven H. (dsb_sven)


Lesenswert?

Das was du da gezeigt hast ist aber nicht der c# Quellcode.

von Quereinsteiger (Gast)


Lesenswert?

Danke für eure Unterstützung.
Ich hab die Tage leider vergebens versucht das so in den Griff zu 
bekommen. Bekomme immer wieder verschiedene Fehler die zum Abbruch des 
Programmes führen.

Mir ist die Idee gekommen die Speicheradresse der Werte im C# Programm 
mit dem C++ Unterprogramm auszulesen und die Übergabe so ein wenig 
eleganter zu gestalten. Habe dazu dieses Tutorial gefunden.
http://www.online-tutorials.net/security/speicherzugriff-tutorial-teil-1/tutorials-t-27-63.html

Das sollte doch schneller und Ressourcenschonender sein, als die Werte 
aus der .txt Datei auszulesen oder ?

von Thomas L. (tom)


Lesenswert?

Sag halt mal welche Fehler das sind, poste den entsprechenden 
Sourcecode, dann ist man sicher klüger ;)

Das mit dem Unterprogramm ist einfach Pfusch.

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.