1

In Delphi I have a structure like this:

  TCustomerInfo = Packed Record
    CustomerNo: Integer;
    FirstName: String[50];
    LastName: String[50];
  End;

With a dummy-proc like this:

procedure GetCustomer(CustomerNo: Integer; var CustomerInfo: TCustomerInfo);
begin
  CustomerInfo.CustomerNo := 19901;
  CustomerInfo.FirstName := 'JOHN';
  CustomerInfo.LastName := 'DOE';
end;

In C# I have this:

 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=1)]
 struct CUSTOMER_INFO
 {
  public Int32 CustomerNo;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=50)]
  public string FirstName;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=50)]
  public string LastName;
 }

With an imported Delphi function like this:

 [DllImport("Exceline.dll")]
 extern static void GetCustomer(Int32 CustomerNo, ref CUSTOMER_INFO CustomerInfo);

The idea is to make sure all memory allocation and storage is being handled by the C# application.

My problem is that nothing gets assigned to my C# struct upon return from GetCustomer :-/

2 Answers 2

2

I finally came up with a solution which avoids all the Alloc/FreeHGlobal, but if this is truly bulletproff with regards to the garbage collector is another matter.

The solution is to first clear the TCustomer structure with FillChar then copy the data using the Move procedure.

The delphi record looks like this:

  TCustomer = packed record
    CustomerNo: Integer;
    FirstName: array [1..50] of Char;
    LastName: array [1..50] of Char;
  end;

Then i copy the string to the structure with a procedure:

procedure StrToBuf(Str: String; var buf);
begin
  Move(Pointer(str)^, buf, Length(str));
end;

Within a proc more or less like this:

procedure LoadCustomerFromQuery(var Query: TQuery; var Customer: TCustomer); stdcall;
begin

  FillChar(Customer, SizeOf(Customer), 0);

  StrToBuf(Query.FieldByName('FNAVN').AsString, Customer.FirstName);
  StrToBuf(Query.FieldByName('ENAVN').AsString, Customer.LastName);

  Customer.CustomerNo := Query.FieldByName('KUNDENR').AsInteger;

end;

Finally the C# struct looks something like this:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=1)]
public struct TCustomer
{
    public Int32 CustomerNo;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string FirstName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string LastName;
}
Sign up to request clarification or add additional context in comments.

1 Comment

StrToBuf should have sizeof (char) in unicode Delphi.
1
extern static void GetCustomer(Int32 CustomerNo, IntPtr CustomerInfo);
...
var info = default(CUSTOMER_INFO);
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(info));
Marshal.StructureToPtr(info, ptr, false);
GetCustomer(n, ptr);
Marshal.PtrToStructure(ptr, info);
Marshal.FreeHGlobal(ptr);

4 Comments

Okay thanks, I'm getting closer with this, but I get an error on: Marshal.PtrToStructure(ptr, custInf) => The structure must not be a value class.
Further it seems that C#'s ByValTStr isn't the right choice for Delphi's String[50]. I assume that Delphi use standard byte-len prefixed Pascal-strings, but I'm not allowed to use AnsiBStr in MarshalAs-attribute.
Try info = (CUSTOMER_INFO)Marshal.PtrToStructure(ptr, typeof(CUSTOMER_INFO)); instead. As for the Delphi strings, they're an alias for AnsiString, which I think is just an array of chars, 1 byte per. I'm not sure if the length is prefixed - any chance of a dump on what you get?
A string[50] is essentially an array of 51 bytes where the first is the length byte. (If you want it zero-terminated you have to do this manually.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.