Dokan[转]

原文地址:http://www.cnblogs.com/xxonehjh/p/3634889.html 

因工作需要,最近与同事合作使用Dokan开发了一个虚拟磁盘的简单程序,初步实现了远程目录映射到本地虚拟磁盘的功能。

    远程服务端是用Python写的,主要是将远程主机上的目录文件传给客戶端,在这里就不细说了。

    Dokan客户端则由Delphi开发,其参考代码来自网络上的Delphi例子,比如Mirror Driver

    本篇文章主要是Dokan开发过程的一些总结,所以不会对Dokan本身做介绍,与Dokan有关的资料及代码,请到google里搜索,或到Dokan的官方网站去下载(Dokan官网),源码是C语言的,应用例子有Ruby、.Net及C的。如果想要Delphi的例子代码,只能自己去找了。

    刚开始时由于不清楚如何用Dokan来实现一个文件系统,所以需要做一些试验,结果一不小心就蓝屏了!悲剧啊,用XP系统已经好多年没遇到蓝屏了。几次蓝屏之后,终于受不了了,于是在VMWare里装了个虚拟机的XP,这下不怕蓝屏了,哈哈。强烈建议装个虚拟机来玩Dokan,否则刚开始的时候你会蓝屏N次!

    为简单起见,我做的Dokan虚拟磁盘采用将远程目录缓存到本地目录的方法来实现,这样就不用自己维护一堆目录、文件的信息,只需要关注如何更新同步目录与文件就可以了。由于Dokan是多线程的,因此实现时需要做到线程安全;查看Dokan使用的结构类型,发现只有两个成员可以使用,即DOKAN_OPTIONS里的GlobalContext和DOKAN_FILE_INFO里的Context,其中GlobalContext只能用来存储全局的信息,比如存放线程实例的指针,这样一来,实际上就剩下 DOKAN_FILE_INFO里的Context 一个成员可以用来存储与文件有关的信息了,一般用它来存储文件指针。我这次实现没有自己定义类来管理目录与文件,而是直接利用缓存目录,因此只需要处理文件指针和是否需要更新文件两个信息就可以了,而 DOKAN_FILE_INFO里的Context是Int64的,在Win32里可以用32位存文件指针,另32位用来存储文件更新信息。 

//以下来自于Dokan.pas里的定义

_DOKAN_OPTIONS = packed record
    DriveLetter: WCHAR; // Drive letter to be mounted
    ThreadCount: Word; // Number of threads to be used
    DebugMode: Boolean;
    UseStdErr: Boolean;
    UseAltStream: Boolean;
    UseKeepAlive: Boolean;
    GlobalContext: Int64; // User-mode filesystem can use this variable
end;
PDOKAN_OPTIONS = ^_DOKAN_OPTIONS;
DOKAN_OPTIONS = _DOKAN_OPTIONS;

TDokanOptions = _DOKAN_OPTIONS;
PDokanOptions = PDOKAN_OPTIONS;

_DOKAN_FILE_INFO = packed record
    Context: Int64; // User-mode filesystem can use this variable
    DokanContext: Int64; // Reserved. Don't touch this!
    DokanOptions: PDOKAN_OPTIONS;
    ProcessId: ULONG; // Process id for the thread that originally requested the I/O operation
    IsDirectory: Boolean; // Indicates a directory file
    DeleteOnClose: Boolean; // Delete when Cleanup is called
    PagingIo: Boolean; // Read or write is paging IO
    SynchronousIo: Boolean; // Read or write is synchronous IO
    Nocache: Boolean; // No caching
    WriteToEndOfFile: Boolean; // If true, write to the current end of file instead of Offset parameter
end;
PDOKAN_FILE_INFO = ^_DOKAN_FILE_INFO;
DOKAN_FILE_INFO = _DOKAN_FILE_INFO;

TDokanFileInfo = _DOKAN_FILE_INFO;
  PDokanFileInfo = PDOKAN_FILE_INFO;

    研究了几天,发现只需要实现少数几个回调函数就可以了:

    1.FindFiles: 在这个回调函数里可以实现从远程目录同步其下的所有目录及文件。当然也可以在OpenDirectory回调函数里做,但实际使用时我发现OpenDirectory调用太频繁,而FindFiles调用次数要少一些。

    2.CreateDirectory: 在这个回调函数里可以实现同步创建远程目录。

    3.DeleteDirectory: 实现同步删除远程目录。 

    4.CreateFile: 这个回调函数调用极其频繁,每次操作目录文件(包括打开文件)时首先都会调到它,我在这里实现了从远程目录同步更新本地文件的内容。需要注意的是,在虚拟磁盘里新建文件时,为了能在Cleanup里正确同步到远程目录,必须记下来。我使用了以下代码来实现:

if not DokanFileInfo.IsDirectory and (CreationDisposition in [CREATE_NEW, OPEN_ALWAYS, CREATE_ALWAYS]) then begin
  MySetFileDate(DokanFileInfo, DateTimeToFileDate(Now)); //Cleanup里会判断FileDate来决定是否保存到远程目录
end;

    5.WriteFile: 可用于指示文件是否已修改,和Cleanup配合,以便保存文件时能正确提交到远程服务器。需要注意的WriteFile可能会被调用多次,所以它并不适合提交修改,只能记录修改标志。

    6.Cleanup: 同步删除远程目录中的文件及保存本地修改的文件到远程目录。实现时我发现,在Cleanup中判断DokanFileInfo.DeleteOnClose及DokanFileInfo.IsDirectory来删除目录的代码根本就不会走到(所以我在DeleteDirectory里实现删除目录的同步),而删除文件则没问题。

    这里有一点需要注意:因为执行Cleanup之前,可能会多次调用CreateFile,比如记事本保存文档时就会执行两次CreateFile之后再调用Cleanup,所以我在Cleanup的最后执行MySetFileDate(DokanFileInfo, 0)来清空标志,而没有在CreateFile里清空标志。

    7.MoveFile: 这个回调函数仅在移动虚拟磁盘里的文件到另一个虚拟磁盘目录中去时才触发,故实现在远程目录中同步移动文件后,就可以正常实现目录文件的移动了。由于操作多个目录文件时,Windows会每个目录文件分别调用相关操作,因此实现这个回调函数后,自然就实现了多个目录文件的移动。如果是从其他盘移动目录文件到虚拟磁盘或从虚拟磁盘移动目录文件到其他盘,都不会触发MoveFile这个回调函数;而目录文件改名,则会触发MoveFile这个回调函数。

    实现时还有一个调试信息如何显示的问题,对控制台程序,可以直接写到控制台;而对带窗口的程序,可以写日志文件,也可以发Windows消息。我采用了SendMessage来处理调试信息,具体实现请参看下面的代码。

    最终的实现是由一个线程来实现Dokan虚拟磁盘的,目录与文件的同步函数则放到一个单独的单元文件里,连接远程服务端则采用IndyTCPClient实现,传输采用了JSON,以便于和服务端的Python脚本通讯。

原文地址:https://www.cnblogs.com/langren1992/p/5373023.html