diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFArray.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFArray.cs new file mode 100644 index 0000000..c5c189b --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFArray.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +// Declared as signed long, which has sizeof(void*) on OSX. +using CFIndex = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CoreFoundationLibrary, EntryPoint = "CFArrayGetCount")] + private static partial CFIndex _CFArrayGetCount(SafeCFArrayHandle cfArray); + + // Follows the "Get" version of the "Create" rule, so needs to return an IntPtr to + // prevent CFRelease from being called on the SafeHandle close. + [LibraryImport(Libraries.CoreFoundationLibrary, EntryPoint = "CFArrayGetValueAtIndex")] + private static partial IntPtr CFArrayGetValueAtIndex(SafeCFArrayHandle cfArray, CFIndex index); + + internal static long CFArrayGetCount(SafeCFArrayHandle cfArray) + { + return _CFArrayGetCount(cfArray).ToInt64(); + } + + internal static IntPtr CFArrayGetValueAtIndex(SafeCFArrayHandle cfArray, int index) + { + return CFArrayGetValueAtIndex(cfArray, new CFIndex(index)); + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFData.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFData.cs new file mode 100644 index 0000000..b7f10be --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFData.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +// Declared as signed long, which has sizeof(void*) on OSX. +using CFIndex = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static unsafe partial byte* CFDataGetBytePtr(SafeCFDataHandle cfData); + + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial CFIndex CFDataGetLength(SafeCFDataHandle cfData); + + internal static unsafe Span CFDataDangerousGetSpan(SafeCFDataHandle cfData) + { + long length = CFDataGetLength(cfData).ToInt64(); + byte* dataBytes = CFDataGetBytePtr(cfData); + return new Span(dataBytes, checked((int)length)); + } + + internal static byte[] CFGetData(SafeCFDataHandle cfData) + { + return CFDataDangerousGetSpan(cfData).ToArray(); + } + + internal static unsafe bool TryCFWriteData(SafeCFDataHandle cfData, Span destination, out int bytesWritten) + { + long length = CFDataGetLength(cfData).ToInt64(); + + if (length > 0) + { + if (destination.Length < length) + { + bytesWritten = 0; + return false; + } + + byte* dataBytes = CFDataGetBytePtr(cfData); + fixed (byte* destinationPtr = &MemoryMarshal.GetReference(destination)) + { + Buffer.MemoryCopy(dataBytes, destinationPtr, destination.Length, length); + } + } + + bytesWritten = (int)length; + return true; + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDate.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDate.cs new file mode 100644 index 0000000..99ad3f1 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDate.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +#pragma warning disable SA1121 // we don't want to simplify built-ins here as we're using aliasing +using CFAbsoluteTime = System.Double; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + // https://developer.apple.com/reference/corefoundation/cfabsolutetime + private static readonly DateTime s_cfDateEpoch = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial SafeCFDateHandle CFDateCreate(IntPtr zero, CFAbsoluteTime at); + + internal static SafeCFDateHandle CFDateCreate(DateTime date) + { + Debug.Assert( + date.Kind != DateTimeKind.Unspecified, + "DateTimeKind.Unspecified should be specified to Local or UTC by the caller"); + + // UTC stays unchanged, Local is changed. + // Unspecified gets treated as Local (which may or may not be desired). + DateTime utcDate = date.ToUniversalTime(); + + double epochDeltaSeconds = (utcDate - s_cfDateEpoch).TotalSeconds; + + SafeCFDateHandle cfDate = CFDateCreate(IntPtr.Zero, epochDeltaSeconds); + + if (cfDate.IsInvalid) + { + cfDate.Dispose(); + throw new OutOfMemoryException(); + } + + return cfDate; + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDictionary.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDictionary.cs new file mode 100644 index 0000000..5d5dc6f --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFDictionary.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CoreFoundationLibrary)] + internal static partial IntPtr CFDictionaryGetValue(SafeCFDictionaryHandle handle, IntPtr key); + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFError.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFError.cs new file mode 100644 index 0000000..a220365 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFError.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +// Declared as signed long, which has sizeof(void*) on OSX. +using CFIndex = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial CFIndex CFErrorGetCode(SafeCFErrorHandle cfError); + + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial SafeCFStringHandle CFErrorCopyDescription(SafeCFErrorHandle cfError); + + internal static int GetErrorCode(SafeCFErrorHandle cfError) + { + unchecked + { + return (int)(CFErrorGetCode(cfError).ToInt64()); + } + } + + internal static string? GetErrorDescription(SafeCFErrorHandle cfError) + { + if (cfError.IsInvalid) + { + return null; + } + + using SafeCFStringHandle cfString = CFErrorCopyDescription(cfError); + return CFStringToString(cfString); + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFNumber.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFNumber.cs new file mode 100644 index 0000000..22d51bd --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFNumber.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + internal enum CFNumberType + { + kCFNumberIntType = 9, + } + + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static unsafe partial int CFNumberGetValue(IntPtr handle, CFNumberType type, int* value); + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFProxy.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFProxy.cs new file mode 100644 index 0000000..9122f43 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFProxy.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +using CFRunLoopSourceRef = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CFNetworkLibrary)] + internal static partial SafeCFDictionaryHandle CFNetworkCopySystemProxySettings(); + + [LibraryImport(Libraries.CFNetworkLibrary)] + internal static partial SafeCFArrayHandle CFNetworkCopyProxiesForURL(SafeCreateHandle url, SafeCFDictionaryHandle proxySettings); + + internal delegate void CFProxyAutoConfigurationResultCallback(IntPtr client, IntPtr proxyList, IntPtr error); + + [LibraryImport(Libraries.CFNetworkLibrary)] + internal static partial CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationURL( + IntPtr proxyAutoConfigURL, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [LibraryImport(Libraries.CFNetworkLibrary)] + internal static partial CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationScript( + IntPtr proxyAutoConfigurationScript, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [StructLayout(LayoutKind.Sequential)] + internal struct CFStreamClientContext + { + public IntPtr Version; + public IntPtr Info; + public IntPtr Retain; + public IntPtr Release; + public IntPtr CopyDescription; + } + + internal sealed class CFProxy + { + private SafeCFDictionaryHandle _dictionary; + + internal static readonly string? kCFProxyTypeAutoConfigurationURL; + internal static readonly string? kCFProxyTypeAutoConfigurationJavaScript; + internal static readonly string? kCFProxyTypeFTP; + internal static readonly string? kCFProxyTypeHTTP; + internal static readonly string? kCFProxyTypeHTTPS; + internal static readonly string? kCFProxyTypeSOCKS; + + private static readonly IntPtr kCFProxyAutoConfigurationJavaScriptKey; + private static readonly IntPtr kCFProxyAutoConfigurationURLKey; + private static readonly IntPtr kCFProxyHostNameKey; + private static readonly IntPtr kCFProxyPasswordKey; + private static readonly IntPtr kCFProxyPortNumberKey; + private static readonly IntPtr kCFProxyTypeKey; + private static readonly IntPtr kCFProxyUsernameKey; + +#pragma warning disable CA1810 // explicit static cctor + static CFProxy() + { + IntPtr lib = NativeLibrary.Load(Interop.Libraries.CFNetworkLibrary); + if (lib != IntPtr.Zero) + { + kCFProxyTypeAutoConfigurationURL = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationURL"); + kCFProxyTypeAutoConfigurationJavaScript = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationJavaScript"); + kCFProxyTypeFTP = LoadCFStringSymbol(lib, "kCFProxyTypeFTP"); + kCFProxyTypeHTTP = LoadCFStringSymbol(lib, "kCFProxyTypeHTTP"); + kCFProxyTypeHTTPS = LoadCFStringSymbol(lib, "kCFProxyTypeHTTPS"); + kCFProxyTypeSOCKS = LoadCFStringSymbol(lib, "kCFProxyTypeSOCKS"); + + kCFProxyAutoConfigurationJavaScriptKey = LoadSymbol(lib, "kCFProxyAutoConfigurationJavaScriptKey"); + kCFProxyAutoConfigurationURLKey = LoadSymbol(lib, "kCFProxyAutoConfigurationURLKey"); + kCFProxyHostNameKey = LoadSymbol(lib, "kCFProxyHostNameKey"); + kCFProxyPasswordKey = LoadSymbol(lib, "kCFProxyPasswordKey"); + kCFProxyPortNumberKey = LoadSymbol(lib, "kCFProxyPortNumberKey"); + kCFProxyTypeKey = LoadSymbol(lib, "kCFProxyTypeKey"); + kCFProxyUsernameKey = LoadSymbol(lib, "kCFProxyUsernameKey"); + } + } +#pragma warning restore CA1810 + + public CFProxy(SafeCFDictionaryHandle dictionary) + { + _dictionary = dictionary; + } + + private static IntPtr LoadSymbol(IntPtr lib, string name) + { + IntPtr indirect = NativeLibrary.GetExport(lib, name); + return indirect == IntPtr.Zero ? IntPtr.Zero : Marshal.ReadIntPtr(indirect); + } + + private static string LoadCFStringSymbol(IntPtr lib, string name) + { + using (SafeCFStringHandle cfString = new SafeCFStringHandle(LoadSymbol(lib, name))) + { + Debug.Assert(!cfString.IsInvalid); + return Interop.CoreFoundation.CFStringToString(cfString); + } + } + + private string? GetString(IntPtr key) + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, key); + if (dictValue != IntPtr.Zero) + { + using (SafeCFStringHandle handle = new SafeCFStringHandle(dictValue)) + { + return CFStringToString(handle); + } + } + return null; + } + + public string? ProxyType => GetString(kCFProxyTypeKey); + public string? HostName => GetString(kCFProxyHostNameKey); + public string? Username => GetString(kCFProxyUsernameKey); + public string? Password => GetString(kCFProxyPasswordKey); + + public int PortNumber + { + get + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, kCFProxyPortNumberKey); + unsafe + { + int value; + if (dictValue != IntPtr.Zero && CFNumberGetValue(dictValue, CFNumberType.kCFNumberIntType, &value) > 0) + { + return value; + } + } + return -1; + } + } + + public IntPtr AutoConfigurationURL => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationURLKey); + public IntPtr AutoConfigurationJavaScript => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationJavaScriptKey); + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFString.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFString.cs new file mode 100644 index 0000000..d5bdc4c --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFString.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + /// + /// Returns the interior pointer of the cfString if it has the specified encoding. + /// If it has the wrong encoding, or if the interior pointer isn't being shared for some reason, returns NULL + /// + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial IntPtr CFStringGetCStringPtr( + SafeCFStringHandle cfString, + CFStringBuiltInEncodings encoding); + + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial SafeCFDataHandle CFStringCreateExternalRepresentation( + IntPtr alloc, + SafeCFStringHandle theString, + CFStringBuiltInEncodings encoding, + byte lossByte); + + internal static string CFStringToString(SafeCFStringHandle cfString) + { + Debug.Assert(!cfString.IsInvalid); + + // If the string is already stored internally as UTF-8 we can (usually) + // get the raw pointer to the data blob, then we can Marshal in the string + // via pointer semantics, avoiding a copy. + IntPtr interiorPointer = CFStringGetCStringPtr( + cfString, + CFStringBuiltInEncodings.kCFStringEncodingUTF8); + + if (interiorPointer != IntPtr.Zero) + { + return Marshal.PtrToStringUTF8(interiorPointer)!; + } + + SafeCFDataHandle cfData = CFStringCreateExternalRepresentation( + IntPtr.Zero, + cfString, + CFStringBuiltInEncodings.kCFStringEncodingUTF8, + 0); + + using (cfData) + { + unsafe + { + // Note that CFDataGetLength(cfData).ToInt32() will throw on + // too large of an input. Since a >2GB string is pretty unlikely, + // that's considered a good thing here. + return Encoding.UTF8.GetString( + CFDataGetBytePtr(cfData), + CFDataGetLength(cfData).ToInt32()); + } + } + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.CFUrl.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFUrl.cs new file mode 100644 index 0000000..390799c --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.CFUrl.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [LibraryImport(Libraries.CoreFoundationLibrary)] + private static partial SafeCreateHandle CFURLCreateWithString( + IntPtr allocator, + SafeCreateHandle str, + IntPtr baseUrl); + + internal static SafeCreateHandle CFURLCreateWithString(string url) + { + Debug.Assert(url != null); + using (SafeCreateHandle stringHandle = CFStringCreateWithCString(url)) + { + return CFURLCreateWithString(IntPtr.Zero, stringHandle, IntPtr.Zero); + } + } + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreFoundation.cs b/src/Ultra.Core/Interop/Interop.CoreFoundation.cs new file mode 100644 index 0000000..c349bb4 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreFoundation.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using CFArrayRef = System.IntPtr; +using CFIndex = System.IntPtr; +using CFStringRef = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + /// + /// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework. + /// + private enum CFStringBuiltInEncodings : uint + { + kCFStringEncodingMacRoman = 0, + kCFStringEncodingWindowsLatin1 = 0x0500, + kCFStringEncodingISOLatin1 = 0x0201, + kCFStringEncodingNextStepLatin = 0x0B01, + kCFStringEncodingASCII = 0x0600, + kCFStringEncodingUnicode = 0x0100, + kCFStringEncodingUTF8 = 0x08000100, + kCFStringEncodingNonLossyASCII = 0x0BFF, + + kCFStringEncodingUTF16 = 0x0100, + kCFStringEncodingUTF16BE = 0x10000100, + kCFStringEncodingUTF16LE = 0x14000100, + kCFStringEncodingUTF32 = 0x0c000100, + kCFStringEncodingUTF32BE = 0x18000100, + kCFStringEncodingUTF32LE = 0x1c000100 + } + + /// + /// Creates a CFStringRef from a specified range of memory with a specified encoding. + /// Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The pointer to the beginning of the encoded string. + /// The number of bytes in the encoding to read. + /// The encoding type. + /// Whether or not a BOM is present. + /// A CFStringRef on success, otherwise a SafeCreateHandle(IntPtr.Zero). + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + private static partial SafeCreateHandle CFStringCreateWithBytes( + IntPtr alloc, + IntPtr bytes, + CFIndex numBytes, + CFStringBuiltInEncodings encoding, + [MarshalAs(UnmanagedType.Bool)] bool isExternalRepresentation); + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The string to get a CFStringRef for + /// The encoding of the str variable. This should be UTF 8 for OS X + /// Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero + /// For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that + [LibraryImport(Interop.Libraries.CoreFoundationLibrary, StringMarshalling = StringMarshalling.Utf8)] + private static partial SafeCreateHandle CFStringCreateWithCString( + IntPtr allocator, + string str, + CFStringBuiltInEncodings encoding); + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The string to get a CFStringRef for + /// The encoding of the str variable. This should be UTF 8 for OS X + /// Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero + /// For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that + [LibraryImport(Interop.Libraries.CoreFoundationLibrary, StringMarshalling = StringMarshalling.Utf8)] + private static partial SafeCreateHandle CFStringCreateWithCString( + IntPtr allocator, + IntPtr str, + CFStringBuiltInEncodings encoding); + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The string to get a CFStringRef for + /// Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle + internal static SafeCreateHandle CFStringCreateWithCString(string str) + { + return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); + } + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The string to get a CFStringRef for + /// Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle + internal static SafeCreateHandle CFStringCreateWithCString(IntPtr utf8str) + { + return CFStringCreateWithCString(IntPtr.Zero, utf8str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); + } + + /// + /// Creates a CFStringRef from a span of chars. + /// Follows the "Create Rule" where if you create it, you delete it. + /// + /// The chars to make a CFString version of. + /// A CFStringRef on success, otherwise a SafeCreateHandle(IntPtr.Zero). + internal static unsafe SafeCreateHandle CFStringCreateFromSpan(ReadOnlySpan source) + { + fixed (char* sourcePtr = source) + { + return CFStringCreateWithBytes( + IntPtr.Zero, + (IntPtr)sourcePtr, + new CFIndex(source.Length * 2), + CFStringBuiltInEncodings.kCFStringEncodingUTF16, + isExternalRepresentation: false); + } + } + + /// + /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The values to put in the array + /// The number of values in the array + /// Should be IntPtr.Zero + /// Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + private static unsafe partial SafeCreateHandle CFArrayCreate( + IntPtr allocator, + IntPtr* values, + UIntPtr numValues, + IntPtr callbacks); + + /// + /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The values to put in the array + /// The number of values in the array + /// Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle + internal static unsafe SafeCreateHandle CFArrayCreate(IntPtr[] values, UIntPtr numValues) + { + fixed (IntPtr* pValues = values) + { + return CFArrayCreate(IntPtr.Zero, pValues, (UIntPtr)values.Length, IntPtr.Zero); + } + } + + /// + /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The values to put in the array + /// Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle + internal static unsafe SafeCreateHandle CFArrayCreate(ReadOnlySpan values) + { + fixed (IntPtr* pValues = &MemoryMarshal.GetReference(values)) + { + return CFArrayCreate(IntPtr.Zero, pValues, (UIntPtr)values.Length, IntPtr.Zero); + } + } + + /// + /// You should retain a Core Foundation object when you receive it from elsewhere + /// (that is, you did not create or copy it) and you want it to persist. If you + /// retain a Core Foundation object you are responsible for releasing it + /// + /// The CFType object to retain. This value must not be NULL + /// The input value + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + internal static partial IntPtr CFRetain(IntPtr ptr); + + /// + /// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object. + /// + /// The pointer on which to decrement the reference count. + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + internal static partial void CFRelease(IntPtr ptr); + } +} diff --git a/src/Ultra.Core/Interop/Interop.CoreServices.SpotlightQuery.cs b/src/Ultra.Core/Interop/Interop.CoreServices.SpotlightQuery.cs new file mode 100644 index 0000000..62df217 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.CoreServices.SpotlightQuery.cs @@ -0,0 +1,81 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using Microsoft.Win32.SafeHandles; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + public static class SpotlightQuery + { + // Core Foundation / Core Services constants + private const uint kCFStringEncodingUTF8 = 0x08000100; + private const uint kMDQuerySynchronous = 1; + + #region CoreServices (Metadata) P/Invoke + + [DllImport(Interop.Libraries.CoreServicesLibrary)] + private static extern SafeMDQueryHandle MDQueryCreate( + IntPtr allocator, + SafeCreateHandle queryString, + SafeCFArrayHandle valueListAttrs, + SafeCFArrayHandle sortingAttrs); + + [DllImport(Interop.Libraries.CoreServicesLibrary)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool MDQueryExecute( + SafeMDQueryHandle query, + uint option); + + [DllImport(Interop.Libraries.CoreServicesLibrary)] + private static extern nint MDQueryGetCount( + SafeMDQueryHandle query); + + [DllImport(Interop.Libraries.CoreServicesLibrary)] + private static extern IntPtr MDQueryGetResultAtIndex( + SafeMDQueryHandle query, + nint idx); + + #endregion + + // Additional APIs for extracting attributes from MDItem results: + [DllImport(Interop.Libraries.CoreServicesLibrary)] + private static extern SafeCFStringHandle MDItemCopyAttribute( + IntPtr item, + SafeCreateHandle name); + + public static void RunSpotlightQuery(string query) + { + var cfQuery = Interop.CoreFoundation.CFStringCreateWithCString(query); + var mdQuery = MDQueryCreate(IntPtr.Zero, cfQuery, default, default); + + // Execute the query synchronously + bool success = MDQueryExecute(mdQuery, kMDQuerySynchronous); + if (!success) + { + Console.WriteLine("Failed to execute metadata query."); + return; + } + + nint count = MDQueryGetCount(mdQuery); + Console.WriteLine($"Found {count} results for query: {query}"); + + for (nint i = 0; i < count; i++) + { + IntPtr item = MDQueryGetResultAtIndex(mdQuery, i); + // `item` is likely an MDItemRef (CFTypeRef), from which you can retrieve attributes. + + // For demonstration, retrieve the kMDItemPath attribute (file path). + var cfKeyPath = Interop.CoreFoundation.CFStringCreateWithCString("kMDItemPath"); + var cfPathValue = MDItemCopyAttribute(item, cfKeyPath); + if (!cfPathValue.IsInvalid) + { + string path = Interop.CoreFoundation.CFStringToString(cfPathValue); + Console.WriteLine($"Result #{i + 1}: {path}"); + } + } + } + } +} + diff --git a/src/Ultra.Core/Interop/Interop.Libraries.cs b/src/Ultra.Core/Interop/Interop.Libraries.cs new file mode 100644 index 0000000..b9ac2f7 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.Libraries.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +internal static partial class Interop +{ + internal static partial class Libraries + { + internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; + internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; + internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; + internal const string libobjc = "/usr/lib/libobjc.dylib"; + internal const string libproc = "/usr/lib/libproc.dylib"; + internal const string libSystem = "libSystem.dylib"; + internal const string OpenLdap = "libldap.dylib"; + internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration"; + internal const string AppleCryptoNative = "libSystem.Security.Cryptography.Native.Apple"; + internal const string MsQuic = "libmsquic.dylib"; + } +} \ No newline at end of file diff --git a/src/Ultra.Core/Interop/Interop.SafeHandles.cs b/src/Ultra.Core/Interop/Interop.SafeHandles.cs new file mode 100644 index 0000000..1836bb4 --- /dev/null +++ b/src/Ultra.Core/Interop/Interop.SafeHandles.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + /// + /// This class is a wrapper around the Create pattern in OS X where + /// if a Create* function is called, the caller must also CFRelease + /// on the same pointer in order to correctly free the memory. + /// + internal record struct SafeCreateHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeMDQueryHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFStringHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFArrayHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFDataHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFDateHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFErrorHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal record struct SafeCFDictionaryHandle(nint Value) : ISafeHandle + { + public bool IsInvalid => Value == 0; + + public void Dispose() + { + if (Value != 0) + { + Interop.CoreFoundation.CFRelease(Value); + Value = 0; + } + } + } + + internal interface ISafeHandle : IDisposable + { + nint Value { get; } + } +} diff --git a/src/Ultra.Core/Interop/readme.md b/src/Ultra.Core/Interop/readme.md new file mode 100644 index 0000000..a8df025 --- /dev/null +++ b/src/Ultra.Core/Interop/readme.md @@ -0,0 +1,5 @@ +# MacOS Interop Code + +* Code extracted from [dotnet/runtime -> src/libraries/Common/src/Interop/OSX](https://github.com/dotnet/runtime/tree/1d69684de8801d500e12de8efd86e7ecfa5ed168/src/libraries/Common/src/Interop/OSX) +* License: MIT +