繁体   English   中英

使用C#从Delphi DLL中检索记录数组

[英]Retrieve record array from Delphi DLL with C#

我正在尝试在Delphi中编写一个DLL,以允许我的C#app访问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

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

[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

现在,您正在为数组使用pass by value,因此您在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. 使用一个打开的查询,获取下一个记录界面,关闭查询,这将导致多次调用本机代码,每个调用返回一个项目。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM