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).
My issue is after I make the call, the array in C# is full of null values.
The 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;
This appears to be working. I've used ShowMessage to show what the information going in looks like and what it looks like after.
The 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 kind, but when I iterate through projectItems each one returns
Id = 0
Description = null
How to Write and Call DLL's within Delphi. The following code creates a DLL containing two functions, Min and Max, with the objective of returning the larger of two integers. Start a new DLL project in Delphi (Click File −> New, select DLL). Save the project as delhpdll. Fill in the code in the library as given below.
In commercial C++ libraries, it is common to get only a few C++ headers and the static library file (.lib) without any of the accompanying .cpp source files. So, in this case, when we want to use those C++ libraries in our Delphi application, we can use a Proxy DLL to make it possible.
The following code creates a DLL containing two functions, Min and Max, with the objective of returning the larger of two integers. Start a new DLL project in Delphi (Click File −> New, select DLL). Save the project as delhpdll. Fill in the code in the library as given below.
The first function will get an index of the object and set the value of the variable. In the second function, it takes the index of the object, calls the “ getSquare ” function of the object, and store the value in the value variable. How to use the C++ Proxy DLL in a Delphi Application? We can link DLLs either statically or dynamically.
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. The alternative would be to marshal as LPWStr
but then you'd have to allocate with CoTaskMemAlloc
on the unmanaged side.
The Delphi record becomes:
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.
Trying to use a Delphi open array is risky at best. 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. What's more, you should pass the right value for high. That is 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. 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.
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. 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.
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
);
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. I have a feeling that it won't. In which case your main options are:
IntPtr
in the record. Allocate with CoTaskMemAlloc
. Destroy on the managed side with Marshal.FreeCoTaskMem
.Personally I would opt for the latter approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With