将Delphi非COM DLL接口转换为C#

时间:2017-02-22 23:25:18

标签: c# .net delphi dll interop

我/我们目前有一个用Delphi(特别是XE)编写的程序,它与非托管DLL(用我听到的C ++编写)进行交互。 Delphi应用程序正在退役,但是需要使用非托管DLL,因此需要编写C#接口。没什么大不了。除了没有访问DLL的源代码或任何有关它的好文档,以及这是我第一次涉足互操作,就是杀了我。

以下是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
  );

在Delphi中,这些成功实现(包括自定义结构):

  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;

请注意,虽然DoWork方法似乎接受了一个项目数组,但所有实现都将NumItems设置为1并在Delphi中循环该对象,而不是将其传递给C ++。

在C#中,我甚至不确定我应该发布哪个示例。我已经google了几天,尝试了我可以尝试的每个版本的代码,但都无济于事。这是最新版本:

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;
        }
    }
}

但是,这样做会提供访问冲突。我已经尝试将方法签名设为IntPtr,但这失败了。我已经尝试使方法签名成为结构的指针,这通常在我尝​​试的每一种方式都是非常错误的,尽管我不能确定我是否真的在'正确的方式'很长时间内,因为我不确定那是什么方式。我想如果我能弄清楚正确的方法签名是什么,那将有助于吨。

此外,对于稍微混淆源代码道歉。我正在使用的DLL是专有的等等。

先发制人,谢谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您不应该在SizeConst MarshalAs参数的Type声明中使用_initiate()属性。输入只是一个指向字符串的指针,所以让C#编组它。

_DoWork()需要一个数组(即使你的实现只传递了1个元素),所以你应该编组一个实际的数组。

对于struct类型,您应该使用class而不是StructureValueNameValue字段的声明与您的Delphi代码不符。在你的Delphi代码中,它们只是原始指针,可能是分配的字符缓冲区。但是在您的C#代码中,您正在编组可变长度string值,就像它们是固定长度的字符数组一样。

尝试更像这样的东西:

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);
    }
}

但是,您在Structure.Value退出后正在阅读_DoWork()字段,因此可能会将新数据写入该字段,因此您可能需要执行更类似的操作:

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);
    }
}