Search code examples
c#windowswinapipinvoke

Kernel32 CopyFile does not find a file which exists c#


I invoked Kernel32's copy file method like that:

[DllImport("kernel32.dll",
           CharSet = CharSet.Unicode,
           CallingConvention = CallingConvention.StdCall,
           SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CopyFile(
                           [MarshalAs(UnmanagedType.LPStr)] string lpExistingFileName,
                           [MarshalAs(UnmanagedType.LPStr)] string lpNewFileName,
                           [MarshalAs(UnmanagedType.Bool)] bool bFailIfExists);
[DllImport("kernel32.dll")]
    public static extern uint GetLastError();

However when I call it it return 2 from the GetLastError() which means file not found. The path certainly exists.

string newfile = Environment.CurrentDirectory + "\\temp" + Path.GetExtension(file);
uint i;
if (!CopyFile(file, newfile, true)) i = GetLastError();

I'm trying to bypass the LongPath exception with this solution. But it doesn't seem to work even with normal files. Any help would be appreciated.

Here's the complete code of Form1:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Shell32;
using System.Xml;
using System.Diagnostics;
using word = Microsoft.Office.Interop.Word;
using System.Runtime.InteropServices;

namespace DocumentCrawler
{
    public partial class Form1 : Form
    {
        [DllImport("kernel32.dll",
           CharSet = CharSet.Unicode,
           CallingConvention = CallingConvention.StdCall,
           SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CopyFile(
                           [MarshalAs(UnmanagedType.LPStr)] string lpExistingFileName,
                           [MarshalAs(UnmanagedType.LPStr)] string lpNewFileName,
                           [MarshalAs(UnmanagedType.Bool)] bool bFailIfExists);

        [DllImport("kernel32.dll")]
        public static extern uint GetLastError();

        public Form1()
        {
            InitializeComponent();
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            progressBar.Style = ProgressBarStyle.Marquee;
            lbProgress.Text = "Finding Word Documents";
            btnSearch.Enabled = false;
            lvResults.Clear();
            SearchDirectory(tbDirectory.Text, tbField.Text);
            btnSearch.Enabled = true;
        }

        void SearchDirectory(string path, string searchPattern)
        {
            List<string> docs = new List<string>();
            foreach (string d in Directory.GetDirectories(path))
            {
                SearchDirectory(path + "\\" + d.Remove(0, d.LastIndexOf('\\') + 1), searchPattern);
            }

            foreach (string f in Directory.GetFiles(path))
            {
                if (Path.GetExtension(f) == ".docx" || Path.GetExtension(f) == ".doc")
                {
                    docs.Add(f);
                }
            }

            progressBar.Value = 0;
            lbProgress.Text = "Processing Word Documents 0%";
            progressBar.Maximum = docs.Count;
            progressBar.Style = ProgressBarStyle.Blocks;

            foreach (string f in docs)
            {
                string txt = TextFromDocument(f);
                if (txt.Contains(searchPattern))
                {
                    lvResults.Items.Add(f);
                }
                progressBar.Value++;
                lbProgress.Text = "Processing Word Documents " + ((int)((float)progressBar.Value / (float)progressBar.Maximum * 100)) + "%";
            }
        }

        string TextFromDocument(string file)
        {
            string newfile = Environment.CurrentDirectory + "\\temp" + Path.GetExtension(file);
            uint i;
            if (!CopyFile(file, newfile, true)) i = GetLastError();
            object nullobj = System.Reflection.Missing.Value;
            word.Application wordApp = new word.Application();
            word.Document doc = wordApp.Documents.Open(newfile, false);

            doc.ActiveWindow.Selection.WholeStory();
            doc.ActiveWindow.Selection.Copy();
            string text = doc.Content.Text;

            doc.Close(ref nullobj, ref nullobj, ref nullobj);
            wordApp.Quit(ref nullobj, ref nullobj, ref nullobj);

            File.Delete(newfile);

            return text;
        }

        private void lvResults_DoubleClick(object sender, EventArgs e)
        {
            Process.Start(lvResults.SelectedItems[0].Text);
            lvResults.SelectedItems[0].ForeColor = Color.Purple;
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fd = new FolderBrowserDialog();
            if (fd.ShowDialog() == DialogResult.OK)
            {
                tbDirectory.Text = fd.SelectedPath;
                btnSearch.Enabled = true;
            }
        }
    }
}

Thanks in advance!


Solution

  • [DllImport("kernel32.dll",
       CharSet = CharSet.Unicode,
       CallingConvention = CallingConvention.StdCall,
       SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CopyFile(
                       [MarshalAs(UnmanagedType.LPStr)] string lpExistingFileName,
                       [MarshalAs(UnmanagedType.LPStr)] string lpNewFileName,
                       [MarshalAs(UnmanagedType.Bool)] bool bFailIfExists);
    

    In the DllImport declaration you select the CharSet.Unicode character set. This will mean that the p/invoke function will be bound to CopyFileW.

    But then you subsequently instruct the marshaller to marshal the parameters as LPStr, ANSI strings. This is the reason why the function always fails.

    A correct p/invoke would be:

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CopyFile(string lpExistingFileName, string lpNewFileName,
        bool bFailIfExists);
    

    You absolutely should not p/invoke GetLastError. Instead use Marshal.GetLastWin32Error for the reasons described in the documentation.