将非托管 dll 的 Delphi 代码转换为 C#
Posted
技术标签:
【中文标题】将非托管 dll 的 Delphi 代码转换为 C#【英文标题】:Converting Delphi code for unmanaged dll to C# 【发布时间】:2009-11-27 07:23:31 【问题描述】:我希望有人可以帮助我解决我目前遇到的问题。我们有很多 Delphi 遗留代码,需要将我们的一些 Delphi 应用程序转换为 C#。
我目前正在处理的遗留代码是从第三方应用程序的非 COM DLL 调用函数的代码。
这里是用于特定功能的 C 风格的标头和结构:
/*** C Function AwdApiLookup ***/
extern BOOL APIENTRY AwdApiLookup( HWND hwndNotify, ULONG ulMsg,
BOOL fContainer, CHAR cObjectType,
SEARCH_CRITERIA* searchCriteria,
USHORT usCount, USHORT usSearchType,
VOID pReserved );
/*** C Struct SEARCH_CRITERIA ***/
typedef struct _search_criteria
UCHAR dataname[4];
UCHAR wildcard;
UCHAR comparator[2];
UCHAR datavalue[75];
SEARCH_CRITERIA;
在我们的Delphi代码中,我们将上面的函数和结构转换为:
(*** Delphi implementation of C Function AwdApiLookup ***)
function AwdApiLookup(hwndNotify: HWND; ulMsg: ULONG; fContainer: Boolean;
cObjectType: Char; pSearchCriteria: Pointer; usCount: USHORT;
usSearchType: USHORT; pReserved: Pointer): Boolean; stdcall;
external 'AWDAPI.dll';
(*** Delphi implementation of C Struct SEARCH_CRITERIA ***)
TSearch_Criteria = record
dataname: array [0..3] of char;
wildcard: char;
comparator: array [0..1] of char;
datavalue: array [0..74] of char;
end;
PSearch_Criteria = ^TSearch_Criteria;
而我们在Delphi中调用上述代码的方式是:
AwdApiLookup(0, 0, true, searchType, @criteriaList_[0],
criteriaCount, AWD_USE_SQL, nil);
criteriaList 定义为
criteriaList_: array of TSearch_Criteria;
说了这么多,我们现在可以查看 C# 代码,我无法开始工作。我确定我在这里做错了,或者我的 C 标头没有正确翻译。我的项目确实编译正确,但是当调用函数时,我得到一个“FALSE”值,这表明该函数在 DLL 中没有正确执行。
到目前为止我的 C# 代码:
/*** C# implementation of C Function AwdApiLookup ***/
DllImport("awdapi.dll", CharSet = CharSet.Auto)]
public static extern bool AwdApiLookup(IntPtr handle, ulong ulMsg,
bool fContainer, char cObjectType,
ref SearchCriteria pSearchCriteria,
ushort usCount, ushort usSearchType,
Pointer pReserverd);
/*** C# implementation of C Struct SEARCH_CRITERIA ***/
[StructLayout(LayoutKind.Sequential)]
public struct SearchCriteria
private readonly byte[] m_DataName;
private readonly byte[] m_Wildcard;
private readonly byte[] m_Comparator;
private readonly byte[] m_DataValue;
public SearchCriteria(string dataName, string comparator, string dataValue)
m_DataName = Encoding.Unicode.GetBytes(
dataName.PadRight(4, ' ').Substring(0, 4));
m_Wildcard = Encoding.Unicode.GetBytes("0");
m_Comparator = Encoding.Unicode.GetBytes(
comparator.PadRight(2, ' ').Substring(0, 2));
m_DataValue = Encoding.Unicode.GetBytes(
dataValue.PadRight(75, ' ').Substring(0, 75));
public byte[] dataname get return m_DataName;
public byte[] wildcard get return m_Wildcard;
public byte[] comparator get return m_Comparator;
public byte[] datavalue get return m_DataValue;
我对 C# 函数的 C# 调用如下所示
var callResult = UnsafeAwdApi.CallAwdApiLookup(IntPtr.Zero, 0, true, 'W',
ref searchCriteria[0], criteriaCount,
66, null);
其中 searchCriteria 和 criteriaCount 定义为
List<SearchCriteria> criteriaList = new List<SearchCriteria>();
var searchCriteria = criteriaList.ToArray();
var criteriaCount = (ushort)searchCriteria.Length;
并将数据添加到 searchCriteria:
public void AddSearchCriteria(string dataName, string comparator, string dataValue)
var criteria = new SearchCriteria();
criteria.DataName = dataName;
criteria.Wildcard = "0";
criteria.Comparator = comparator;
criteria.DataValue = dataValue;
criteriaList.Add(criteria);
就像我说的,我的代码编译正确,但是当函数执行时,它返回“FALSE”,这不应该是这种情况,因为 Delphi 函数确实返回具有完全相同输入的数据。
我知道我在这里肯定做错了,我尝试了几件事,但似乎没有任何效果。
非常感谢您在正确方向上的任何帮助或推动。
谢谢,瑞安
【问题讨论】:
Pointer pReserverd
- 你是说IntPtr
吗?
我不确定。 C++ API 将其定义为 VOID pReserved,而有效的 Delphi 代码将其定义为 Pointer pReserved。
【参考方案1】:
这里有几件事。
首先,C++ ULONG
是一个 32 位整数,在 C# 中变为 uint
- ulong
是 64 位。
对于结构体,你不需要弄乱字节数组。使用字符串和ByValTStr
。此外,不值得为readonly
和互操作结构的属性而烦恼。是的,可变值类型在纯 .NET API 中通常不好,但在这种情况下,它是现有的 API,屏蔽它没有意义。所以:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SearchCriteria
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4]
public string m_DataName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1]
public string m_Wildcard;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2]
public string m_Comparator;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 75]
public string m_DataValue;
如果你真的想自己做所有的字符串转换,使用unsafe
和固定大小的数组可能会更容易:
public unsafe struct SearchCriteria
public fixed byte m_DataName[4];
public byte m_Wildcard;
public fixed byte m_Comparator[2];
public fixed byte m_DataValue[75];
[编辑]还有两件事。
CHAR cObjectType
应该变成 byte cObjectType
,而不是您当前使用的 char cObjectType
。
另外,是的,您的示例中的数组封送处理存在问题。由于您的 P/Invoke 声明是 ref SearchCriteria pSearchCriteria
- 即通过引用传递的 single 值 - 这正是 P/Invoke mashaler 将要做的。请记住,除非您的结构中只有非托管类型的字段(上面带有fixed
数组的那个是,带有string
的那个不是),否则封送拆收器将不得不复制结构。它不会直接将地址传递给数组的第一个元素。在这种情况下,由于您没有告诉它那里是一个数组,它只会复制您引用的单个元素。
因此,如果您使用带有string
字段的struct
版本,则需要更改P/Invoke 声明。如果您只需要将SEARCH_CRITERIA
对象传递给函数,而返回后不需要从中读取数据,则只需使用数组即可:
public static extern bool AwdApiLookup(IntPtr handle, uint ulMsg,
bool fContainer, byte cObjectType,
SearchCriteria[] pSearchCriteria,
ushort usCount, ushort usSearchType,
Pointer pReserverd);
然后这样称呼它:
var callResult = UnsafeAwdApi.CallAwdApiLookup(
IntPtr.Zero, 0, true, (byte)'W',
searchCriteria, criteriaCount,
66, null);
如果函数将数据写入该数组,并且您需要读取它,请使用[In, Out]
:
[In, Out] SearchCriteria[] pSearchCriteria,
如果使用带有fixed byte[]
数组的版本,也可以将P/Invoke声明改为SearchCriteria* pSearchCriteria
,然后使用:
fixed (SearchCriteria* p = &searchCriteria[0])
AwdApiLookup(..., p, ...);
不过,这也需要unsafe
。
【讨论】:
嗨 Pavel,感谢您修改结构。我也是这样,但改回你的建议。也感谢您发现 ULONG 错误。我也相应地更新了我的代码,但不幸的是我仍然得到一个“FALSE”,这意味着该函数没有正确执行。还有一个问题,是不是非托管 DLL 无法访问托管数组的第一个元素的内存? 是的,那里的数组存在我没有发现的问题。更新了答案。 嗨 Pavel,我已经添加了 searchCriteria 正在添加的方式。根据您的建议更改结构,我的代码当前未编译。更新 AddSearchCriteria 函数以接受新的结构数据成员的正确方法是什么。我试过 Convert.ToByte,但它也给出了编译错误。感谢您迄今为止的所有帮助。 我假设您使用带有ByValTStr
的版本?如果是这样,那么您的AddSearchCriteria
应该没问题;如果没有,请提供您收到的确切错误消息。我还编辑了答案并添加了一个示例函数调用 - 您需要将'W'
转换为字节,并将ref ...[0]
删除为searchCriteria
。另外,Pointer
是什么类型?我保留了它,但它不是普通的 C# 类型。
您好 Pavel,感谢您迄今为止的回复。我已经相应地更新了我的代码,但到目前为止还没有运气。它现在执行,但外部函数不返回任何值。类型指针在 C++ 头文件中定义为 VOID,但在 Delphi 中它被定义为指针(指针引用),但在 Delhpi 代码中将空值传递给函数。不过会及时通知你。 (这周剩下的时间我都不在办公室,但我会在此期间看看我能想出什么)以上是关于将非托管 dll 的 Delphi 代码转换为 C#的主要内容,如果未能解决你的问题,请参考以下文章