Skip to content
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

Updating usages of IntPtr/UIntPtr to be the language keyword nint/nuint now that they are equivalent #70297

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public unsafe struct ComActivationContextInternal
public char* AssemblyPathBuffer;
public char* AssemblyNameBuffer;
public char* TypeNameBuffer;
public IntPtr ClassFactoryDest;
public nint ClassFactoryDest;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and many other places are pointers. nint makes them look odd. We should be changing these to void* instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to do that, but just noting its going to make this a much more in depth change.

Also noting that this isn't critical for .NET 7, its mostly "stylistic" cleanup outside the places where IntPtr.Zero and similar were swapped to something more efficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to do that, but just noting its going to make this a much more in depth change.

That's sounds fine to me. It can be done in chunks to make it reviewable.

I would switch to void* or other appropriate pointer type where possible, and keep using IntPtr for pointers where it is not possible to switch. Switch to nint/nuint only in places where the value is not pointer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and keep using IntPtr for pointers where it is not possible to switch

This is going to conflict, longer term, with editorconfig options and what IntelliSense/docs display. As of C# 11 (when targeting .NET 7 or later), IntPtr/nint and UIntPtr/nuint are true aliases of eachother, in the same way as Int32/int and UInt32/uint. They are identical in IL (not even a NativeInteger attribute any longer) and all the same style rules and support will exist/apply.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion it is about intent. Yes, in IL they are identical aliases for each other. But the important thing here is for the code reader, not the compiler or tools.

What is the intent of this variable? Is it "just a number" like a size or a counter? Or is it a pointer?

I find it super confusing to use nint for "pointer". As a reader, you need to inspect further to figure out what the intention of the variable is than just looking at its type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C# does not have one universally accepted style.

Yes. But IntelliSense does and IntelliSense is going to display nint everywhere, just as it displays int everywhere.

IntelliSense "could" respect https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0049, but that would be IntPtr and Int32 everywhere with nint and int nowhere.

Nothing says that we have to enable style rule that says use nint/uint everywhere.

https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md.

  1. We use language keywords instead of BCL types (e.g. int, string, float instead of Int32, String, Single, etc) for both type references as well as method calls (e.g. int.Parse instead of Int32.Parse). See issue Language or BCL data types #13976 for examples.

Now that IntPtr and nint are true aliases of eachother, nint is a keyword and is per our rules and the general styling/editorconfig rules, preferred.


Users are going to have to update their incorrect expectations here and change. This is and has always been the case for many languages outside of C#.

We can improve some things on our own end by utilizing pointers instead, where feasible. But trying to continue perpetuating the leaky abstraction that IntPtr is somehow different from nint is flawed and not going to end up in a positive area.

This was already discussed in depth by API review and C# LDM with consideration for how other languages do things, how APIs are exposed, and what our official guidance for exposing such APIs is.

Copy link
Member Author

@tannergooding tannergooding Jun 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever precedence we set in our own code is one that, for better or worse, is going to be used as a representative example for other codebases.

The handful of APIs that fall into the weird category of "this should have been void* but the API review committee at the time had an aversion to unsafe code" will be exposed to the end user as nint and that is how everyone is going to see that API in VS, regardless of what we use in source.

Because of that, just being consistent with our own existing rules and guidelines should be fine and will help ensure that source link vs decompilation match up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever precedence we set in our own code is one that, for better or worse, is going to be used as a representative example for other codebases.

Agree. I believe that blank find&replace of IntPtr with nint is a bad precedence to set. We should set the precedence by converting IntPtr pointer values to use proper unmanaged pointer values where possible.

As the language evolves, the coding style has to evolve as well. I am proposing to add a new rule to the repo style rule:

We use unmanaged pointers (e.g. void*, byte*) for pointer values where possible. IntPtr/nint pointer values used by legacy APIs should be converted to and from unmanaged pointers at once (e.g. void* p = (void*)Marshal.AllocHGlobal(...); Marshal.FreeGlobal((IntPtr)p);).

Copy link
Member Author

@tannergooding tannergooding Jun 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I agree with that. The remaining bit is whether AllocHGlobal is declared in source as IntPtr AllocHGlobal(nint cb) or as nint AllocHGlobal(nint cb).

