简体   繁体   English

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

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

I'm trying to write a DLL in Delphi to allow my C# app to access an Advantage database (using VS2013 and not been able to access the data directly). 我正在尝试在Delphi中编写一个DLL,以允许我的C#app访问Advantage数据库(使用VS2013并且无法直接访问数据)。

My issue is after I make the call, the array in C# is full of null values. 我的问题是在我调用之后,C#中的数组充满了空值。

The Delphi DLL code: 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;

This appears to be working. 这似乎有效。 I've used ShowMessage to show what the information going in looks like and what it looks like after. 我已经使用ShowMessage来显示出现的信息和之后的样子。



The C# Code: 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);
}

This code executes, no errors of any kind, but when I iterate through projectItems each one returns 这段代码执行,没有任何错误,但是当我遍历projectItems时,每个都会返回

Id = 0
Description = null

There are quite a few issues that I can see. 我可以看到很多问题。 First of all, I would declare the struct like this: 首先,我会声明这样的结构:

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

You'll need to use UnmanagedType.BStr so that the string can be allocated on the unmanaged side, and deallocated on the managed side. 您需要使用UnmanagedType.BStr以便可以在非托管端分配字符串,并在托管端解除分配。 The alternative would be to marshal as LPWStr but then you'd have to allocate with CoTaskMemAlloc on the unmanaged side. 另一种方法是编组为LPWStr但是你必须在非托管端使用CoTaskMemAlloc进行分配。

The Delphi record becomes: Delphi记录变为:

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

You can clearly see that your code is wrong by looking at this line: 通过查看以下行,您可以清楚地看到代码错误:

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

Here you make result[index].Description point to memory that will be deallocated when the function returns. 在这里,您将result[index].Description指向将在函数返回时释放的内存。


Trying to use a Delphi open array is risky at best. 尝试使用Delphi开放阵列充其量是有风险的。 I would not do that. 我不会这样做。 If you insist on doing so you should at least heed the value passed for high and not write over the end of the array. 如果你坚持这样做,你至少应该注意传递给high的值,而不是写在数组的末尾。 What's more, you should pass the right value for high. 更重要的是,你应该传递正确的价值。 That is projectItems.Length-1 . 那就是projectItems.Length-1

Now, you are using pass by value for the array so nothing you write in the Delphi code will find its way back to the C# code. 现在,您正在为数组使用pass by value,因此您在Delphi代码中编写的任何内容都无法找回C#代码。 What's more, the C# code has [In] marshalling by default and so even when you switch to pass by var, the marshaller won't marshal the items back in to projectItems on the managed side. 更重要的是,C#代码默认情况下有[In]编组,因此即使切换到传递var,编组器也不会将项目封送回管理端的projectItems

Personally I'd stop using an open array and be explicit: 就个人而言,我将停止使用一个开放的数组并明确:

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

Here Items points to the first item in the array and ItemsLen gives the length of the supplied array. 这里Items指向数组中的第一项, ItemsLen提供的数组的长度。 The function return value should be the number of items copied to the array. 函数返回值应该是复制到数组的项数。

To implement this use either pointer arithmetic, or ($POINTERMATH ON} . I prefer the latter option. I don't think I need to demonstrate that. 要实现这个,请使用指针算术或($POINTERMATH ON} 。我更喜欢后一个选项。我认为我不需要证明这一点。

On the C# side you have: 在C#方面你有:

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

Call it like this: 像这样称呼它:

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

Having said all of the above, I do have a doubt as to whether or not the marshaller will marshal an array of non-blittable types. 说完上述所有内容之后,我确实怀疑编组人员是否会编组一系列非blittable类型。 I have a feeling that it won't. 我有一种感觉,它不会。 In which case your main options are: 在这种情况下,您的主要选项是:

  1. Switch to passing the string back as IntPtr in the record. 切换到将字符串作为IntPtr传递回记录中。 Allocate with CoTaskMemAlloc . 使用CoTaskMemAlloc分配。 Destroy on the managed side with Marshal.FreeCoTaskMem . 使用Marshal.FreeCoTaskMem在托管端销毁。
  2. Use an open query, get next record interface, close query which would lead to multiple calls to the native code, each one returning a single item. 使用一个打开的查询,获取下一个记录界面,关闭查询,这将导致多次调用本机代码,每个调用返回一个项目。

Personally I would opt for the latter approach. 我个人会选择后一种方法。

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

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