[英]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類型。 我有一種感覺,它不會。 在這種情況下,您的主要選項是:
IntPtr
傳遞回記錄中。 使用CoTaskMemAlloc
分配。 使用Marshal.FreeCoTaskMem
在托管端銷毀。 我個人會選擇后一種方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.