Fixed and extended functions from PathCch.h

pull/60/head
David Hall 2019-06-25 19:00:24 -06:00
parent 19d8a8d396
commit 6649425ebb
2 changed files with 429 additions and 1 deletions

View File

@ -332,6 +332,54 @@ namespace Vanara.PInvoke
[PInvokeData("pathcch.h", MSDNShortId = "89adf45f-f16d-49d1-9e76-b57b73b4d4c3")]
public static extern HRESULT PathCchAddBackslashEx(StringBuilder pszPath, SizeT cchPath, out IntPtr ppszEnd, out SizeT pcchRemaining);
/// <summary>
/// <para>
/// Adds a backslash to the end of a string to create the correct syntax for a path. If the source path already has a trailing
/// backslash, no backslash will be added.
/// </para>
/// <para>
/// This function differs from PathCchAddBackslash in that it can return a pointer to the new end of the string and report the number
/// of unused characters remaining in the buffer.
/// </para>
/// <para>This function differs from PathAddBackslash in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
/// <para>
/// <c>Note</c> This function, or <c>PathCchAddBackslashEx</c>, should be used in place of PathAddBackslash to prevent the
/// possibility of a buffer overrun.
/// </para>
/// </summary>
/// <param name="pszPath">
/// <para>
/// A pointer to the path string. When this function returns successfully, the buffer contains the string with the appended
/// backslash. This value should not be <c>NULL</c>.
/// </para>
/// </param>
/// <param name="cchPath">
/// <para>The size of the buffer pointed to by pszPath, in characters.</para>
/// </param>
/// <param name="ppszEnd">
/// <para>
/// A value that, when this function returns successfully, receives the address of a pointer to the terminating null character at the
/// end of the string.
/// </para>
/// </param>
/// <param name="pcchRemaining">
/// <para>
/// A pointer to a value that, when this function returns successfully, is set to the number of unused characters in the destination
/// buffer, including the terminating null character.
/// </para>
/// </param>
/// <returns>
/// <para>
/// This function returns S_OK if the function was successful, S_FALSE if the path string already ends in a backslash, or an error
/// code otherwise.
/// </para>
/// </returns>
// https://docs.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathcchaddbackslashex HRESULT PathCchAddBackslashEx( PWSTR
// pszPath, SizeT cchPath, PWSTR *ppszEnd, SizeT *pcchRemaining );
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "89adf45f-f16d-49d1-9e76-b57b73b4d4c3")]
public static extern HRESULT PathCchAddBackslashEx(IntPtr pszPath, SizeT cchPath, out IntPtr ppszEnd, out SizeT pcchRemaining);
/// <summary>
/// <para>Adds a file name extension to a path string.</para>
/// <para>This function differs from PathAddExtension in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
@ -1028,6 +1076,35 @@ namespace Vanara.PInvoke
[PInvokeData("pathcch.h", MSDNShortId = "dac6cf02-7b53-449c-b788-4a7b6d1622ed")]
public static extern HRESULT PathCchFindExtension(string pszPath, SizeT cchPath, out IntPtr ppszExt);
/// <summary>
/// <para>
/// Searches a path to find its file name extension, such as ".exe" or ".ini". This function does not search for a specific
/// extension; it searches for the presence of any extension.
/// </para>
/// <para>This function differs from PathFindExtension in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
/// <para><c>Note</c> This function should be used in place of PathFindExtension to prevent the possibility of a buffer overrun.</para>
/// </summary>
/// <param name="pszPath">
/// <para>A pointer to the path to search.</para>
/// </param>
/// <param name="cchPath">
/// <para>The size of the buffer pointed to by pszPath, in characters.</para>
/// </param>
/// <param name="ppszExt">
/// <para>
/// The address of a pointer that, when this function returns successfully, points to the "." character that precedes the extension
/// within pszPath. If no extension is found, it points to the string's terminating null character.
/// </para>
/// </param>
/// <returns>
/// <para>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</para>
/// </returns>
// https://docs.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathcchfindextension HRESULT PathCchFindExtension( PCWSTR
// pszPath, SizeT cchPath, PCWSTR *ppszExt );
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "dac6cf02-7b53-449c-b788-4a7b6d1622ed")]
public static extern HRESULT PathCchFindExtension(IntPtr pszPath, SizeT cchPath, out IntPtr ppszExt);
/// <summary>
/// <para>Determines whether a path string refers to the root of a volume.</para>
/// <para>This function differs from PathIsRoot in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
@ -1238,6 +1315,55 @@ namespace Vanara.PInvoke
[PInvokeData("pathcch.h", MSDNShortId = "250c2faa-94bb-42c1-97d4-37f8f59dbde6")]
public static extern HRESULT PathCchRemoveBackslashEx(StringBuilder pszPath, SizeT cchPath, out IntPtr ppszEnd, out SizeT pcchRemaining);
/// <summary>
/// <para>Removes the trailing backslash from the end of a path string.</para>
/// <para>
/// This function differs from PathCchRemoveBackslash in that it can return a pointer to the new end of the string and report the
/// number of unused characters remaining in the buffer.
/// </para>
/// <para>This function differs from PathRemoveBackslash in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
/// <para>
/// <c>Note</c> This function, or PathCchRemoveBackslash, should be used in place of PathRemoveBackslash to prevent the possibility
/// of a buffer overrun.
/// </para>
/// </summary>
/// <param name="pszPath">
/// <para>
/// A pointer to the path string. When this function returns successfully, the string contains the path with any trailing backslash
/// removed. If no trailing backslash was found, the string is unchanged.
/// </para>
/// </param>
/// <param name="cchPath">
/// <para>The size of the buffer pointed to by pszPath, in characters.</para>
/// </param>
/// <param name="ppszEnd">
/// <para>
/// A value that, when this function returns successfully, receives the address of a pointer to end of the new string. If the string
/// is a root path such as "C:", the pointer points to the backslash; otherwise the pointer points to the string's terminating null character.
/// </para>
/// </param>
/// <param name="pcchRemaining">
/// <para>
/// A pointer to a value that, when this function returns successfully, receives the number of unused characters in the destination
/// buffer, including the terminating null character. If the string is a root path such as "C:", this count includes the backslash in
/// that string.
/// </para>
/// </param>
/// <returns>
/// <para>
/// This function returns S_OK if the function was successful, S_FALSE if the string was a root path or if no backslash was found, or
/// an error code otherwise.
/// </para>
/// </returns>
/// <remarks>
/// <para>This function will not remove the backslash from a root path string, such as "C:".</para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathcchremovebackslashex HRESULT PathCchRemoveBackslashEx(
// PWSTR pszPath, SizeT cchPath, PWSTR *ppszEnd, SizeT *pcchRemaining );
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "250c2faa-94bb-42c1-97d4-37f8f59dbde6")]
public static extern HRESULT PathCchRemoveBackslashEx(IntPtr pszPath, SizeT cchPath, out IntPtr ppszEnd, out SizeT pcchRemaining);
/// <summary>
/// <para>Removes the file name extension from a path, if one is present.</para>
/// <para>This function differs from PathRemoveExtension in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
@ -1375,6 +1501,32 @@ namespace Vanara.PInvoke
[PInvokeData("pathcch.h", MSDNShortId = "187bc49e-c5ae-42b8-acbd-a765f871d73b")]
public static extern HRESULT PathCchSkipRoot(string pszPath, out IntPtr ppszRootEnd);
/// <summary>
/// <para>
/// Retrieves a pointer to the first character in a path following the drive letter or Universal Naming Convention (UNC) server/share
/// path elements.
/// </para>
/// <para>This function differs from PathSkipRoot in that it accepts paths with "\", "\?" and "\?\UNC" prefixes.</para>
/// </summary>
/// <param name="pszPath">
/// <para>A pointer to the path string.</para>
/// </param>
/// <param name="ppszRootEnd">
/// <para>
/// The address of a pointer that, when this function returns successfully, points to the first character in a path following the
/// drive letter or UNC server/share path elements. If the path consists of only a root, this value will point to the string's
/// terminating null character.
/// </para>
/// </param>
/// <returns>
/// <para>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</para>
/// </returns>
// https://docs.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathcchskiproot HRESULT PathCchSkipRoot( PCWSTR pszPath,
// PCWSTR *ppszRootEnd );
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "187bc49e-c5ae-42b8-acbd-a765f871d73b")]
public static extern HRESULT PathCchSkipRoot(IntPtr pszPath, out IntPtr ppszRootEnd);
/// <summary>
/// <para>Removes the "\?" prefix, if present, from a file path.</para>
/// </summary>
@ -1465,6 +1617,29 @@ namespace Vanara.PInvoke
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "3b2a4158-63ec-49eb-a031-7493d02f2caa")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PathIsUNCEx(string pszPath, ref StringBuilder ppszServer);
public static extern bool PathIsUNCEx(string pszPath, out IntPtr ppszServer);
/// <summary>
/// <para>Determines if a path string is a valid Universal Naming Convention (UNC) path, as opposed to a path based on a drive letter.</para>
/// <para>This function differs from PathIsUNC in that it also allows you to extract the name of the server from the path.</para>
/// </summary>
/// <param name="pszPath">
/// <para>A pointer to the path string.</para>
/// </param>
/// <param name="ppszServer">
/// <para>
/// A pointer to a string that, when this function returns successfully, receives the server portion of the UNC path. This value can
/// be <c>NULL</c> if you don't need this information.
/// </para>
/// </param>
/// <returns>
/// <para>Returns <c>TRUE</c> if the string is a valid UNC path; otherwise, <c>FALSE</c>.</para>
/// </returns>
// https://docs.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathisuncex BOOL PathIsUNCEx( PCWSTR pszPath, PCWSTR
// *ppszServer );
[DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
[PInvokeData("pathcch.h", MSDNShortId = "3b2a4158-63ec-49eb-a031-7493d02f2caa")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PathIsUNCEx(IntPtr pszPath, out IntPtr ppszServer);
}
}

