用C#从Delphi DLL中检索记录数组
本文关键字:检索 记录 数组 DLL Delphi | 更新日期: 2023-09-27 18:22:20
我正试图在Delphi中编写一个DLL,以允许我的C#应用程序访问Advantage数据库(使用VS2013,无法直接访问数据)。
我的问题是,在我进行调用后,C#中的数组中充满了null值。
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
进行分配。
德尔福记录变为:
type
TItem = record
Id : Int32;
Description : WideString;
end;
通过查看以下行,您可以清楚地看到您的代码是错误的:
result[index].Description := PWideChar(Query.FieldByName(ADataField).AsString);
在这里,使result[index].Description
指向函数返回时将被释放的内存。
尝试使用Delphi开放数组充其量是有风险的。我不会那样做。如果您坚持这样做,那么至少应该注意为high
传递的值,而不是重写数组的末尾。更重要的是,你应该为high传递正确的值。那就是projectItems.Length-1
。
现在,您正在为数组使用pass-by-value,因此您在Delphi代码中编写的任何内容都不会返回到C#代码。更重要的是,C#代码默认情况下具有[In]
封送,因此即使切换到pass-by-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
说了以上所有内容,我确实怀疑整理器是否会整理一个非blitable类型的数组。我有一种感觉,它不会。在这种情况下,您的主要选择是:
- 切换到将字符串作为
IntPtr
在记录中传回。使用CoTaskMemAlloc
进行分配。使用Marshal.FreeCoTaskMem
销毁受管端 - 使用打开的查询,获取下一个记录接口,关闭查询,这将导致对本机代码的多次调用,每次调用都返回一个项目
就我个人而言,我会选择后一种方法。