C#/C Interop M1 Padding/Alignment Issues
I have a small application I've written to help showcase this issue that I have uploaded to GitHub (https://github.com/Flash619/InteropTest).
When calling C methods from C# with a series of complex parameters (size_t, uint32_t, and void *) it seems memory alignment / marshalling is somehow thrown off. When both the DLL and C# are built and executed on intel x64 linux, the interop works flawlessly. However when built and executed on an M1 MacBook, the variables do not match those expected.
C# Side
[DllImport("clib", EntryPoint = "test_interop", CallingConvention = CallingConvention.Cdecl)]
private static extern int test_interop(int number1, int number2, int number3, byte[] data1, int number4, byte[] data2, int number5, int number6, int number7, int number8);
C Side
int test_interop(
const uint32_t number1,
const uint32_t number2,
const uint32_t number3,
const void *data1,
const size_t number4,
const void *data3,
const size_t number5,
const size_t number6,
const size_t number7,
const uint32_t number8
)
Output
SENT: number1: 24 number2: 48 number3: 1223 number4: 86 number5: 108 number6: 132 number7: 1935 number8: 213 RECEIVED: number1: 24 number2: 48 number3: 1223 number4: 86 number5: 108 number6: 132 number7: 914828035983 number8: 1843242248As you can see above, number7 and number8 are both incorrect, and if I remove arguments it will still be inaccurate in various unpredictable ways... The same code built/executed on linux works flawlessly so I'm sorta confused... Possibly an M1 specific issue due to the varying sizes of size_t and uint32_t?
18 Replies
uint32_t
would be uint
and size_t
would be nint
on the C# side, besides that i dunno thoI've tried to play around with nuint and uint but they had the same issue iirc.
i mean,
int
for size_t
is definitely wrong
it is a long
size_t afaik doesn't directly map to long as size_t depends on the architecture, correct?
yes, but it would be a long on x64 or arm64
and, on arm64, multiple parameters can be packed into one register. actually, this is not true, never mind
I've tried using different variable types, long, uint, nuint, etc... the result is the same. Additionally if the variable types were the cause I would expect the same incorrect values to be present on x64 intel linux correct? It works fine on x64 linux...
i mean, the problem is pretty clear to me.
number7
must be nint
, because the function is expecting to read 64 bytes of stack space but the caller only pushes 32
and this also affects number8
since it is expected to be allocated 32 bits later than it isWhy does number 1 through 6 work in that case?
on arm64, the first 8 integer arguments are passed in registers, so the size being smaller here does not cause an immediate problem. the later ones are pushed onto the stack and that absolutely will screw up if the size is not correct
i would have to look at why it works fine on SysV ABI and i kind of do not want to
So what C# types should I use for uint32_t and size_t then?
uint32_t
should be uint
. though, it doesn't matter for this case if it's int
Why not?
size_t
sould be nuint
, though again the sign does not matter
the sign does not change the way it is handled by the ABI
and, like, (uint)myInt
does not lose or change any data. the value in the processor is identical after the cast. it is just treated differently by code using that valueBut didn't you say it needed to be nint for alignment?
nint
and nuint
are identical other than the signedness. which doesn't affect this
i mean. it's nice to use the correct type. but i am just saying that nint
vs nuint
is sort of a red herring, swapping them would not fix or cause a problem hereI'll try to set these to the correct types on the C# side and report back.
sorry, i see why you got confused. yes, i should have said
nuint
to be preciseAll numbers checked and passed! Thank you so much!