Laden...

Unmanaged Dll C++ die Funktionen in C Sharp einbinden

Erstellt von doubleII vor 7 Jahren Letzter Beitrag vor 7 Jahren 16.837 Views
D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren
Unmanaged Dll C++ die Funktionen in C Sharp einbinden

Hallo zusammen, Ich habe folgendes Problem:

Ich habe eine C# Application und ein C++ Projekt. MS Visual.net 2015.
Wie rufe ich jetzt von C# eine C++ Funktionen auf.
Suche schon einige Zeit und hab leider noch kein passendes Bsp. gefunden.
Wo könnte man mehr Info findet?
Ich habe eine Toolbox und der Code für sie ist auf C++ geschrieben, wie man oben als Beispiel eine Funktion sehen kann. Dll Datei habe ich zur Verfügung.
Ich wäre sehr dankbar , wenn mir jemand ein bisschen mehr erklären kann.
Vielen Dank!

Hier ist eine Funktion von dem C++ Code:
C ++ Funktion

int Toolbox::Initialize(LPCSTR systemPath, LPCSTR license, DLL_LogFunc* logFunc)
{
   if (m_initialize) return m_initialize((char*)systemPath,(char*)license,logFunc);
   return -1;
}

C Sharp

using System.Runtime.InteropServices;

namespace Project_1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        [DllImport("Toolbox.dll")]
        public static extern int Initialize( ????? ); // Funktion keine Methode. Was muss ich zwischen Klammern schreiben???
        private void btnExecute_Click(object sender, EventArgs e)
        {
              initialize( ???? );
        }
    }
Y
102 Beiträge seit 2005
vor 7 Jahren

Hallo,

LPCSTR ist ein string


[DllImport("Toolbox.dll", CharSet = CharSet.Ansi)]
public static extern int Initialize( string systemPath, string license,  IntPtr logFunc);

Zu dem DLL_LogFunc solltest du in der API nachlesen wie hierzu die Definition aussieht. Aber einfach gesagt erwartet sich hier die C++ dll einen Pointer zu einer Methode.
Das kann man mittels eines delegate realisieren.

Sieh hierfür: Marshal Function Pointer

Beispiel für das arbeiten mit einem Delegate:


[UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void GatewayCallback(uint arg0, uint arg1, uint arg2, IntPtr arg3);

[DllImport(@"XXXX.dll",
            CharSet = CharSet.Ansi,
            EntryPoint = "CommSetCallback",
            SetLastError = true,
            CallingConvention = _callingConvention)]
        public static extern void CommSetCallback(GatewayCallback fCall);

Anschließend ist dann noch die Frage des Pointers:

DLL_LogFunc*

der * zeigt an das es sich um einen Pointer handelt.

W
872 Beiträge seit 2005
vor 7 Jahren

Mittels DllImport kannst Du nur auf C Funktionen und nicht auf C++ Methoden/Objekte zugreifen.
Ob Managed C++ oder ein C Wrapper weniger Arbeit ist, muss Du selber prüfen.

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Vielen Dank für die Info erst mal!

Ich habe heute ein Tipp bekommen. Ich kann die C++ Datei weglassen. D.h. ich darf direkt auf die dll Datei zugreifen (dll Datei ist auf Delphi geschrieben), also ich brauche keine Wrapper Klassen zu erstellen (so weit wie ich gelesen habe). Die Funktionen existieren in der dll Datei ich muss sie ansprechen. Es wurde mir so erklärt, dass ich das C++ Projekt als Muster nutzen kann. Ich habe ins Internet rescher-schiert und habe den Code so geschrieben:

 
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;

namespace Project_1
        private IntPtr logFung;   
     
        [DllImport("Toolbox.dll", CharSet = CharSet.Auto)]
        static extern int Initialize( string systemPath, string license, IntPtr logFung );

        [DllImport("ScorpionVisionToolbox.dll", CharSet = CharSet.Auto )]
        static extern int finalize();

        private void btnExecute_Click(object sender, EventArgs e)
        {
            // Windows DLL (non-.NET assembly)
            //systemPath = Environment.ExpandEnvironmentVariables("%SystemDrive%");
            //if (!systemPath.Trim().EndsWith(@"\"))
            //  systemPath += @"\";
            
            systemPath = @"E:\DLL\Debug\Toolbox.dll";
            

            try
            {
                Assembly assem = Assembly.LoadFile(systemPath);
            }
            catch (DllNotFoundException ex)
            {
                MessageBox.Show(ex.Message);
            }

            //nur als Beispiel 
            license = "F7LRmIFGHcJkqAZfPV4sN1Tuga2SyKJeBL3ZJ0SBZPwRpoeXb8wZUYVcO3EQcwPUfzTQDcOlMC";
            Initialize(systemPath, license, logFung);

 
        }
    }
}

