我希望有人可以帮我解决我目前遇到的问题。我们有很多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函数确实返回具有完全相同输入的数据。
我知道我肯定在这里做错了,我尝试了几件事,但似乎没有任何工作。
非常感谢任何正确方向的帮助或推动。
谢谢,Riaan
答案 0 :(得分:2)
这里有几件事。
首先,C ++ ULONG
是一个32位整数,在C#中成为uint
- ulong
是64位。
对于struct,您不需要弄乱字节数组。使用字符串和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
- 即通过引用传递的单个值 - 这正是P / Invoke mashaler将要做的事情。请记住,除非你的struct只有非托管类型的字段(上面有fixed
数组的那个是那个,string
不是),编组人员必须复制结构。它不会直接将地址传递给数组的第一个元素。在这种情况下,由于你没有告诉它它是一个数组,它只会复制你引用的单个元素。
因此,如果您使用带有struct
字段的string
版本,则需要更改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
。