IntelliSense, tooling, and other bits are all going to show and default to the latter for .NET 7+ and so I think we should be consistent there. The proposed rule you gave should be that we convert the returned nint to void* at all usage sites, since we can't actually change the API signature to return void*.

It might be beneficial to have an analyzer to help flag these edge cases, but that's not a strict need.

Copy link
Member

@jkotas jkotas Jun 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining bit is whether AllocHGlobal is declared in source as IntPtr AllocHGlobal(nint cb) or as nint AllocHGlobal(nint cb).

I do not have a strong opinion on that. It is going to need a comment that explains that it is actually a pointer. It can be also declared as nint /* IntPtr */ AllocHGlobal(nint cb) or nint /* void* */ AllocHGlobal(nint cb) to get the point across.

}

//
Expand Down Expand Up @@ -44,7 +44,7 @@ internal interface IClassFactory
void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
ref Guid riid,
out IntPtr ppvObject);
out nint ppvObject);

void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal interface IClassFactory2 : IClassFactory
new void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
ref Guid riid,
out IntPtr ppvObject);
out nint ppvObject);

new void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);

Expand All @@ -54,7 +54,7 @@ void CreateInstanceLic(
[MarshalAs(UnmanagedType.Interface)] object? pUnkReserved,
ref Guid riid,
[MarshalAs(UnmanagedType.BStr)] string bstrKey,
out IntPtr ppvObject);
out nint ppvObject);
}

internal partial struct ComActivationContext
Expand All @@ -71,9 +71,9 @@ public static unsafe ComActivationContext Create(ref ComActivationContextInterna
{
ClassId = cxtInt.ClassId,
InterfaceId = cxtInt.InterfaceId,
AssemblyPath = Marshal.PtrToStringUni(new IntPtr(cxtInt.AssemblyPathBuffer))!,
AssemblyName = Marshal.PtrToStringUni(new IntPtr(cxtInt.AssemblyNameBuffer))!,
TypeName = Marshal.PtrToStringUni(new IntPtr(cxtInt.TypeNameBuffer))!
AssemblyPath = Marshal.PtrToStringUni((nint)cxtInt.AssemblyPathBuffer)!,
AssemblyName = Marshal.PtrToStringUni((nint)cxtInt.AssemblyNameBuffer)!,
TypeName = Marshal.PtrToStringUni((nint)cxtInt.TypeNameBuffer)!
};
}
}
Expand Down Expand Up @@ -237,14 +237,14 @@ private static unsafe int GetClassFactoryForTypeInternal(ComActivationContextInt
0x{(ulong)cxtInt.AssemblyPathBuffer:x}
0x{(ulong)cxtInt.AssemblyNameBuffer:x}
0x{(ulong)cxtInt.TypeNameBuffer:x}
0x{cxtInt.ClassFactoryDest.ToInt64():x}");
0x{(ulong)cxtInt.ClassFactoryDest:x}");
}

try
{
var cxt = ComActivationContext.Create(ref cxtInt);
object cf = GetClassFactoryForType(cxt);
IntPtr nativeIUnknown = Marshal.GetIUnknownForObject(cf);
nint nativeIUnknown = Marshal.GetIUnknownForObject(cf);
Marshal.WriteIntPtr(cxtInt.ClassFactoryDest, nativeIUnknown);
}
catch (Exception e)
Expand Down Expand Up @@ -279,11 +279,11 @@ private static unsafe int RegisterClassForTypeInternal(ComActivationContextInter
0x{(ulong)cxtInt.AssemblyPathBuffer:x}
0x{(ulong)cxtInt.AssemblyNameBuffer:x}
0x{(ulong)cxtInt.TypeNameBuffer:x}
0x{cxtInt.ClassFactoryDest.ToInt64():x}");
0x{(ulong)cxtInt.ClassFactoryDest:x}");
}

