2014-09-17 7 views
6

Ich versuche, eine DLL in Delphi zu schreiben, um meine C# -App auf eine Advantage-Datenbank zugreifen (mit VS2013 und nicht in der Lage, auf die Daten direkt zugreifen).Retrieve Array von Delphi DLL mit C#

Mein Problem ist, nachdem ich den Aufruf, das Array in C# ist voll von Null-Werten.

Die Delphi-DLL-Code:

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; 

Dies scheint zu funktionieren. Ich habe ShowMessage verwendet, um zu zeigen, wie die Informationen aussehen und wie sie aussehen.



Der C# Code:

[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); 
} 

Dieser Code ausgeführt wird, keine Fehler jeglicher Art, aber wenn ich durch projectItems iterieren jeder kehrt

Id = 0 
Description = null 

Antwort

5

Es gibt durchaus ein paar Probleme das kann ich sehen. Zunächst einmal möchte ich die Struktur wie folgt erklären:

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

Sie benötigen UnmanagedType.BStr so zu verwenden, dass die Zeichenfolge auf der nicht verwalteten Seite zugeordnet werden können, und freigegeben auf der verwalteten Seite. Die Alternative wäre, als LPWStr zu marshalen, aber dann müssten Sie mit CoTaskMemAlloc auf der unmanaged Seite zuweisen.

Die Delphi-Datensatz wird:

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

Man kann deutlich sehen, dass Ihr Code ist falsch an dieser Linie von der Suche:

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

Hier können Sie result[index].Description Punkt in dem Speicher zu machen, die freigegeben werden, wenn Die Funktion kehrt zurück.


Der Versuch, ein offenes Delphi-Array zu verwenden, ist bestenfalls riskant. Ich würde das nicht tun. Wenn Sie darauf bestehen, sollten Sie zumindest den für high übergebenen Wert beachten und nicht über das Ende des Arrays schreiben. Darüber hinaus sollten Sie den richtigen Wert für hoch angeben. Das ist projectItems.Length-1.

Jetzt verwenden Sie den Übergabewert für das Array, sodass nichts, was Sie in den Delphi-Code schreiben, den Weg zurück zum C# -Code findet. Darüber hinaus verfügt der C# -Code standardmäßig über [In]. Daher wird der Marshaller die Elemente auch dann nicht auf der verwalteten Seite an die Adresse projectItems übertragen, wenn Sie zu var wechseln.

Persönlich würde ich mit einem offenen Array stoppen und explizit sein:

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

Hier Items zeigt auf das erste Element im Array und ItemsLen gibt die Länge des mitgelieferten Array. Der Rückgabewert der Funktion sollte die Anzahl der Elemente sein, die in das Array kopiert wurden.

Um dies zu implementieren, verwenden Sie entweder Zeigerarithmetik oder ($POINTERMATH ON}. Ich bevorzuge die letztere Option. Ich glaube nicht, dass ich das zeigen muss.

Auf der C# Seite Sie haben:

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

Nennen Sie es wie folgt aus:

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

alle oben gesagt hatte, habe ich einen Zweifel darüber, ob oder nicht Der Marshaller wird ein Array von nicht blitbaren Typen bereitstellen. Ich habe das Gefühl, dass es nicht geht. In diesem Fall wird Ihre wichtigsten Optionen sind:

  1. Switch, um die Zeichenfolge zurück, als IntPtr in dem Datensatz zu übergeben. Zuweisen mit CoTaskMemAlloc. Zerstören Sie auf der verwalteten Seite mit .
  2. Verwenden Sie eine offene Abfrage, rufen Sie die nächste Datensatzschnittstelle auf, schließen Sie die Abfrage, was zu mehreren Aufrufen des systemeigenen Codes führen würde, von denen jeder ein einzelnes Element zurückgibt.

Persönlich würde ich für den letzteren Ansatz entscheiden.

+0

Perfekte Antwort, vielen Dank. Die Verwendung der Zeigerarithmetik war etwas, das ich vorher nicht benutzt hatte (und daher nicht berücksichtigt wurde), aber jetzt scheint es so offensichtlich. – Ranky

+0

Wird der Marshall das nicht-blitbare Array in beide Richtungen marshalieren? –

+0

Ja, sicher. – Ranky