Skip to content

PCSTR/PCWSTR should not be used for double-null terminated strings #228

@JellosPorsche

Description

@JellosPorsche

Actual behavior

I have wrapped the SHFileOperation command because I don't want to write a file conflict comparer.
One of the quirks of that command is that the paths are null terminated and the last file is double null terminated.
However, when I try to convert a string with null terminators to a PCWSTR it drops off everything after the first file and will cause a crash. Is there a way to create a PCWSTR with null terminators?

Expected behavior

I expect the program not to crash.

Repro steps

  1. NativeMethods.txt content:
SHFileOperation
  1. NativeMethods.json content (if present):
  1. Any of your own code that should be shared?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

using Microsoft.Windows.Sdk;

namespace FileOperations.Helpers
{
    public static class WindowsShellFileOperation
    {
        private enum FO_Func : uint
        {
            FO_MOVE = 0x0001,
            FO_COPY = 0x0002,
            FO_DELETE = 0x0003,
            FO_RENAME = 0x0004
        }

        [Flags]
        private enum FILEOP_FLAGS : ushort
        {
            FOF_MULTIDESTFILES = 0x1,
            FOF_CONFIRMMOUSE = 0x2,
            FOF_SILENT = 0x4,
            FOF_RENAMEONCOLLISION = 0x8,
            FOF_NOCONFIRMATION = 0x10,
            FOF_WANTMAPPINGHANDLE = 0x20,
            FOF_ALLOWUNDO = 0x40,
            FOF_FILESONLY = 0x80,
            FOF_SIMPLEPROGRESS = 0x100,
            FOF_NOCONFIRMMKDIR = 0x200,
            FOF_NOERRORUI = 0x400,
            FOF_NOCOPYSECURITYATTRIBS = 0x800,
            FOF_NORECURSION = 0x1000,
            FOF_NO_CONNECTED_ELEMENTS = 0x2000,
            FOF_WANTNUKEWARNING = 0x4000,
            FOF_NORECURSEREPARSE = 0x8000
        }

        public static bool TransferFiles(List<string> src, List<string> dest, bool isMove = false)
        {
            return TransferFiles(MergeFilenames(src), MergeFilenames(dest), isMove);
        }

        private static unsafe bool TransferFiles(string src, string dest, bool isMove)
        {
            bool success = false;

            SHFILEOPSTRUCTW fileOp = new SHFILEOPSTRUCTW();
            fileOp.hwnd = new HWND(0);
            fileOp.hNameMappings = null;
            fileOp.fFlags = (ushort)(FILEOP_FLAGS.FOF_NORECURSION |
                              FILEOP_FLAGS.FOF_NOCONFIRMMKDIR |
                              FILEOP_FLAGS.FOF_MULTIDESTFILES);
            fileOp.fAnyOperationsAborted = false;

            fixed (char* cSrc = src)
            {
                fileOp.pFrom = new PCWSTR(cSrc);
            }

            fixed (char* cDest = dest)
            {
                fileOp.pTo = new PCWSTR(cDest);
            }

            if (isMove)
            {
                fileOp.wFunc = (uint)FO_Func.FO_MOVE;
                fixed (char* cTitle = "Moving files")
                {
                    fileOp.lpszProgressTitle = new PCWSTR(cTitle);
                }
            }
            else
            {
                fileOp.wFunc = (uint)FO_Func.FO_COPY;
                fixed (char* cTitle = "Copying files")
                {
                    fileOp.lpszProgressTitle = new PCWSTR(cTitle);
                }
            }

            // Do file operation and check success
            int result = PInvoke.SHFileOperation(ref fileOp);
            if (result == 0)
            {
                success = !fileOp.fAnyOperationsAborted;
            }

            return success;
        }

        private static string MergeFilenames(List<string> filenames)
        {
            StringBuilder result = new StringBuilder();
            foreach (string file in filenames)
            {
                // Separate files with null termination
                result.Append(file);
                result.Append("\0");
            }

            // Double null termination
            result.Append("\0");     

            return result.ToString();
        }
    }
}

Context

  • CsWin32 version: [0.1.422-beta]
  • Win32Metadata version (if explicitly set by project):
  • Target Framework: [.NET 5.0]
  • LangVersion (if explicitly set by project): [e.g. 9]

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions