2017-02-22 4 views
1

Ich/wir haben derzeit ein Programm in Delphi (XE, speziell) geschrieben, die Schnittstellen mit einer nicht verwalteten DLL (geschrieben in C++, von dem, was ich höre). Die Delphi-Anwendung wird außer Kraft gesetzt, aber die Notwendigkeit, mit der nicht verwalteten DLL zu arbeiten, bleibt bestehen, so dass eine C# -Schnittstelle geschrieben werden muss. Keine große Sache. Abgesehen davon, dass ich keinen Zugriff auf die Quelle der DLL oder irgendeine gute Dokumentation dazu habe, und dies ist mein erster Ausflug in die Interoperabilität, bringt mich um.Konvertieren von Delphi Nicht-COM-DLL-Schnittstelle zu C#

Hier ist die Dokumentation über die verfügbaren Funktionen in der DLL:

extern EXTERNC __declspec(dllexport) long initiate (
    double conc1,    
    double conc2,     
    long temp, 
    const char* NT 
); 

    extern EXTERNC __declspec(dllexport) long DoWork (
    long NumItems, 
    struct Structure* Items 
); 

In Delphi, diese wie dies erfolgreich umgesetzt werden (mit der benutzerdefinierten Struktur enthielt auch):

function CustomInitialize 
    (
    Concentration1 : double; 
    Concentration2 : double; 
    Temperature : LongInt; 
    const Type : PAnsiChar 
    ) : LongInt; cdecl; external 'CustomDLL.dll' name '_initiate'; 

    procedure DoWork 
    (
    NumItems : LongInt; 
    Items : PStructure 
    ); cdecl; external 'CustomDLL.dll' name '_DoWork'; 

    TStructure = record 
    ValueName : PAnsiChar; 
    Value : PAnsiChar; 
    Status : LongInt;  
    ReturnVal1 : Double; 
    ReturnVal2 : Double; 
    ReturnVal3 : Double; 
    Temp : Double; 
    end; 
    PStructure = ^TStructure; 

Beachten Sie, dass während Die DoWork-Methode scheint ein Array von Elementen aufzunehmen. Alle Implementierungen setzen NumItems auf 1 und durchlaufen das Objekt in Delphi, anstatt es an C++ weiterzuleiten.

In C# bin ich nicht einmal sicher, welches Beispiel ich veröffentlichen sollte. Ich habe seit Tagen gegoogelt und versucht, was sich wie jede Version des Codes anfühlt, die ich ausprobieren kann, aber alles ohne Erfolg. Hier ist die neueste Version:

namespace JunkProject 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      int test = _initiate(.05, .05, 60, "1"); 

      Console.WriteLine(test); 
      if (test != 1) 
       Console.ReadLine(); 

      var structure = new FoldStructure() { ValueName = "Test1", Value = "TESTTESTTESTTESTTESTTESTTEST", Status = 0, ReturnVal1 = 0.0, ReturnVal2 = 0.0, ReturnVal3 = 0.0, Temp = 0.0 }; 

      test = _DoWork(1, structure); 

      Console.WriteLine(structure.Value); 
      Console.ReadLine(); 
     } 

     private const string DLL_LOCATION = "CustomDLL.dll"; 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
     private static extern int _initiate(double Conc1, double Conc2, int Temp, [MarshalAs(UnmanagedType.LPStr, SizeConst = 5)] string Type); 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
     private static extern int _DoWork(int NumItems, [In, Out] Structure Struct); 

     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
     public class Structure 
     { 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)] 
      public string ValueName; 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 28)] 
      public string Value; 

      public int Status; 
      public double ReturnVal1; 
      public double ReturnVal2; 
      public double ReturnVal3; 
      public double Temp; 
     } 
    } 
} 

Dies bietet jedoch eine Zugriffsverletzung. Ich habe versucht, die Methodensignatur zu einem IntPtr zu machen, das schlägt fehl. Ich habe versucht, die Methodensignatur zu einem Zeiger auf die Struktur zu machen, das geht normalerweise auf jede Art und Weise schief, die ich versuche, obwohl ich nicht sicher sein kann, dass ich wirklich sehr lange auf den richtigen Weg gehämmert habe, da bin ich mir nicht sicher, was das ist. Ich denke, wenn ich herausfinden könnte, was die korrekte Methodensignatur ist, würde das einer Tonne helfen.

Auch Entschuldigung für die leichte Verschleierung der Quelle. Die DLL, die ich verwende, ist proprietär und nicht.

Präventiver Dank für jede Hilfe!

Antwort

1

Sie sollten nicht die SizeConst Attribut in Ihrer MarshalAs Erklärung des Type Parameter in _initiate() werden. Die Eingabe ist nur ein Zeiger auf eine Zeichenkette, also lasse sie von C# als solche marshallen.

_DoWork() erwartet ein Array (auch wenn Ihre Implementierungen immer nur 1 Element passieren), also sollten Sie ein Array anordnen.

Sie sollten einen struct anstelle eines class für den Structure Typ verwenden. Und die Deklaration der Felder ValueName und Value stimmt nicht mit Ihrem Delphi-Code überein. In Ihrem Delphi-Code handelt es sich nur um unformatierte Zeiger, vermutlich um zugewiesene Zeichenpuffer. In Ihrem C# -Code werden Sie jedoch Werte string mit variabler Länge so als ob sie Zeichenfelder fester Länge waren.

etwas mehr wie diese stattdessen versuchen:

namespace JunkProject 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      int test = initiate(.05, .05, 60, "1"); 

      Console.WriteLine(test); 
      if (test != 1) 
       Console.ReadLine(); 

      Structure[] structure = new Structure[1]; 
      structure[0].ValueName = "Test1"; 
      structure[0].Value = "TESTTESTTESTTESTTESTTESTTEST"; 
      structure[0].Status = 0; 
      structure[0].ReturnVal1 = 0.0; 
      structure[0].ReturnVal2 = 0.0; 
      structure[0].ReturnVal3 = 0.0; 
      structure[0].Temp = 0.0; 

      test = DoWork(1, structure); 

      Console.WriteLine(structure[0].Value); 
      Console.ReadLine(); 
     } 

     private const string DLL_LOCATION = "CustomDLL.dll"; 

     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
     public struct Structure 
     { 
      [MarshalAs(UnmanagedType.LPStr)] 
      public string ValueName; 
      [MarshalAs(UnmanagedType.LPStr)] 
      public string Value; 
      public int Status; 
      public double ReturnVal1; 
      public double ReturnVal2; 
      public double ReturnVal3; 
      public double Temp; 
     } 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "_initiate")] 
     private static extern int initiate(double Conc1, double Conc2, int Temp, [MarshalAs(UnmanagedType.LPStr)] string Type); 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "_DoWork")] 
     private static extern int DoWork(int NumItems, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] Structure[] Struct); 
    } 
} 

jedoch lesen Sie das Structure.Value Feld nach _DoWork() Ausfahrten, so vermutlich wird es neue Daten in diesen Bereich schreiben, so dass Sie etwas mehr tun müssen, können wie folgt:

namespace JunkProject 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      int test = initiate(.05, .05, 60, "1"); 

      Console.WriteLine(test); 
      if (test != 1) 
       Console.ReadLine(); 

      Structure[] structure = new Structure[1]; 

      structure[0].ValueName = "Test1"; 
      structure[0].Value = Marshal.AllocHGlobal(28); 
      // alternatively: 
      // structure[0].Value = (IntPtr) Marshal.StringToHGlobalAnsi("TESTTESTTESTTESTTESTTESTTEST"); 
      structure[0].Status = 0; 
      structure[0].ReturnVal1 = 0.0; 
      structure[0].ReturnVal2 = 0.0; 
      structure[0].ReturnVal3 = 0.0; 
      structure[0].Temp = 0.0; 

      test = DoWork(1, structure); 

      String Value = Marshal.PtrToStringAnsi(structure[0].Value); 
      Console.WriteLine(Value); 
      Console.ReadLine(); 

      Marshal.FreeHGlobal(structure[0].Value); 
     } 

     private const string DLL_LOCATION = "CustomDLL.dll"; 

     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
     public struct Structure 
     { 
      [MarshalAs(UnmanagedType.LPStr)] 
      public string ValueName; 
      public IntPtr Value; 
      public int Status; 
      public double ReturnVal1; 
      public double ReturnVal2; 
      public double ReturnVal3; 
      public double Temp; 
     } 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "_initiate")] 
     private static extern int initiate(double Conc1, double Conc2, int Temp, [MarshalAs(UnmanagedType.LPStr)] string Type); 

     [DllImport(DLL_LOCATION, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "_DoWork")] 
     private static extern int DoWork(int NumItems, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] Structure[] Struct); 
    } 
} 
+0

Vielen Dank!Das Array-Konzept war etwas, das ich früher ausprobiert habe, aber ich war immer noch grün in meinem Verständnis von dem, was ich tatsächlich mache, also habe ich es wahrscheinlich falsch verstanden. Diese Kommentare machen definitiv Sinn und helfen zu erklären, was mir fehlte. Ich war auf jeden Fall hin und her gegangen, aber ich steckte in einem "Hin und Her" -Programm-Albtraum fest, ohne Feedback-Mechanismus, um zu sehen, ob irgendetwas besser funktionierte. –