【问题标题】:Retrieve record array from Delphi DLL with C#使用 C# 从 Delphi DLL 中检索记录数组
【发布时间】:2014-09-17 10:52:09
【问题描述】:

我正在尝试在 Delphi 中编写一个 DLL,以允许我的 C# 应用程序访问 Advantage 数据库(使用 VS2013 并且无法直接访问数据)。

我的问题是调用后,C# 中的数组充满了空值。

Delphi DLL 代码:

TItem = record
  Id          : Int32;
  Description : PWideChar;
end;

function GetNumElements(const ATableName: PWideChar): Integer; stdcall;
var recordCount : Integer;
begin
... // code to get the number of records from ATableName
  Result := recordCount;
end;

procedure GetTableData(const ATableName: PWideChar; const AIdField: PWideChar; 
                       const ADataField: PWideChar; result: array of TItem); stdcall;
begin
  ... // ATableName, AIdField, and ADataField are used to query the specific table, then I loop through the records and add each one to result array
  index := -1;
  while not Query.Eof do begin
    Inc(index);
    result[index].Id := Query.FieldByName(AIdField).AsInteger;
    result[index].Description := PWideChar(Query.FieldByName(ADataField).AsString);
    Query.Next;
  end;
  ... // cleanup stuff (freeing created objects, etc)
end;

这似乎有效。我已经使用 ShowMessage 来显示输入的信息是什么样的以及之后的样子。



C# 代码:

[StructLayoutAttribute(LayoutKind.Explicit)] // also tried LayoutKind.Sequential without FieldOffset
public struct TItem
{
    [FieldOffset(0)]
    public Int32 Id;

    [MarshalAs(UnmanagedType.LPWStr),FieldOffset(sizeof(Int32))]
    public string Description;
}

public static extern void GetTableData(
    [MarshalAs(UnmanagedType.LPWStr)] string tableName,
    [MarshalAs(UnmanagedType.LPWStr)] string idField,
    [MarshalAs(UnmanagedType.LPWStr)] string dataField, 
    [MarshalAs(UnmanagedType.LPArray)] TItem[] items, int high);

public void GetListItems()
{
    int numProjects = GetNumElements("Project");

    TItems[] projectItems = new TItem[numProjects];

    GetTableData("Project", "ProjectId", "ProjectName", projectItems, numProjects);
}

此代码执行,没有任何错误,但是当我遍历 projectItems 时,每个都返回

Id = 0
Description = null

【问题讨论】:

    标签: c# delphi pinvoke delphi-xe3


    【解决方案1】:

    我可以看到很多问题。首先,我会像这样声明结构:

    [StructLayoutAttribute(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct TItem
    {
        public Int32 Id;
        [MarshalAs(UnmanagedType.BStr)]
        public string Description;
    }
    

    您需要使用UnmanagedType.BStr 以便可以在非托管端分配字符串,并在托管端解除分配。另一种方法是将封送为LPWStr,但您必须在非托管端分配CoTaskMemAlloc

    Delphi 记录变为:

    type
      TItem = record
        Id          : Int32;
        Description : WideString;
      end;
    

    通过查看这一行,您可以清楚地看到您的代码是错误的:

    result[index].Description := PWideChar(Query.FieldByName(ADataField).AsString);
    

    在这里,您使result[index].Description 指向将在函数返回时释放的内存。


    尝试使用 Delphi 开放数组充其量是有风险的。我不会那样做。如果您坚持这样做,您至少应该注意为high 传递的值,而不是覆盖数组的末尾。更重要的是,您应该将正确的值传递给高。那是projectItems.Length-1

    现在,您正在对数组使用按值传递,因此您在 Delphi 代码中编写的任何内容都不会返回到 C# 代码。更重要的是,C# 代码默认具有[In] 编组,因此即使您切换到通过 var,编组器也不会将项目编组回托管端的 projectItems

    我个人会停止使用开放数组并明确表示:

    function GetTableData(
        ATableName: PWideChar; 
        AIdField: PWideChar; 
        ADataField: PWideChar; 
        Items: PItem;
        ItemsLen: Integer
    ): Integer; stdcall;
    

    这里Items 指向数组中的第一项,ItemsLen 给出了所提供数组的长度。函数返回值应该是复制到数组中的项目数。

    要实现这一点,请使用指针算法或($POINTERMATH ON}。我更喜欢后一种选择。我认为我不需要证明这一点。

    在 C# 方面你有:

    [DllImport(dllname, CharSet=CharSet.Unicode)]
    public static extern int GetTableData(
        string tableName,
        string idField,
        string dataField, 
        [In,Out] TItem[] items, 
        int itemsLen
    );
    

    这样称呼它:

    int len = GetTableData("Project", "ProjectId", "ProjectName", projectItems, 
        projectItems.Length);
    // here you can check that the expected number of items were copied
    

    说了以上所有内容后,我确实对编组器是否会编组一组非 blittable 类型存有疑问。我有种不会的感觉。在这种情况下,您的主要选择是:

    1. 切换到在记录中将字符串作为IntPtr 传回。使用CoTaskMemAlloc 分配。使用 Marshal.FreeCoTaskMem 在托管端销毁。
    2. 使用打开查询,获取下一条记录接口,关闭查询,这将导致多次调用本机代码,每次都返回一个项目。

    我个人会选择后一种方法。

    【讨论】:

    • 完美答案,非常感谢。使用指针算法是我以前没有使用过的(因此没有考虑过),但现在看起来很明显。
    • 编组器会在两个方向上编组该非 blittable 数组吗?
    • 好的,谢谢。即使在所有这些 p/invoke 问题之后,我仍然无法预测编组器是否会堵住特定的复杂类型!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多