将 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
而不是 class
。 ValueName
和 Value
字段的声明与您的 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);
}
}
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
而不是 class
。 ValueName
和 Value
字段的声明与您的 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);
}
}