-
Notifications
You must be signed in to change notification settings - Fork 6k
Update best-practices.md for cross-platform C long #23466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a166d86
11cb3d1
2b26da4
d007a23
3b8b004
e52394a
d7d639d
e126fbc
9bdfb57
f3ab433
7848881
77ce9ff
e353b12
bff1261
e44d4d9
98771f0
a979181
89e658a
133f36e
f55a0b3
d7da6e8
2333611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,29 +169,29 @@ Here is a list of data types commonly used in Windows APIs and which C# types to | |
|
||
The following types are the same size on 32-bit and 64-bit Windows, despite their names. | ||
|
||
| Width | Windows | C (Windows) | C# | Alternative | | ||
|:------|:-----------------|:---------------------|:---------|:-------------------------------------| | ||
| 32 | `BOOL` | `int` | `int` | `bool` | | ||
| 8 | `BOOLEAN` | `unsigned char` | `byte` | `[MarshalAs(UnmanagedType.U1)] bool` | | ||
| 8 | `BYTE` | `unsigned char` | `byte` | | | ||
| 8 | `CHAR` | `char` | `sbyte` | | | ||
| 8 | `UCHAR` | `unsigned char` | `byte` | | | ||
| 16 | `SHORT` | `short` | `short` | | | ||
| 16 | `CSHORT` | `short` | `short` | | | ||
| 16 | `USHORT` | `unsigned short` | `ushort` | | | ||
| 16 | `WORD` | `unsigned short` | `ushort` | | | ||
| 16 | `ATOM` | `unsigned short` | `ushort` | | | ||
| 32 | `INT` | `int` | `int` | | | ||
| 32 | `LONG` | `long` | `int` | | | ||
| 32 | `ULONG` | `unsigned long` | `uint` | | | ||
| 32 | `DWORD` | `unsigned long` | `uint` | | | ||
| 64 | `QWORD` | `long long` | `long` | | | ||
| 64 | `LARGE_INTEGER` | `long long` | `long` | | | ||
| 64 | `LONGLONG` | `long long` | `long` | | | ||
| 64 | `ULONGLONG` | `unsigned long long` | `ulong` | | | ||
| 64 | `ULARGE_INTEGER` | `unsigned long long` | `ulong` | | | ||
| 32 | `HRESULT` | `long` | `int` | | | ||
| 32 | `NTSTATUS` | `long` | `int` | | | ||
| Width | Windows | C# | Alternative | | ||
|:------|:-----------------|:---------|:-------------------------------------| | ||
| 32 | `BOOL` | `int` | `bool` | | ||
| 8 | `BOOLEAN` | `byte` | `[MarshalAs(UnmanagedType.U1)] bool` | | ||
| 8 | `BYTE` | `byte` | | | ||
| 8 | `CHAR` | `sbyte` | | | ||
| 8 | `UCHAR` | `byte` | | | ||
| 16 | `SHORT` | `short` | | | ||
| 16 | `CSHORT` | `short` | | | ||
| 16 | `USHORT` | `ushort` | | | ||
| 16 | `WORD` | `ushort` | | | ||
| 16 | `ATOM` | `ushort` | | | ||
| 32 | `INT` | `int` | | | ||
| 32 | `LONG` | `int` | See [`CLong` and `CULong`](#cc-long). | | ||
| 32 | `ULONG` | `uint` | See [`CLong` and `CULong`](#cc-long). | | ||
| 32 | `DWORD` | `uint` | | | ||
| 64 | `QWORD` | `long` | | | ||
| 64 | `LARGE_INTEGER` | `long` | | | ||
| 64 | `LONGLONG` | `long` | | | ||
| 64 | `ULONGLONG` | `ulong` | | | ||
| 64 | `ULARGE_INTEGER` | `ulong` | | | ||
| 32 | `HRESULT` | `int` | | | ||
| 32 | `NTSTATUS` | `int` | | | ||
|
||
The following types, being pointers, do follow the width of the platform. Use `IntPtr`/`UIntPtr` for these. | ||
|
||
|
@@ -251,6 +251,61 @@ finally | |
} | ||
``` | ||
|
||
## Cross-platform data type considerations | ||
|
||
There are types in the C/C++ language that have latitude in how they are defined. When writing cross-platform interop, cases can arise where platforms differ and can cause issues if not considered. | ||
|
||
### C/C++ `long` | ||
|
||
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
C/C++ `long` and C# `long` are not the same types. Using C# `long` to interop with C/C++ `long` is almost never correct. | ||
|
||
The `long` type in C/C++ is defined to have ["at least 32"](https://en.cppreference.com/w/c/language/arithmetic_types) bits. This means there is a minimum number of required bits, but platforms can choose to use more bits if desired. The following table illustrates the differences in provided bits for the C/C++ `long` data type between platforms. | ||
|
||
| Platform | 32-bit | 64-bit | | ||
|:------------|:-------|:-------| | ||
| Windows | 32 | 32 | | ||
| macOS/\*nix | 32 | 64 | | ||
|
||
These differences can make authoring cross-platform P/Invokes difficult when the native function is defined to use `long` on all platforms. | ||
|
||
In .NET 6 and later versions, use the [`CLong` and `CULong`](https://github.com/dotnet/runtime/issues/13788) types for interop with C/C++ `long` and `unsigned long` data types. The following example is for `CLong`, but you can use `CULong` to abstract `unsigned long` in a similar way. | ||
|
||
```csharp | ||
// Cross platform C function | ||
// long Function(long a); | ||
[DllImport("NativeLib")] | ||
extern static CLong Function(CLong a); | ||
|
||
// Usage | ||
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
nint result = Function(new CLong(10)).Value; | ||
``` | ||
|
||
When targeting .NET 5 and earlier versions, you should declare separate Windows and non-Windows signatures to handle the problem. | ||
|
||
```csharp | ||
static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); | ||
|
||
// Cross platform C function | ||
// long Function(long a); | ||
|
||
[DllImport("NativeLib", EntryPoint = "Function")] | ||
extern static int FunctionWindows(int a); | ||
|
||
[DllImport("NativeLib", EntryPoint = "Function")] | ||
extern static nint FunctionUnix(nint a); | ||
|
||
// Usage | ||
nint result; | ||
if (IsWindows) | ||
{ | ||
result = FunctionWindows(10); | ||
} | ||
else | ||
{ | ||
result = FunctionUnix(10); | ||
} | ||
``` | ||
|
||
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
## Structs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc jumps back and forth between general guidelines and very specific Windows stuff. I think overall flow of this document can be improved. It can be done in a different PR. General guidelines - strings, structs, fixed buffers, etc. It may be even fine to split this into multiple docs. I see that we have Writing cross platform P/Invoke code. Should the "Cross-platform data type considerations" topic be there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. This is part of the exercise being done with the interop-doc - what is a logical flow for learning about interop and best to walk users through writing interop solutions. |
||
|
||
Managed structs are created on the stack and aren't removed until the method returns. By definition then, they are "pinned" (it won't get moved by the GC). You can also simply take the address in unsafe code blocks if native code won't use the pointer past the end of the current method. | ||
|
Uh oh!
There was an error while loading. Please reload this page.