我正试图编写一个简单的程序,该程序将采用由两列组成的CSV,并给定第一列中的关键字,返回第二列中的相应值。问题是,我需要CSV位于备用数据流中,以使程序尽可能具有可移植性(我想让它在用户将CSV文件放在可执行文件上时,CSV会被覆盖)。这就是为什么我尝试使用WinAPI的CreateFile函数——.NET不支持备用数据流。不幸的是,我失败得很惨。
在当前状态下,该程序应该读取一个名为test.csv的文件。我想对它执行CreateFile,将intPtr句柄转换为SafeFileHandle,然后将SafeFileHandler传递给FileStream构造函数。不过,我所能达到的最好成绩是一股清流。在我看来,这个程序实际上并没有得到正确的处理。当我尝试"CREATE_ALLWAYS"或"CREATE_NEW"而不是"OPEN_ALWAYS"时,无论我如何处理其余参数,都会出现"无效句柄"错误。使用"OPEN_ALWAYS",当我检查"stream.Name"的值时,我得到的是"Unknown"。
这是代码:
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(),看看它是否在改变,它是——它是一个随机数。
这不起作用的原因是您指定了一个没有字符集的入口点。当您没有在DllImport
中指定字符集时,默认值为CharSet.Ansi
,这意味着平台调用将按如下方式搜索函数:
- 首先是未处理的导出函数,即您指定的入口点的名称
CreateFile
(在kernel32.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
正在创建文件,因为它找不到它
不过,文件名将显示为"[未知]"。我怀疑这是因为从句柄获取文件名的代码非常简单。