if (cxtInt.InterfaceId != Guid.Empty
|| cxtInt.ClassFactoryDest != IntPtr.Zero)
|| cxtInt.ClassFactoryDest != 0)
{
throw new ArgumentException(null, nameof(pCxtInt));
}
Expand Down Expand Up @@ -324,11 +324,11 @@ private static unsafe int UnregisterClassForTypeInternal(ComActivationContextInt
0x{(ulong)cxtInt.AssemblyPathBuffer:x}
0x{(ulong)cxtInt.AssemblyNameBuffer:x}
0x{(ulong)cxtInt.TypeNameBuffer:x}
0x{cxtInt.ClassFactoryDest.ToInt64():x}");
0x{(ulong)cxtInt.ClassFactoryDest:x}");
}

if (cxtInt.InterfaceId != Guid.Empty
|| cxtInt.ClassFactoryDest != IntPtr.Zero)
|| cxtInt.ClassFactoryDest != 0)
{
throw new ArgumentException(null, nameof(pCxtInt));
}
Expand Down Expand Up @@ -446,7 +446,7 @@ public static Type GetValidatedInterfaceType([DynamicallyAccessedMembers(Dynamic
throw new InvalidCastException();
}

public static IntPtr GetObjectAsInterface(object obj, Type interfaceType)
public static nint GetObjectAsInterface(object obj, Type interfaceType)
{
// If the requested "interface type" is type object then return as IUnknown
if (interfaceType == typeof(object))
Expand All @@ -462,9 +462,9 @@ public static IntPtr GetObjectAsInterface(object obj, Type interfaceType)
// Scenarios where this is relevant:
// - Interfaces that use Generics
// - Interfaces that define implementation
IntPtr interfaceMaybe = Marshal.GetComInterfaceForObject(obj, interfaceType, CustomQueryInterfaceMode.Ignore);
nint interfaceMaybe = Marshal.GetComInterfaceForObject(obj, interfaceType, CustomQueryInterfaceMode.Ignore);

if (interfaceMaybe == IntPtr.Zero)
if (interfaceMaybe == 0)
{
// E_NOINTERFACE
throw new InvalidCastException();
Expand All @@ -476,11 +476,11 @@ public static IntPtr GetObjectAsInterface(object obj, Type interfaceType)
public static object CreateAggregatedObject(object pUnkOuter, object comObject)
{
Debug.Assert(pUnkOuter != null && comObject != null);
IntPtr outerPtr = Marshal.GetIUnknownForObject(pUnkOuter);
nint outerPtr = Marshal.GetIUnknownForObject(pUnkOuter);

try
{
IntPtr innerPtr = Marshal.CreateAggregatedObject(outerPtr, comObject);
nint innerPtr = Marshal.CreateAggregatedObject(outerPtr, comObject);
return Marshal.GetObjectForIUnknown(innerPtr);
}
finally
Expand All @@ -494,7 +494,7 @@ public static object CreateAggregatedObject(object pUnkOuter, object comObject)
public void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
ref Guid riid,
out IntPtr ppvObject)
out nint ppvObject)
{
Type interfaceType = BasicClassFactory.GetValidatedInterfaceType(_classType, ref riid, pUnkOuter);

Expand Down Expand Up @@ -532,7 +532,7 @@ public LicenseClassFactory(Guid clsid, [DynamicallyAccessedMembers(DynamicallyAc
public void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
ref Guid riid,
out IntPtr ppvObject)
out nint ppvObject)
{
CreateInstanceInner(pUnkOuter, ref riid, key: null, isDesignTime: true, out ppvObject);
}
Expand Down Expand Up @@ -563,7 +563,7 @@ public void CreateInstanceLic(
[MarshalAs(UnmanagedType.Interface)] object? pUnkReserved,
ref Guid riid,
[MarshalAs(UnmanagedType.BStr)] string bstrKey,
out IntPtr ppvObject)
out nint ppvObject)
{
Debug.Assert(pUnkReserved == null);
CreateInstanceInner(pUnkOuter, ref riid, bstrKey, isDesignTime: false, out ppvObject);
Expand All @@ -574,7 +574,7 @@ private void CreateInstanceInner(
ref Guid riid,
string? key,
bool isDesignTime,
out IntPtr ppvObject)
out nint ppvObject)
{
Type interfaceType = BasicClassFactory.GetValidatedInterfaceType(_classType, ref riid, pUnkOuter);

Expand Down Expand Up @@ -769,7 +769,7 @@ public object AllocateAndValidateLicense([DynamicallyAccessedMembers(Dynamically
}

// See usage in native RCW code
public void GetCurrentContextInfo(RuntimeTypeHandle rth, out bool isDesignTime, out IntPtr bstrKey)
public void GetCurrentContextInfo(RuntimeTypeHandle rth, out bool isDesignTime, out nint bstrKey)
{
Type targetRcwTypeMaybe = Type.GetTypeFromHandle(rth)!;

Expand All @@ -787,9 +787,9 @@ public void GetCurrentContextInfo(RuntimeTypeHandle rth, out bool isDesignTime,
// object inside a designtime license context.
// It's purpose is to save away the license key that the CLR
// retrieved using RequestLicKey().
public void SaveKeyInCurrentContext(IntPtr bstrKey)
public void SaveKeyInCurrentContext(nint bstrKey)
{
if (bstrKey == IntPtr.Zero)
if (bstrKey == 0)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static partial class ComponentActivator
{
// This hook for when GetFunctionPointer is called when the feature is disabled allows us to
// provide error messages for known hosting scenarios such as C++/CLI.
private static void OnDisabledGetFunctionPointerCall(IntPtr typeNameNative, IntPtr methodNameNative)
private static void OnDisabledGetFunctionPointerCall(nint typeNameNative, nint methodNameNative)
{
if (!OperatingSystem.IsWindows())
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class InMemoryAssemblyLoader
/// </summary>
/// <param name="moduleHandle">The native module handle for the assembly.</param>
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath)
public static unsafe void LoadInMemoryAssembly(nint moduleHandle, nint assemblyPath)
=> throw new PlatformNotSupportedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static class InMemoryAssemblyLoader
/// </summary>
/// <param name="moduleHandle">The native module handle for the assembly.</param>
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath)
public static unsafe void LoadInMemoryAssembly(nint moduleHandle, nint assemblyPath)
{
if (!IsSupported)
throw new NotSupportedException(SR.NotSupported_CppCli);
Expand All @@ -40,23 +40,23 @@ public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assem
/// </summary>
/// <param name="moduleHandle">The native module handle for the assembly.</param>
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
/// <param name="loadContext">Load context (currently must be IntPtr.Zero)</param>
/// <param name="loadContext">Load context (currently must be 0)</param>
[UnmanagedCallersOnly]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The same C++/CLI feature switch applies to LoadInMemoryAssembly and this function. We rely on the warning from LoadInMemoryAssembly.")]
public static unsafe void LoadInMemoryAssemblyInContext(IntPtr moduleHandle, IntPtr assemblyPath, IntPtr loadContext)
public static unsafe void LoadInMemoryAssemblyInContext(nint moduleHandle, nint assemblyPath, nint loadContext)
{
if (!IsSupported)
throw new NotSupportedException(SR.NotSupported_CppCli);

if (loadContext != IntPtr.Zero)
if (loadContext != 0)
throw new ArgumentOutOfRangeException(nameof(loadContext));

LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, AssemblyLoadContext.Default);
}

[RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")]
private static void LoadInMemoryAssemblyInContextImpl(IntPtr moduleHandle, IntPtr assemblyPath, AssemblyLoadContext? alc = null)
private static void LoadInMemoryAssemblyInContextImpl(nint moduleHandle, nint assemblyPath, AssemblyLoadContext? alc = null)
{
string? assemblyPathString = Marshal.PtrToStringUni(assemblyPath);
if (assemblyPathString == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private static int GetCVTypeFromClass(Type ctype)
#region Private FCalls

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void ChangeTypeEx(ref Variant result, ref Variant source, int lcid, IntPtr typeHandle, int cvType, short flags);
private static extern void ChangeTypeEx(ref Variant result, ref Variant source, int lcid, nint typeHandle, int cvType, short flags);

#endregion
}
Expand Down
18 changes: 9 additions & 9 deletions src/coreclr/System.Private.CoreLib/src/System/ArgIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ namespace System
[StructLayout(LayoutKind.Sequential)]
public ref struct ArgIterator
{
private IntPtr ArgCookie; // Cookie from the EE.
private nint ArgCookie; // Cookie from the EE.

// The SigPointer structure consists of the following members. (Note: this is an inline native SigPointer data type)
private IntPtr sigPtr; // Pointer to remaining signature.
private IntPtr sigPtrLen; // Remaining length of the pointer
private nint sigPtr; // Pointer to remaining signature.
private nint sigPtrLen; // Remaining length of the pointer

// Note, sigPtrLen is actually a DWORD, but on 64bit systems this structure becomes
// 8-byte aligned, which requires us to pad it.

private IntPtr ArgPtr; // Pointer to remaining args.
private nint ArgPtr; // Pointer to remaining args.
private int RemainingArgs; // # of remaining args.

#if (TARGET_WINDOWS && !TARGET_ARM) // Native Varargs are not supported on Unix (all architectures) and Windows ARM
[MethodImpl(MethodImplOptions.InternalCall)]
private extern ArgIterator(IntPtr arglist);
private extern ArgIterator(nint arglist);

// create an arg iterator that points at the first argument that
// is not statically declared (that is the first ... arg)
Expand All @@ -36,7 +36,7 @@ public ArgIterator(RuntimeArgumentHandle arglist) : this(arglist.Value)
}

[MethodImpl(MethodImplOptions.InternalCall)]
private extern unsafe ArgIterator(IntPtr arglist, void* ptr);
private extern unsafe ArgIterator(nint arglist, void* ptr);

// create an arg iterator that points just past 'firstArg'.
// 'arglist' is the value returned by the ARGLIST instruction
Expand Down Expand Up @@ -70,7 +70,7 @@ public TypedReference GetNextArg()
[CLSCompliant(false)]
public TypedReference GetNextArg(RuntimeTypeHandle rth)
{
if (sigPtr != IntPtr.Zero)
if (sigPtr != 0)
{
// This is an ordinary ArgIterator capable of determining
// types from a signature. Just do a regular GetNextArg.
Expand All @@ -83,7 +83,7 @@ public TypedReference GetNextArg(RuntimeTypeHandle rth)
// type). Check that ArgPtr isn't zero or this API will allow a
// malicious caller to increment the pointer to an arbitrary
// location in memory and read the contents.
if (ArgPtr == IntPtr.Zero)
if (ArgPtr == 0)
#pragma warning disable CA2208 // Instantiate argument exceptions correctly, the argument not applicable
throw new ArgumentNullException();
#pragma warning restore CA2208
Expand Down Expand Up @@ -118,7 +118,7 @@ public void End()

public unsafe RuntimeTypeHandle GetNextArgType()
{
return new RuntimeTypeHandle(Type.GetTypeFromHandleUnsafe((IntPtr)_GetNextArgType()));
return new RuntimeTypeHandle(Type.GetTypeFromHandleUnsafe((nint)_GetNextArgType()));
}

public override int GetHashCode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ public static unsafe void Clear(Array array)
}
else
{
Debug.Assert(totalByteLength % (nuint)sizeof(IntPtr) == 0);
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref pStart), totalByteLength / (nuint)sizeof(IntPtr));
Debug.Assert(totalByteLength % (nuint)sizeof(nint) == 0);
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, nint>(ref pStart), totalByteLength / (nuint)sizeof(nint));
}

// GC.KeepAlive(array) not required. pMT kept alive via `pStart`
Expand Down Expand Up @@ -216,7 +216,7 @@ public static unsafe void Clear(Array array, int index, int length)
nuint byteLength = (uint)length * elementSize;

if (pMT->ContainsGCPointers)
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, nint>(ref ptr), byteLength / (uint)sizeof(nint));
else
SpanHelpers.ClearWithoutReferences(ref ptr, byteLength);

Expand Down Expand Up @@ -375,7 +375,7 @@ public unsafe int GetLowerBound(int dimension)
private unsafe bool IsValueOfElementType(object value)
{
MethodTable* thisMT = RuntimeHelpers.GetMethodTable(this);
return (IntPtr)thisMT->ElementType == (IntPtr)RuntimeHelpers.GetMethodTable(value);
return (nint)thisMT->ElementType == (nint)RuntimeHelpers.GetMethodTable(value);
}

// if this is an array of value classes and that value class has a default constructor
Expand Down
Loading