簡體   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