View File

@ -0,0 +1,253 @@
using NUnit.Framework;
using System;
using System.Runtime.InteropServices;
using System.Text;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.AdvApi32;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.PInvoke.Tests
{
[TestFixture]
public class PathCchTests
{
[Test]
public void PathAllocCanonicalizeTest()
{
Assert.That(PathAllocCanonicalize(@"C:\name_1\.\name_2\..\name_3", PATHCCH_OPTIONS.PATHCCH_NONE, out var ret), Is.EqualTo((HRESULT)0));
Assert.That(ret, Is.EqualTo(@"C:\name_1\name_3"));
}
[Test]
public void LocalStringMarshalerTest()
{
var originalByteCount = GC.GetTotalMemory(true);
for (int i = 0; i < 5000; i++)
{
PathAllocCanonicalize(@"C:\name_1\.\name_2\..\name_3", PATHCCH_OPTIONS.PATHCCH_NONE, out var ret);
Assert.That(ret, Is.EqualTo(@"C:\name_1\name_3"));
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var finalByteCount = GC.GetTotalMemory(true);
Assert.That(Math.Abs(finalByteCount - originalByteCount), Is.LessThan(2000));
}
[Test]
public void PathAllocCombineTest()
{
Assert.That(PathAllocCombine(@"C:\name_1\.\name_2\..", @"name_3", PATHCCH_OPTIONS.PATHCCH_NONE, out var ret), Is.EqualTo((HRESULT)0));
Assert.That(ret, Is.EqualTo(@"C:\name_1\name_3"));
}
[Test]
public void PathCchAddBackslashTest()
{
var sb = new StringBuilder(@"C:\Temp\", 100);
Assert.That(PathCchAddBackslash(sb, sb.Capacity), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
sb = new StringBuilder(@"C:\Temp", 100);
Assert.That(PathCchAddBackslash(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
}
[Test]
public void PathCchAddBackslashExTest()
{
var sb = new StringBuilder(@"C:\Temp\", 64);
Assert.That(PathCchAddBackslashEx(sb, sb.Capacity, out var end, out var rem), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
Assert.That(end, Is.Not.EqualTo(IntPtr.Zero));
Assert.That(rem, Is.LessThan(60));
sb = new StringBuilder(@"C:\Temp", 64);
Assert.That(PathCchAddBackslashEx(sb, sb.Capacity, out end, out rem), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
Assert.That(rem, Is.LessThan(60));
}
[Test]
public void PathCchAddBackslashExTest2()
{
var sb = new SafeCoTaskMemString(@"C:\Temp\", 64);
Assert.That(PathCchAddBackslashEx((IntPtr)sb, sb.Capacity, out var end, out var rem), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
Assert.That(end, Is.EqualTo(sb.DangerousGetHandle().Offset(sb.Length * 2)));
sb = new SafeCoTaskMemString(@"C:\Temp", 64);
Assert.That(PathCchAddBackslashEx((IntPtr)sb, sb.Size, out end, out rem), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\"));
Assert.That(end, Is.EqualTo(sb.DangerousGetHandle().Offset(sb.Length * 2)));
}
[Test]
public void PathCchAddExtensionTest()
{
var sb = new StringBuilder(@"C:\Temp\Dog", 64);
Assert.That(PathCchAddExtension(sb, sb.Capacity, "txt"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\Dog.txt"));
}
[Test]
public void PathCchAppendTest()
{
var sb = new StringBuilder(@"C:\Temp\Dog", 64);
Assert.That(PathCchAppend(sb, sb.Capacity, "txt"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\Dog\txt"));
}
[Test]
public void PathCchAppendExTest()
{
var sb = new StringBuilder(@"C:\Temp\Dog", 64);
Assert.That(PathCchAppendEx(sb, sb.Capacity, "txt", PATHCCH_OPTIONS.PATHCCH_ENSURE_TRAILING_SLASH), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\Dog\txt\"));
}
[Test]
public void PathCchCanonicalizeTest()
{
var sb = new StringBuilder(64);
Assert.That(PathCchCanonicalize(sb, sb.Capacity, @"C:\name_1\.\name_2\..\name_3"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\name_1\name_3"));
}
[Test]
public void PathCchCanonicalizeExTest()
{
var sb = new StringBuilder(64);
Assert.That(PathCchCanonicalizeEx(sb, sb.Capacity, @"C:\name_1\.\name_2\..\name_3", PATHCCH_OPTIONS.PATHCCH_ENSURE_TRAILING_SLASH), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\name_1\name_3\"));
}
[Test]
public void PathCchCombineTest()
{
var sb = new StringBuilder(64);
Assert.That(PathCchCombine(sb, sb.Capacity, @"C:\name_1\.\name_2\..", @"name_3"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\name_1\name_3"));
}
[Test]
public void PathCchCombineExTest()
{
var sb = new StringBuilder(64);
Assert.That(PathCchCombineEx(sb, sb.Capacity, @"C:\name_1\.\name_2\..", @"name_3", PATHCCH_OPTIONS.PATHCCH_ENSURE_TRAILING_SLASH), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\name_1\name_3\"));
}
[Test]
public void PathCchFindExtensionTest()
{
var sb = new SafeCoTaskMemString(@"C:\Temp\dog.txt", 64);
Assert.That(PathCchFindExtension((IntPtr)sb, sb.Capacity, out var ptr), Is.EqualTo((HRESULT)0));
Assert.That(ptr, Is.EqualTo(sb.DangerousGetHandle().Offset(22)));
}
[Test]
public void PathCchIsRootTest()
{
Assert.That(PathCchIsRoot(@"C:\"), Is.True);
}
[Test]
public void PathCchRemoveBackslashTest()
{
var sb = new StringBuilder(@"C:\Temp\", 64);
Assert.That(PathCchRemoveBackslash(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp"));
sb = new StringBuilder(@"C:\Temp", 64);
Assert.That(PathCchRemoveBackslash(sb, sb.Capacity), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp"));
}
[Test]
public void PathCchRemoveBackslashExTest()
{
var sb = new SafeCoTaskMemString(@"C:\Temp\", 64);
Assert.That(PathCchRemoveBackslashEx((IntPtr)sb, sb.Capacity, out var end, out var rem), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp"));
Assert.That(end, Is.EqualTo(sb.DangerousGetHandle().Offset(14)));
sb = new SafeCoTaskMemString(@"C:\Temp", 64);
Assert.That(PathCchRemoveBackslashEx((IntPtr)sb, sb.Capacity, out end, out rem), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp"));
Assert.That(end, Is.EqualTo(sb.DangerousGetHandle().Offset(14)));
}
[Test]
public void PathCchRemoveExtensionTest()
{
var sb = new StringBuilder(@"C:\Temp\dog.txt", 64);
Assert.That(PathCchRemoveExtension(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\dog"));
sb = new StringBuilder(@"C:\Temp\dog", 64);
Assert.That(PathCchRemoveExtension(sb, sb.Capacity), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\dog"));
}
[Test]
public void PathCchRemoveFileSpecTest()
{
var sb = new StringBuilder(@"C:\foo\bar.txt", 64);
Assert.That(PathCchRemoveFileSpec(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\foo"));
}
[Test]
public void PathCchRenameExtensionTest()
{
var sb = new StringBuilder(@"C:\Temp\dog.txt", 64);
Assert.That(PathCchRenameExtension(sb, sb.Capacity, "doc"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\dog.doc"));
sb = new StringBuilder(@"C:\Temp\dog", 64);
Assert.That(PathCchRenameExtension(sb, sb.Capacity, "txt"), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\Temp\dog.txt"));
}
[Test]
public void PathCchSkipRootTest()
{
var sb = new SafeCoTaskMemString(@"C:\Temp\", 64);
Assert.That(PathCchSkipRoot((IntPtr)sb, out var end), Is.EqualTo((HRESULT)0));
Assert.That(end, Is.EqualTo(sb.DangerousGetHandle().Offset(6)));
}
[Test]
public void PathCchStripPrefixTest()
{
var sb = new StringBuilder(@"\\?\C:\foo\bar.txt", 64);
Assert.That(PathCchStripPrefix(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\foo\bar.txt"));
sb = new StringBuilder(@"C:\foo\bar.txt", 64);
Assert.That(PathCchStripPrefix(sb, sb.Capacity), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\foo\bar.txt"));
}
[Test]
public void PathCchStripToRootTest()
{
var sb = new StringBuilder(@"C:\foo\bar.txt", 64);
Assert.That(PathCchStripToRoot(sb, sb.Capacity), Is.EqualTo((HRESULT)0));
Assert.That(sb.ToString(), Is.EqualTo(@"C:\"));
sb = new StringBuilder(@"\\path1\path2", 64);
Assert.That(PathCchStripToRoot(sb, sb.Capacity), Is.EqualTo((HRESULT)HRESULT.S_FALSE));
Assert.That(sb.ToString(), Is.EqualTo(@"\\path1\path2"));
}
[Test]
public void PathIsUNCExTest()
{
Assert.That(PathIsUNCEx(@"\\path1\path2\path3", out var svr), Is.True);
Assert.That(Marshal.PtrToStringUni(svr), Is.EqualTo(@"path1\path2\path3"));
}
}
}