将非托管 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#的主要内容,如果未能解决你的问题,请参考以下文章

将非托管GUID转换为托管Guid ^

将非托管 DLL 部署到输出目录

如何将非托管 dll 和托管程序集合并到一个文件中?

在.net中调用Delphi dll的Pchar转换

如何将非托管 C++ 表单嵌入到 .NET 应用程序中?

如何将非托管库引用添加到 NUnit 测试