Get array of records from Delphi DLL using C #

I am trying to write a DLL in Delphi so that my C # application can access the Advantage database (using VS2013 and cannot access the data directly).

My problem is after making the call, the array in C # is filled with null values.

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; 

It works. I used ShowMessage to show how the information looks, and how it looks after.



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

This code executes, no errors of any type, but when I repeat the elements of the projectItems, each returns

 Id = 0 Description = null 
+6
source share
1 answer

There are many problems that I see. First of all, I would declare the structure as follows:

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

You will need to use UnmanagedType.BStr so that the string can be allocated on the unmanaged side and freed on the managed side. An alternative would be marshaling like LPWStr , but then you have to allocate CoTaskMemAlloc on the unmanaged side.

The Delphi entry becomes:

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

You can clearly see that your code is incorrect if you look at this line:

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

Here you point result[index].Description to the memory that will be freed when the function returns.


Trying to use an open Delphi array is risky at best. I would not do that. If you insist on this, you should at least consider the value passed to high , and not write at the end of the array. What more, you have to pass the correct value for maximum. This is projectItems.Length-1 .

Now you are using pass by value for the array, so nothing you write in Delphi code will return to the C # code. Moreover, C # code has [In] sorting by default, so even when you switch to pass by var, the marshaller will not march elements back to projectItems on the managed side.

Personally, I stop using the open array and will be explicit:

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

Here Items points to the first element in the array, and ItemsLen length of the provided array. The return value of the function must be the number of elements 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 this.

On the C # side, you have:

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

Name it as follows:

 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 have doubts as to whether the marshaller will marshal an array of unresponsive types. I have a feeling that this will not happen. In this case, your main options are:

  • Switch to passing the string as an IntPtr to the record. Highlight using CoTaskMemAlloc . Destroy the Marshal.FreeCoTaskMem on the managed side.
  • Use an open request, get the following recording interface, close the request, which will lead to several calls of its own code, each of which returns one element.

Personally, I would choose the latter approach.

+5
source

Source: https://habr.com/ru/post/975415/


All Articles