ertank 27 Posted August 30, 2023 Hello, I need to convert a C# project into Delphi. This project is using a C++ DLL and passing a struct named Members to the DLL. I could not make same size record in Delphi. There is a 4 byte difference and Delphi is smaller. The Members struct has more than 80 string variables, one enum type and three other structs. These three other struct sizes are exactly match to Delphi conversions. They all have StructLayout as Sequential defined. But, Members struct has no StructLayout at all. Actually, it was defined as Sequential before but now that code is remarked. There are only MarshalAs for strings like below [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string StartRNo; I searched on the web and I think I should convert above to Delphi as following StartRNo: Array[0..31] of AnsiChar; This Delphi definition give me exact size for three other structs. They have only string variables. DLL function calls are CharSet.Ansi for all, but the only one using the Members struct is defined as CharSet.Unicode. If I am to use Char instead of AnsiChar on Delphi side for strings (just for the Members record), difference is a lot more like double. I am unable to figure why there is a 4 byte difference and how to make both C# Members struct and Delphi record identical. Any help is appreciated. Thanks & Regards, Ertan Share this post Link to post
FPiette 383 Posted August 30, 2023 Data alignment in records (struct) are different depending on compiler and alignment directive. Look at Delphi documentation https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Align_fields_(Delphi) To avoid that problem, I always use a packed record and insert dummy data between fields to align used fields the same way as in C++. I use offsetof macro in C/C++ to get the offset of each struct field and a similar Delphi function I wrote to get the offset of a record member. function OffsetOf(const Base : Pointer; const Field) : Integer; begin Result := Integer(UIntPtr(@Field) - UIntPtr(Base)); end; And you can call it like this: var O0 : Integer := OffsetOf(@bitmapProperties, bitmapProperties.pixelFormat); 2 1 Share this post Link to post
ertank 27 Posted August 30, 2023 I used x86 target on C# project and Delphi side is also 32bit application. Using your offset function revealed some differences. C# struct "Members" has following remarked offsets (Layout is indeed commented in the project code. I removed remark for testing and this didn't change total struct size) //[StructLayout(LayoutKind.Sequential)] public struct Members { public Tags ResponsedTag; // Offset 0 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string StartRNo; // Offset 4 // continues and ends as following public GrupDF02 groupDF02; public GrupDF41 groupDF41; public GrupDF6F groupDF6F; } However, Delphi record has following offsets. I intentionally didn't use packed record as I am completely unsure of the C# side. TMembers = record ResponsedTag: TTags; // Offset 0 StartRNo: Array[0..31] of AnsiChar; // Offset 1 // continues and ends as following groupDF02: TGrupDF02; groupDF41: TGrupDF41; groupDF6F: TGrupDF6F; end; Tags is an enum and takes up 4 bytes in C# but 1 byte in Delphi. That explains 3 bytes difference. I'm not sure about the reason of this difference. These last three item's sizes on C# and Delphi are the same. When I compare the very last item "groupDF6F" offsets, the difference is still 3 bytes. So, I could not find a reason for total difference of 4 bytes. I don't know how I can modify Delphi to match C# just for this enum difference. I could not find where this remaining 1 byte difference is coming from. Share this post Link to post
FPiette 383 Posted August 30, 2023 22 minutes ago, ertank said: I don't know how I can modify Delphi to match C# just for this enum difference. As I said, use a packed record and dummy data like this: TMembers = packed record ResponsedTag: TTags; Dummy1: array [0..2] of Byte; StartRNo: Array[0..31] of AnsiChar; // continues and ends as following groupDF02: TGrupDF02; groupDF41: TGrupDF41; groupDF6F: TGrupDF6F; end; Also, you must taken into account the sizeof the whole record and mayba add more dummy data at the end to reflect the sizeof in C#/C/C++. Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 3 hours ago, FPiette said: To avoid that problem, I always use a packed record and insert dummy data between fields to align used fields the same way as in C++. I use offsetof macro in C/C++ to get the offset of each struct field and a similar Delphi function I wrote to get the offset of a record member. This is bad advice. If you are using aligned records in Delphi, then the compiler uses the same alignment rules as for aligned C++ structs, and for aligned C# structs. Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 57 minutes ago, ertank said: Tags is an enum and takes up 4 bytes in C# but 1 byte in Delphi. In your Delphi code you should be using {$MINENUMSIZE 4} at the point where you define the enums. An example from my own codebase: //Dragon4 {$MINENUMSIZE 4} type TDragon4DigitMode = (DigitMode_Unique, DigitMode_Exact); TDragon4CutoffMode = (CutoffMode_TotalLength, CutoffMode_FractionLength); {$MINENUMSIZE 1} I would however say that using offset methods to compare offsets is very good advice. But I definitely do not advise manual alignment. The compiler can do it perfectly well. You may as well get it to do that. Then, come the day you move to 64 bit, you won't have to change a thing. 1 Share this post Link to post
FPiette 383 Posted August 30, 2023 (edited) 3 hours ago, David Heffernan said: This is bad advice. If you are using aligned records in Delphi, then the compiler uses the same alignment rules as for aligned C++ structs, and for aligned C# structs. My experience shows it is NOT always the case. Manual alignment ALWAYS work. Edited August 30, 2023 by FPiette 3 Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 10 minutes ago, FPiette said: My experience shows it is NOT always the case. Show us an example, with modern Delphi. For sure some ancient versions didn't get alignment correct. That's no longer the case. Compiler alignment always works for me, and I do a lot of it. And converting to 64 bit was trivial when I had to do that. Share this post Link to post
FPiette 383 Posted August 30, 2023 3 hours ago, David Heffernan said: For sure some ancient versions didn't get alignment correct. You are now understanding... My method works with ALL Delphi compilers. Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 48 minutes ago, FPiette said: My method works with ALL Delphi compilers. It would have been useful to say something like this, like you may need manual alignment if you need to use ancient compilers like Delphi 6. But even then, in most cases, aligned records work as expected. It's only in some very rare cases that this is not the case. Perhaps you have an example for us. Or has that knowledge been lost in the passage of time. For people that don't need to support 20 year old compilers, feel free to use the compiler to do the alignment. Share this post Link to post
ertank 27 Posted August 30, 2023 Even if I make it identical C# enum with Delphi enum, There is still one byte difference in total struct vs record comparison. I check even the last item offset in C# struct matches to Delphi and this last item size also matches on both C# and Delphi. It must be that C# has one byte more at the very bottom of its struct. @FPiette advise to add it at the end of Delphi record as a dummy variable and I can do that. I wonder if I absolutely need it? If I do need it, I wonder if there is another way of handling that in newer Delphi versions like a compiler directive {$MINENUMSIZE 4}? Thanks. Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 (edited) 1 hour ago, ertank said: Even if I make it identical C# enum with Delphi enum, There is still one byte difference in total struct vs record comparison. I check even the last item offset in C# struct matches to Delphi and this last item size also matches on both C# and Delphi. It must be that C# has one byte more at the very bottom of its struct. @FPiette advise to add it at the end of Delphi record as a dummy variable and I can do that. I wonder if I absolutely need it? If I do need it, I wonder if there is another way of handling that in newer Delphi versions like a compiler directive {$MINENUMSIZE 4}? Thanks. Hard to comment on this without seeing you actual code. This is all about the detail and we have none of it. For all I know you are using packed records and manually inserting padding. In which case, if that's what you've decided to do then you know how to fix it. Edited August 30, 2023 by David Heffernan Share this post Link to post
FPiette 383 Posted August 30, 2023 (edited) 2 hours ago, ertank said: @FPiette advise to add it at the end of Delphi record as a dummy variable and I can do that. I wonder if I absolutely need it? This is required to do that because there may be cases when sizeof() is used to create an array of structures, array which could be written to disk or send to another program or context where the sizeof the structure is different. The dummy data at the end of the structure will never be used except for padding the structure to have the exact required size. Edited August 30, 2023 by FPiette Share this post Link to post
David Heffernan 2345 Posted August 30, 2023 (edited) Of course the other problem with packing a record that should be aligned is that gives it alignment of 1 so they can be misaligned. These days that doesn't usually matter for x86/x64 but it can be a issue for some SSE2 instructions and for arm processors. Edited August 30, 2023 by David Heffernan Share this post Link to post