C/C++ → C# Windows Native API / ABI Cheat Sheet
A practical reference for translating Windows native API definitions, structures, pointers, unions, and function prototypes from C/C++ to C# P/Invoke/delegates.
1. Core Rule
When translating a native API, don’t ask:
“What C# type looks like this C type?”
Ask:
“What is the size, representation, and indirection of this native type?”
For every parameter, determine:
- Is it a value or pointer?
- What is its size?
- Is it pointer-sized?
- Is it a structure?
- Is it a union?
- Is the native function expected to read, write, or both?
- Is the type architecture-dependent?
2. Basic Integer Types
| Native C/C++ | Size | C# |
|---|---|---|
BYTE |
1 | byte |
CHAR |
1 | sbyte / byte |
UCHAR |
1 | byte |
WORD |
2 | ushort |
SHORT |
2 | short |
USHORT |
2 | ushort |
DWORD |
4 | uint |
LONG |
4 | int |
ULONG |
4 | uint |
INT |
4 | int |
UINT |
4 | uint |
LONG64 |
8 | long |
ULONG64 |
8 | ulong |
LONGLONG |
8 | long |
ULONGLONG |
8 | ulong |
Important
Don’t confuse:
|
|
with:
|
|
LONG is always 32-bit.
LONG_PTR is pointer-sized.
3. Pointer-Sized Types
These are especially important on x64.
| Native | Meaning | C# |
|---|---|---|
ULONG_PTR |
unsigned pointer-sized integer | UIntPtr / nuint |
LONG_PTR |
signed pointer-sized integer | IntPtr / nint |
SIZE_T |
unsigned pointer-sized size | UIntPtr / nuint |
SSIZE_T |
signed pointer-sized size | IntPtr / nint |
UINT_PTR |
unsigned pointer-sized integer | UIntPtr / nuint |
PVOID |
generic pointer | IntPtr |
HANDLE |
opaque pointer-sized handle | IntPtr |
Architecture
|
|
Check your process:
|
|
Expected on x64:
|
|
4. Pointers
This is one of the most important concepts.
Native value
|
|
C#:
|
|
Pointer to a value
|
|
Possible C# representation:
|
|
or:
|
|
depending on how the pointer is being used.
Pointer to a structure
|
|
C#:
|
|
Pointer to a pointer
|
|
Break it down:
|
|
C#:
|
|
5. ref vs out
out
Use when the native function produces the value.
Native:
|
|
C#:
|
|
Example:
|
|
ref
Use when the caller provides an existing value that the native function can read and/or modify.
Native:
|
|
C#:
|
|
Example:
|
|
Mental model
|
|
6. Common Windows Types
| Windows type | C# |
|---|---|
HANDLE |
IntPtr |
HMODULE |
IntPtr |
HINSTANCE |
IntPtr |
HWND |
IntPtr |
LPVOID |
IntPtr |
PVOID |
IntPtr |
LPBYTE |
IntPtr / byte* |
ACCESS_MASK |
uint |
NTSTATUS |
int |
BOOLEAN |
byte |
BOOL |
bool / int depending on API |
SIZE_T |
UIntPtr / nuint |
ULONG_PTR |
UIntPtr / nuint |
7. NTSTATUS
Native:
|
|
Usually map to:
|
|
Example:
|
|
Inspect the status
Don’t only print decimal:
|
|
Also print hexadecimal:
|
|
Example:
|
|
This makes Windows error values much easier to identify.
8. Structures
Native:
|
|
Translate the fields first:
|
|
Then create:
|
|
Why Sequential?
It tells .NET to lay the fields out in the same order as the native structure.
9. Structure Validation
Never assume your structure layout is correct.
Check its size:
|
|
On x64:
|
|
You can also inspect offsets:
|
|
10. OBJECT_ATTRIBUTES Example
Native concept:
|
|
C#:
|
|
On x64, this structure is expected to be:
|
|
Approximate offsets:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | Length |
| 4 | 4 | Padding |
| 8 | 8 | RootDirectory |
| 16 | 8 | ObjectName |
| 24 | 4 | Attributes |
| 28 | 4 | Padding |
| 32 | 8 | SecurityDescriptor |
| 40 | 8 | SecurityQualityOfService |
11. Unions
Native:
|
|
A union means:
“Multiple fields occupy the same memory location.”
Don’t use normal sequential layout for the union.
Use:
|
|
Visual representation:
|
|
12. Bitfields
Native:
|
|
C# doesn’t have native C-style bitfields.
Usually represent the underlying storage:
|
|
Then manipulate the bits manually.
For example:
|
|
13. Arrays
Native embedded array:
|
|
Possible C# representation:
|
|
But distinguish this from:
|
|
The first:
|
|
The second:
|
|
They are not equivalent.
14. Strings
ANSI
Native:
|
|
Potential C# representation:
|
|
Unicode
Native:
|
|
Potential C# representation:
|
|
Windows APIs commonly use UTF-16 Unicode strings.
15. Function Pointers
Native:
|
|
C#:
|
|
Then an exported function address can be converted into the delegate.
The important point:
“The delegate must exactly match the native function’s ABI.”
That means matching:
- Return type
- Parameter count
- Parameter order
- Parameter sizes
- Pointer/value semantics
- Calling convention
- Structure layout
16. Calling Convention
For Windows APIs, you’ll commonly encounter:
|
|
For modern x64 Windows, the underlying x64 ABI has a standardized calling convention, so the old x86 distinction between stdcall and cdecl does not work the same way.
For x86 targets, calling convention differences matter much more.
17. Example: NtOpenProcess
Native:
|
|
Translate one parameter at a time:
|
|
Final delegate:
|
|
18. Example: NtAllocateVirtualMemory
Native concept:
|
|
Translate:
|
|
Result:
|
|
Notice that ULONG_PTR is not:
|
|
because it is pointer-sized.
19. Export Name Must Match Delegate
This is an especially important lesson from debugging native APIs.
Bad:
|
|
or:
|
|
The address may resolve successfully, but the call can still fail catastrophically.
Think:
|
|
All of them must agree.
20. Don’t Trust the Function Address Alone
This:
|
|
only proves that you found an address.
It does not prove:
- ✓ Correct prototype
- ✓ Correct parameter count
- ✓ Correct parameter sizes
- ✓ Correct calling convention
- ✓ Correct structure layout
So:
|
|
21. A Systematic ABI Translation Workflow
When you find a new Windows native prototype, use this process.
Step 1 — Get the native declaration
Example:
|
|
Step 2 — Identify every base type
|
|
Step 3 — Resolve typedefs
|
|
Step 4 — Resolve indirection
|
|
Step 5 — Check structure definitions
If a parameter is:
|
|
find the actual definition of:
|
|
Don’t guess its fields.
Step 6 — Check architecture
|
|
Step 7 — Check structure sizes
|
|
Step 8 — Check the function prototype against the actual export
The function name and delegate signature must correspond.
22. The Most Useful Mental Model
Think in terms of levels of indirection.
Given:
|
|
you have:
|
|
Given:
|
|
you have:
|
|
Given:
|
|
you have:
|
|
Often represented using:
|
|
or another explicitly marshalled representation depending on what the pointer actually points to.
For:
|
|
think:
|
|
For:
|
|
think:
|
|
23. Quick Decision Tree
When you see a native parameter, ask:
|
|
Then separately ask:
- Is it pointer-sized?
- Is it an array?
- Is it a union?
- Is it a bitfield?
- Is it architecture-dependent?
24. Validation Checklist
Before calling an unfamiliar native function:
- Native prototype obtained
- Return type mapped
- Every parameter mapped
- Parameter order preserved
- Parameter count preserved
- Pointer indirection understood
- ref/out semantics understood
- Structures defined
- Struct packing/layout checked
- Unions represented with Explicit layout
- Bitfields accounted for
- Pointer-sized types identified
- x86/x64 considered
- Calling convention considered
- Export name matches function
- Function address is non-zero
- Struct sizes validated with
Marshal.SizeOf
25. The Golden Rules
If you remember only a few things, remember these:
Rule 1
Don’t map by name alone.
|
|
They may look similar but have different widths.
Rule 2
Pointers matter more than the base type.
|
|
are three different things.
Rule 3
Structure layout is part of the ABI.
The fields, order, alignment, and size all matter.
Rule 4
Unions are not normal structures.
Use:
|
|
with:
|
|
when appropriate.
Rule 5
An exported address doesn’t validate your delegate.
The function’s ABI and your delegate must match.
Rule 6
Validate assumptions.
Use:
|
|
rather than guessing.
26. One-Line Mental Shortcut
When translating Windows C → C#, think:
|
|
That process will take you much further than memorizing a giant P/Invoke table.