es zeigt mir folgender Fehler:
Ein Ausnahmefehler des Typs "System.IO.FileNotFoundException" ist in mscorlib.dll aufgetreten.

Zusätzliche Informationen: Das System kann die angegebene Datei nicht finden. (Ausnahme von HRESULT: 0x80070002)
Ich habe unter Projekt -> Eigenschaften -> Estellen auf x86 eingestellt, da die Toolbox auf 32 Bit ist, keine Änderung.

Ich verstehe immer noch nicht was ich am IntPtr übergeben muss.

C++:
//Als Muster ich muss sie auf C# umschreiben. Sie existieren in der dll Datei.

int Toolbox::Initialize(LPCSTR systemPath, LPCSTR license, DLL_LogFunc* logFunc)
{
	if (m_initialize) return m_initialize((char*)systemPath,(char*)license,logFunc);
	return -1;
}

int Toolbox::Finalize()

	if (m_finalize) return m_finalize();
	return -1;
709 Beiträge seit 2008
vor 7 Jahren

Es ist zwar keine Antwort auf die Frage, aber den Lizenzschlüssel solltest du mal fix rauseditieren. 😉

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Er ist nur als Beispiel. Es ist nicht der echte Key. 😃

16.807 Beiträge seit 2008
vor 7 Jahren

Du kannst mit Hilfe von Assembly.Load nur CLR Assemblies bzw. Managed Assemblies wie Managed C++ laden.
Liegt diese Situation vor?

Es spricht vieles dafür, dass entweder die zu ladende DLL fehlt oder, dass eine DLL fehlt, die Deine zu ladende DLL benötigt.
Also evtl. braucht die Toolbox.dll andere DLLs, die Dir fehlen.

Verwende procmon um zu schauen, was genau er versucht zu laden und was fehlt.
Ist ne ziemliche Sisyphusarbeit aber so ists manchmal.

Wenn es sich um eine Managed Assembly handelt, dass kannst sie auch direkt in Visual Studio als Referenz verweisen.
Dann bekommst Du - eventuell - bessere Informationen, was fehlt.

4.931 Beiträge seit 2008
vor 7 Jahren

Hallo doubleII,

das "Assembly.LoadFile" ist überflüssig, da du ja direkt per P/Invoke ("DllImport") auf die DLL zugreifst (den Zugriff regelt dann schon das .NET-Laufzeitsystem) - und dies würde, wie schon geschrieben, sowie nur für .NET-Assemblies funktionieren.

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Du kannst mit Hilfe von Assembly.Load nur CLR Assemblies bzw. Managed Assemblies wie Managed C++ laden.
Liegt diese Situation vor?

Danke! Es klingt logisch. Also wie Th69 geschrieben hat, "Assebly.LoadFile" ist überflüssig.

Verwende procmon um zu schauen, was genau er versucht zu laden und was fehlt.
Ist ne ziemliche Sisyphusarbeit aber so ists manchmal.

Ich habe etwas gefunden aber noch nicht ausprobiert. Was meint ihr muss ich was ändern oder passt es so. Wie ich den Code verstehe, er zeigt alle Prozesse.


using System.Diagnostics;
public static void Monitor()
    { 
        ArrayList existingProcesses = GetExistingProcess();  

        while (true)
        {  
            ArrayList currentProcesses = new ArrayList();
            currentProcesses = GetCurrentProcess();

            ArrayList NewApps = new ArrayList(GetCurrentProcess());

            foreach (var p in ExistingProcess)
            {
                NewApps.Remove(p); 
            }
            string str = "";
            foreach (string NewApp in NewApps)
            {
                str = "Process Name : " + NewApp + "   Process ID : " + System.Diagnostics.Process.GetProcessesByName(NewApp)[0].Id.ToString() + " ";
            }
            MessageBox.Show(str);
        }
    }

