'GCHandle.AddrOfPinnedObject()' for strings and arrays

Last edit on 2024-09-24

For System.String and System.Array, GCHandle.AddrOfPinnedObject() returns address of its buffer. So, You don't have to use Encoding.Unicode.GetBytes, fixed statements or Span<T> to aquire address of basic types.

How can it be done? Until .NET Core 2.0, GCHandle.AddrOfPinnedObject() is implemented in native-side:

// Get the address of a pinned object referenced by the supplied pinned
// handle.  This routine assumes the handle is pinned and does not check.
FCIMPL1(LPVOID, MarshalNative::GCHandleInternalAddrOfPinnedObject, OBJECTHANDLE handle)
{
    FCALL_CONTRACT;

    LPVOID p;
    OBJECTREF objRef = ObjectFromHandle(handle);

    if (objRef == NULL)
    {
        p = NULL;
    }
    else
    {
        // Get the interior pointer for the supported pinned types.
        if (objRef->GetMethodTable() == g_pStringClass)
            p = ((*(StringObject **)&objRef))->GetBuffer();
        else if (objRef->GetMethodTable()->IsArray())
            p = (*((ArrayBase**)&objRef))->GetDataPtr();
        else
            p = objRef->GetData();
    }

    return p;
}
FCIMPLEND

If object is string or array type, It returns its buffer's address by calling GetBuffer() and GetDataPtr().

In contrast, After .NET Core 3.0, GCHandle.AddrOfPinnedObject() is implemented in managed-side:

/// <summary>
/// Retrieve the address of an object in a Pinned handle.  This throws
/// an exception if the handle is any type other than Pinned.
/// </summary>
public IntPtr AddrOfPinnedObject()
{
    // Check if the handle was not a pinned handle.
    // You can only get the address of pinned handles.
    IntPtr handle = _handle;
    ThrowIfInvalid(handle);

    if (!IsPinned(handle))
    {
        ThrowHelper.ThrowInvalidOperationException_HandleIsNotPinned();
    }

    // Get the address.

    object target = InternalGet(GetHandleValue(handle));
    if (target is null)
    {
        return default;
    }

    unsafe
    {
        if (RuntimeHelpers.ObjectHasComponentSize(target))
        {
            if (target.GetType() == typeof(string))
            {
                return (IntPtr)Unsafe.AsPointer(ref Unsafe.As<string>(target).GetRawStringData());
            }

            Debug.Assert(target is Array);
            return (IntPtr)Unsafe.AsPointer(ref Unsafe.As<Array>(target).GetRawArrayData());
        }

        return (IntPtr)Unsafe.AsPointer(ref target.GetRawData());
    }
}

As described, raw address of string's buffer or array are returned by String.GetRawStringData() and Array.GetRawArrayData.