从CreateFile生成的句柄构造FileStream会给出一个空流

时间:2016-01-09 04:17:34

标签: c# file winapi stream

我尝试编写一个简单的程序,该程序将采用由两列组成的CSV,并从第一列给出一个关键字,从第二列返回相应的值。问题是我需要CSV在备用数据流中以使程序尽可能便携(我想这样做,以便当用户在可执行文件上删除CSV文件时,CSV被覆盖)。这就是为什么我尝试使用WinAPI的CreateFile函数 - .NET不支持备用数据流。不幸的是,我惨遭失败。

在当前状态下,程序应该读取名为test.csv的文件。我想在其上执行CreateFile,将intPtr句柄转换为SafeFileHandle,然后将SafeFileHandle传递给FileStream构造函数。我能够实现的最好的是一个空流。在我看来,程序实际上是正确的处理方式。当我尝试" CREATE_ALWAYS"或" CREATE_NEW"而不是" OPEN_ALWAYS",我得到了一个"无效的句柄"错误,无论我如何处理其余参数。使用" OPEN_ALWAYS",当我检查" stream.Name"的值时,我得到"未知"。

以下是代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace Searcher
{
    public partial class Searcher : Form
    {
        public static List<string> keywords;
        public static List<string> values;

        public Searcher()
        {
            InitializeComponent();

            //byte[] path = Encoding.UTF8.GetBytes("C:\\Users\\as\\Documents\\test.csv");
            SafeFileHandle safeADSHandle = NativeMethods.CreateFileW("test.csv",
            NativeConstants.GENERIC_READ,
            NativeConstants.FILE_SHARE_READ,
            IntPtr.Zero,
            NativeConstants.OPEN_ALWAYS,
            0,
            IntPtr.Zero);
            //var safeADSHandle = new SafeFileHandle(handle, true);
            if (safeADSHandle.IsInvalid)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            var stream = new FileStream(safeADSHandle, FileAccess.Read);
            MessageBox.Show(stream.Name);
            var reader = new StreamReader(stream);
            Searcher.keywords = new List<string>();
            Searcher.values = new List<string>();
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
                var values = line.Split(',');
                Searcher.keywords.Add(values[0]);
                Searcher.values.Add(values[1]);
            }
            cbKeyword.DataSource = Searcher.keywords;
            cbKeyword.AutoCompleteSource = AutoCompleteSource.ListItems;
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            tbResult.Text = Searcher.values[cbKeyword.SelectedIndex];
        }
    }
}

public partial class NativeMethods
{
        [DllImportAttribute("kernel32.dll", SetLastError = true, EntryPoint = "CreateFile")]
        public static extern SafeFileHandle CreateFileW(
            [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            [InAttribute()] System.IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            [InAttribute()] System.IntPtr hTemplateFile
        );
}


public partial class NativeConstants
{

        /// GENERIC_WRITE -> (0x40000000L)
        public const int GENERIC_WRITE = 1073741824;

        public const uint GENERIC_READ = 2147483648;

        /// FILE_SHARE_DELETE -> 0x00000004
        public const int FILE_SHARE_DELETE = 4;

        /// FILE_SHARE_WRITE -> 0x00000002
        public const int FILE_SHARE_WRITE = 2;

        /// FILE_SHARE_READ -> 0x00000001
        public const int FILE_SHARE_READ = 1;

        /// OPEN_ALWAYS -> 4
        public const int OPEN_ALWAYS = 4;
        public const int CREATE_NEW = 1;
}

编辑我稍微更改了上面的代码,以反映评论后的更改。现在我没有从IntPtr转换为SafeFileHandle。或许值得一提的是,在此更改之前,我尝试读取handle.ToString()的值以查看它是否正在发生变化,它是 - 它是一个随机数。

1 个答案:

答案 0 :(得分:4)

这不起作用的原因是因为您指定了没有字符集的入口点。如果您未在DllImport中指定字符集,则默认值为CharSet.Ansi,这意味着平台调用将按以下方式搜索该函数:

  • 首先是一个未编译的导出函数,即您指定的入口点的名称CreateFilekernel32.dll中不存在)
  • 接下来,如果找不到未编码的名称,它将根据字符集搜索损坏的名称,因此它将搜索CreateFileA

因此它将找到CreateFileA导出函数,该函数假定传入其中的任何字符串都是1字节字符ANSI格式。但是,您将字符串编组为宽字符串。请注意,函数的宽字符版本称为CreateFileW(ANSI和广义字符版本的函数在Windows API中很常见)。

要解决此问题,您只需确保参数的编组与您导入的函数所期望的匹配。因此,您可以删除EntryPoint字段,在这种情况下,它将使用C#方法名称来查找导出的函数(因此它将找到CreateFileW)。

然而为了使它更清楚,我会编写你的平台调用代码,如下所示:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
    [MarshalAs(UnmanagedType.LPTStr)] string filename,
    [MarshalAs(UnmanagedType.U4)] FileAccess access,
    [MarshalAs(UnmanagedType.U4)] FileShare share,
    IntPtr securityAttributes,
    [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
    [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
    IntPtr templateFile);

我从pinvoke.net网站上获取了这个内容,这应该是您编写pinvoke代码的首选,而不是试图自己抨击它(特别是如果您不熟悉Windows API或编组) :)

没有错误的原因可能是因为CreateFile正在创建文件,因为它无法找到它。

文件名将显示为&#34; [未知]&#34;虽然。我怀疑这是因为从句柄中获取文件名的代码并不重要。