Hat jemand eine Ahnung was der Pointer "IntPtr logFunc" bekommt? es ist mir nicht ganz klar. 😦

D
261 Beiträge seit 2015
vor 7 Jahren

Ich glaube mit "procmon" war eigentlich das hier gemeint: Process Monitor

Das Tool zeigt dir alle Dateisystem/Registry Aktionen, die deine Programm macht. So kannst du z.B. herausfinden ob er versucht Dateien aus dem falschen Pfad zu laden o.ä.

Und "logFunc" hört sich für mich nach einer Log-Methode an. Hast du keine Dokumentation für die DLL?

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Ich bin ein Schritt weiter gekommen. Ich habe Zugriff auf der Toolbox. Ich habe viel ausprobiert und für die, die gleiches Problem wie ich haben. SEHR WICHTIG!

[DllImport("Pfad\DLLdatei.dll")]
Beispiel: [DllImport("E:\Laser\Debug\Toolbox.dll")]
Wenn man die DLL Datei ins system32 kopiert funktioniert auch nicht, die Dll Datei wird nicht gefunden.

Könnte mir jemand sagen, ob ich die funktion richtig ins C sharp umgeschrieben habe?
Das ist die Funkt. von Dll Datei:

typedef void LogFunc(char* msg);

int  Initialize(const char* WorkingDirectory,
                const char* License,
                LogFunc*    logFunc);

Parameters:
WorkingDirectory - writeable location which may be used by the library for configuration files, logging and debugging purpose.
License - Tordivel supplied license string to enable toolbox image processing.
logFunc - callback function for info and debug messages. Normally set to NULL.
Returns:
errorcode or 0 for succeed

ich habe den Code jetzt so umgeschrieben:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Runtime.InteropServices;


namespace Project_1
{
   // ????
    public delegate void DLL_Func(string logFunc);
    public partial class Form1 : Form
    {
        
        public Form1()
        {
            InitializeComponent();
        }
        
        [DllImport("E:\\Laser\\Debug\\Toolbox.dll")]
        static extern int Initialize(string systemPath, string license, DLL_Func logFunc);

        private void btnExecute_Click(object sender, EventArgs e)
        {
            systemPath = @"E:\DLL\Debug\";
            license = "zFm6pwdkz";

            try
            {

                Form1.Initialize(systemPath, license, logFunc);
              
            }


            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                //MessageBox.Show(ex.Message);
            }

        }
    }
}

Für die logFunc bin ich mir nicht sicher, ob man sie so als Delegate eingeben kann und der Wert, den sie liefert irgendwie ist mir nicht klar, ob es so richtig ist. Wäre dankbar, wenn mich jemand korrigieren kann. Es zeigt mir folgendes als Fehlermeldung:

"Zusätzliche Informationen: Ein Aufruf an die PInvoke-Funktion "Project_1!Project_1.Form1::Initialize" hat das Gleichgewicht des Stapels gestört. Wahrscheinlich stimmt die verwaltete PInvoke-Signatur nicht mit der nicht verwalteten Zielsignatur überein. Überprüfen Sie, ob die Aufrufkonvention und die Parameter der PInvoke-Signatur mit der nicht verwalteten Zielsignatur übereinstimmen."

Danke!

Y
102 Beiträge seit 2005
vor 7 Jahren

public static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetDllDirectory(string lpPathName);
    }

Damit kann man eine Dll in den Speicher laden und verwenden.
Das kann dann so verwendet werden:


SetDllDirectory(@"..\..\..\DLL");
LoadLibrary("Toolbox.dll");


[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DLL_Func(string logFunc);

Mfg

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Das kling super. Kurz zusammengefasst, ob ich es richtig verstanden habe.

  1. Die static Klasse erstellen

public static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetDllDirectory(string lpPathName);
    }

jetzt kann ich im Speicher dll Datei laden.

  1. Hier gebe ich den Pfad zur dll Datei und sie wird geladen. Der Zusammenhang mit der anderen Klasse Form1 ist mir nicht klar. 😦

SetDllDirectory(@"..\..\..\DLL");
LoadLibrary("Toolbox.dll");

######################################

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DLL_Func(string logFunc);

Gerade habe getestet. Der gleiche Fehler wird rausgespuckt. 😦


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Runtime.InteropServices;


namespace Project_1
{
   //geändert
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void DLL_Func(string logFunc);

    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }
        
        [DllImport("E:\\Laser\\Debug\\Toolbox.dll")]
        static extern int Initialize(string systemPath, string license, DLL_Func logFunc);

        private void btnExecute_Click(object sender, EventArgs e)
        {
            systemPath = @"E:\DLL\Debug\";
            license = "zFm6pwdkz";

            try
            {

                Form1.Initialize(systemPath, license, logFunc);

            }


            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                //MessageBox.Show(ex.Message);
            }

        }
    }
}

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Hallo Yeats,
ich habe das Problem gelöst jetzt funktioniert.
Könntest du mir etwas mehr den Code oben erklären.
Danke Yeats,

Die Lösung:



    //geändert
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void DLL_Func(string logFunc);
   [DllImport("E:\\Laser 2000\\Debug\\Toolbox.dll",  CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 
Y
102 Beiträge seit 2005
vor 7 Jahren

Was möchtest du wissen?

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Hallo Yeats,

Das kling super. Kurz zusammengefasst, ob ich es richtig verstanden habe.

  1. Die static Klasse erstellen
public static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetDllDirectory(string lpPathName);
    }

jetzt kann ich im Speicher dll Datei laden.
Wie verbinde ich die Klasse jetzt mit meiner. Mach bitte ein Beispiel.
Vielen Dank!

Y
102 Beiträge seit 2005
vor 7 Jahren

Hallo,

Bezieh mich hier auf ein Projekt an dem ich gerade arbeite.

Ich beziehe mich auf XC_Comm.dll. Diese Dll liegt in einem Ordner. Zuerst rufe ich die Methode SetDllDirectory auf und gib den relativen Pfad an in dem die Dll liegt.
Anschließend rufe ich die Methode LoadLibrary auf, bei dieser gebe ich den Namen der dll an.

Momentan ist das ganze noch ein Prototyp und nicht nach MVVM gebaut. Somit liegt mein Code noch im Code-Behind-File der Wpf Anwendung.

MainWindow.xaml.cs:


public void SetAndLoadDll()
{
if (!NativeMethods.SetDllDirectory(@"..\..\..\DLL"))
return;
var handler = NativeMethods.LoadLibrary("XC_Comm.dll");
}

handler sollte beim Schließen benutzt werden um die dll wieder frei zu geben(FreeLibrary).

Grüße

4.931 Beiträge seit 2008
vor 7 Jahren

Hallo Yeats und doubleII,

ich habe doch schon geschrieben, daß es gar nicht nötig ist, die DLL selbst dynamisch zu laden, da dies alles per P/Invoke gemacht wird!

Y
102 Beiträge seit 2005
vor 7 Jahren

Hallo Th69,

Dies aber nur solange die DLL im GAC oder im selben Programm Verzeichnis liegt.

D
doubleII Themenstarter:in
33 Beiträge seit 2016
vor 7 Jahren

Hallo TH69,

Wie meinst du das? ich habe die Dll Datei unter debug, Release, sogar unter
System32 gespeichert , funktioniert nicht. Oder ich mache etwas falsch? Ich bin offen, wenn man etwas einfach lösen kann.

Schöne Grüße

16.807 Beiträge seit 2008
vor 7 Jahren

Und wie.
DLLs registriert man in den GAC via gacutil.exe
Der GAC liegt übrigens unter %windir%\Assembly

Hinweis: niemals den GAC ohne gacutil anfassen.

Ansonsten müssen die DLL im gleichen Verzeichnis wie die Exe liegen.
Wenn das nicht funktioniert; hab Dir bereits weiter vorne im Thread gesagt, dass man mit procmon diese Art von Fehler aufspürt um zu schauen, was er versucht zu laden.