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

Converting Delphi non-COM DLL Interface to C#

I/we 目前有一个用 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# 中,我什至不确定应该使用哪个示例 post。我已经用谷歌搜索了好几天,并尝试了我可以尝试的每个版本的代码,但都无济于事。这是最新版本:

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,但失败了。我已经尝试使方法签名成为指向结构的指针,这通常在我尝​​试的每一种方式中都会出现可怕的错误,尽管我不能确定我是否真的在 'the right way' 上敲了很长时间,因为我不确定那是什么。我想如果我能弄清楚正确的方法签名是什么,那将大有帮助。

此外,对于稍微混淆了来源,我们深表歉意。我使用的 dll 是专有的等等。

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

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

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

对于 Structure 类型,您应该使用 struct 而不是 classValueNameValue 字段的声明与您的 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);
    }
}

但是,您在 _DoWork() 退出后读取 Structure.Value 字段,因此可能会将新数据写入该字段,因此您可能需要做更多类似的事情:

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