[C#] 커널 드라이브 생성(Dokan)하는 방법(Cloud 프로젝트)


Development note/C#  2021. 1. 25. 15:00

안녕하세요. 명월입니다.


이 글은 C#으로 커널 드라이브 생성(Dokan)하는 방법(Cloud 프로젝트)에 대한 글입니다.


Dokan이라는 시스템은 우리가 따로 커널 드라이브를 설치하지 않고 프로그램으로 드라이브를 사용할 수 있게 만드는 라이브러리입니다.

링크 - https://github.com/dokan-dev/dokany


지금은 Naver의 클라우드 디스크인 NDrive가 사용하고 있는 라이브러리입니다. 이 Dokan은 예전에 저도 개인 NAT서버를 만들어서 Dokan으로 공유 프로그램을 만들어서 사용한 적이 있습니다.

지금은 Google drive부터 무료 Cloud 드라이브가 워낙 많고, 윈도우 Explorer에서 이제는 특별한 툴이 없이 ftp나 smb(samba)를 접속할 수 있기 때문에 구디 힘들게 Dokan 라이브러리를 만들어서 파일을 공유하게 할 건 없는 것 같습니다.

그러나 지금도 단순히 파일 복사나 이동, 삭제가 아닌 드라이브로 통한 특별한 이벤트를 만들고 싶으면 사용하기에 매우 좋을 것 같습니다. 특별한 이벤트라는 것은 excel파일을 파일 시스템처럼 업로드를 하면 그 엑셀을 읽어서 데이터 베이스에 저장하는 액션등을 말합니다.


Dokan은 사실 C/C++로 만들어진 라이브러리입니다만, 누가 C#과 Java, Python에서 사용할 수 있게 Wrapper를 만들어 놨네요..

링크 - https://github.com/dokan-dev

여기서는 C#을 이용해서 만들어 보겠습니다.


먼저 Dokan을 사용하기 위해서는 드라이버를 설치해야 합니다.

링크 - https://github.com/dokan-dev/dokany/releases

다운을 받고 install를 하겠습니다.

설치가 되었습니다.


이제 프로그램을 작성하겠습니다.

프로그램을 작성하기 전에 nuget에서 DokanNet 라이브러리를 연결합니다.

using System;
using System.IO;
using DokanNet;
using System.Collections.Generic;
using System.Security.AccessControl;
using System.Linq;

namespace Example
{
  // 도칸(Dokan) 설정 클래스
  public class DokanOperations : IDokanOperations
  {
    // Dokan 드라이브 설정 디렉토리
    private readonly DirectoryInfo path;
    // 액세스 권한
    private const DokanNet.FileAccess DataAccess = DokanNet.FileAccess.ReadData |
                             DokanNet.FileAccess.WriteData |
                             DokanNet.FileAccess.AppendData |
                             DokanNet.FileAccess.Execute |
                             DokanNet.FileAccess.GenericExecute |
                             DokanNet.FileAccess.GenericWrite |
                             DokanNet.FileAccess.GenericRead;
    // 파일 생성 권한
    private const DokanNet.FileAccess DataWriteAccess = DokanNet.FileAccess.WriteData |
                              DokanNet.FileAccess.AppendData |
                              DokanNet.FileAccess.Delete |
                              DokanNet.FileAccess.GenericWrite;
    // 생성자
    public DokanOperations(string path)
    {
      // 물리 디렉토리 설정
      this.path = new DirectoryInfo(path);
    }
    // 물리 디렉토리 경로 취득
    protected string GetPath(string fileName)
    {
      // 물리 디렉토리 + 파일 이름
      return this.path.FullName + fileName;
    }
    // 데이터 권한 체크
    protected bool IsAccess(DokanNet.FileAccess access, DokanNet.FileAccess check)
    {
      // 비트 연산자
      return (access & check) == check;
    }
    // 데이터 읽기 권한
    protected bool IsDataAccess(DokanNet.FileAccess access)
    {
      // 비트 연산자
      return (access & DataAccess) != 0;
    }
    // 데이터 작성 권한
    protected bool IsDataWriteAccess(DokanNet.FileAccess access)
    {
      // 비트 연산자
      return (access & DataWriteAccess) != 0;
    }
    // Directory Flag 확인
    protected bool IsHasFlagDirectory(string filePath)
    {
      try
      {
        // 디렉토리가 아닌 파일이라면
        if (File.GetAttributes(filePath).HasFlag(FileAttributes.Directory))
        {
          // 디렉토리가 아니다
          return false;
        }
      }
      catch
      {

      }
      // 디렉토리!
      return true;
    }
    // CloseFile이 호출되기 전에 정리 요청
    public void Cleanup(string fileName, IDokanFileInfo info)
    {
      // DeleteOnClose가 true이면 file을 삭제해야 한다.
      if (info.DeleteOnClose)
      {
        // 디렉토리의 경우
        if (info.IsDirectory)
        {
          // 디렉토리 삭제
          Directory.Delete(GetPath(fileName));
        }
        else
        {
          // 파일 삭제
          File.Delete(GetPath(fileName));
        }
      }
    }
    // 파일 생성 및 읽기 등의 I/O 리소스 사용이 끝나면 호출
    public void CloseFile(string fileName, IDokanFileInfo info)
    {
      // I/O 리소스 반환
      (info.Context as FileStream)?.Dispose();
      info.Context = null;
    }
    // 파일 및 디렉토리 객체에 대한 요청?
    public NtStatus CreateFile(string fileName, DokanNet.FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, IDokanFileInfo info)
    {
      var result = DokanResult.Success;
      // 정보 요청한 객체의 물리 경로
      var filePath = GetPath(fileName);
      // 요청 옵션이 디렉토리라면
      if (info.IsDirectory)
      {
        try
        {
          // 모드
          switch (mode)
          {
            // 모드가 Open이라면
            case FileMode.Open:
              // 디렉토리가 존재하지 않으면
              if (!Directory.Exists(filePath))
              {
                if (IsHasFlagDirectory(filePath))
                {
                  return DokanResult.PathNotFound;
                }
                else
                {
                  return DokanResult.NotADirectory;
                }
              }
              else
              {
                // 성공!
                return DokanResult.Success;
              }
            // 모드가 생성이라면
            case FileMode.CreateNew:
              // 디렉토리가 존재한다면
              if (Directory.Exists(filePath))
              {
                // 파일이 존재한다.
                return DokanResult.FileExists;
              }
              if (IsHasFlagDirectory(filePath))
              {
                return DokanResult.AlreadyExists;
              }
              // 디렉토리를 생성
              Directory.CreateDirectory(GetPath(fileName));
              // 성공!
              return DokanResult.Success;
          }
        }
        catch (UnauthorizedAccessException)
        {
          // 접근 근지 에러
          return DokanResult.AccessDenied;
        }
      }
      else
      {
        // 경로가 존재
        var pathExists = true;
        // 경로가 디렉토리라면?
        var pathIsDirectory = false;
        // 읽기, 작성 권한
        var readWriteAttributes = !IsDataAccess(access);
        // 읽기 권한
        var readAccess = !IsDataWriteAccess(access);
        // 디렉토리 또는 파일이 존재
        pathExists = (Directory.Exists(filePath) || File.Exists(filePath));
        // 디렉토리라면
        pathIsDirectory = pathExists ? IsHasFlagDirectory(filePath) : false;
        // 모드 체크
        switch (mode)
        {
          // 파일 읽기
          case FileMode.Open:

            // 파일이 존재하면
            if (pathExists)
            {
              // 읽기 쓰기가 존재하면
              if (readWriteAttributes || pathIsDirectory)
              {
                // 싱크 권한이 없다면...
                if (pathIsDirectory && !IsAccess(access, DokanNet.FileAccess.Synchronize))
                {
                  // 접근 거부
                  return DokanResult.AccessDenied;
                }
                // 디렉토리 여부
                info.IsDirectory = pathIsDirectory;
                info.Context = new object();
                // 성공!
                return DokanResult.Success;
              }
            }
            else
            {
              // 파일이 존재하지 않는다.
              return DokanResult.FileNotFound;
            }
            break;
          // 생성한다면
          case FileMode.CreateNew:
            // 파일이 있으면
            if (pathExists)
            {
              // 존재 에러
              return DokanResult.FileExists;
            }
            break;
          // 삭제
          case FileMode.Truncate:
            // 존재하지 않는다면.
            if (!pathExists)
            {
              // 파일이 존재하지 않는다.
              return DokanResult.FileNotFound;
            }
            break;
        }
        try
        {
          // 읽기에서 권한이 없고, 생성에서 파일이 없고, 삭제에서 파일이 있다면
          // I/O를 설정합니다.
          info.Context = new FileStream(filePath, mode, readAccess ? System.IO.FileAccess.Read : System.IO.FileAccess.ReadWrite, share, 4096, options);

          // 파일이 존재하고 파일 옵션이 생성이라면
          if (pathExists && (mode == FileMode.OpenOrCreate || mode == FileMode.Create))
          {
            // 이미 존재 결과
            result = DokanResult.AlreadyExists;
          }
          // 파일 생성시에 모드가 생성이고 파일이 존재하지 않으면.
          if (mode == FileMode.CreateNew || mode == FileMode.Create || (!pathExists && mode == FileMode.OpenOrCreate))
          {
            // 파일 옵션 설정
            FileAttributes new_attributes = attributes;
            new_attributes |= FileAttributes.Archive;
            new_attributes &= ~FileAttributes.Normal;
            File.SetAttributes(filePath, new_attributes);
          }
        }
        catch (UnauthorizedAccessException)
        {
          // 권한 에러가 발생하면
          // 스트림을 해제하고
          if (info.Context is FileStream fileStream)
          {
            fileStream.Dispose();
            info.Context = null;
          }
          // 권한 에러
          return DokanResult.AccessDenied;
        }
        catch (DirectoryNotFoundException)
        {
          // 디렉토리 에러가 발생하면
          // 경로 에러
          return DokanResult.PathNotFound;
        }
        catch (Exception)
        {
          // 무언가 잘못된 에러
          return DokanResult.SharingViolation;
        }
      }
      // 결과 리턴
      return result;
    }
    // 디렉토리가 삭제 가능한지 확인 여부
    public NtStatus DeleteDirectory(string fileName, IDokanFileInfo info)
    {
      // 경로 취득
      var filePath = GetPath(fileName);
      // 디렉토리라면
      if (Directory.Exists(filePath))
      {
        // 삭제 가능
        return DokanResult.Success;
      }
      else if (File.Exists(filePath))
      {
        // 파일이면 에러
        return DokanResult.AccessDenied;
      }
      // 디렉토리가 아니면 에러
      return DokanResult.NotADirectory;
    }
    // 파일 삭제가능한지 여부
    public NtStatus DeleteFile(string fileName, IDokanFileInfo info)
    {
      // 경로 취득
      var filePath = GetPath(fileName);
      // 디렉토리라면
      if (Directory.Exists(filePath))
      {
        // 파일이 아니면 에러
        return DokanResult.AccessDenied;
      }
      // 파일이 존재하지 않으면
      else if (!File.Exists(filePath))
      {
        // 파일을 존재하지 않는 에러
        return DokanResult.FileNotFound;
      }
      // 디렉토리라면
      else if (IsHasFlagDirectory(filePath))
      {
        // 접근 에러
        return DokanResult.AccessDenied;
      }
      // 삭제 가능
      return DokanResult.Success;
    }
    // 파일 검색
    public NtStatus FindFiles(string fileName, out IList<FileInformation> files, IDokanFileInfo info)
    {
      // 패턴 파일 검색에서 모든 표시
      return FindFilesWithPattern(fileName, "*", out files, info);
    }
    // 패턴으로 파일 검색
    public NtStatus FindFilesWithPattern(string fileName, string searchPattern, out IList<FileInformation> files, IDokanFileInfo info)
    {
      // Linq식으로 파일을 검색한다.
      files = new DirectoryInfo(GetPath(fileName))
                    .EnumerateFileSystemInfos()
                    .Where(finfo => DokanHelper.DokanIsNameInExpression(searchPattern, finfo.Name, true))
                    .Select(finfo => new FileInformation
                    {
                      Attributes = finfo.Attributes,
                      CreationTime = finfo.CreationTime,
                      LastAccessTime = finfo.LastAccessTime,
                      LastWriteTime = finfo.LastWriteTime,
                      Length = (finfo as FileInfo)?.Length ?? 0,
                      FileName = finfo.Name
                    }).ToArray();
      // 성공
      return DokanResult.Success;
    }
    // 파일에서 모든 NTFS 스트림 정보를 검색합니다.
    public NtStatus FindStreams(string fileName, out IList<FileInformation> streams, IDokanFileInfo info)
    {
      // 사용하지 않습니다.
      streams = null;
      return DokanResult.NotImplemented;
    }
    // 스트림 버퍼 초기화
    public NtStatus FlushFileBuffers(string fileName, IDokanFileInfo info)
    {
      try
      {
        // 스트림 초기화
        ((FileStream)(info.Context)).Flush();
        // 성공!
        return DokanResult.Success;
      }
      catch (IOException)
      {
        // Disk가 차면 에러가 발생
        return DokanResult.DiskFull;
      }
    }
    // 디스크 크기를 설정
    public NtStatus GetDiskFreeSpace(out long freeBytesAvailable, out long totalNumberOfBytes, out long totalNumberOfFreeBytes, IDokanFileInfo info)
    {
      // 5G
      long total = 1024L * 1024L * 1024L * 5L;
      // 경로의 모든 파일의 크기
      long usedsize = path.GetFiles("*", SearchOption.AllDirectories).Sum(x => { return x.Length; });
      // 사용할 수 있는 디스크 크기
      freeBytesAvailable = total - usedsize;
      // 전체 크기
      totalNumberOfBytes = total;
      // 남아 있는 크기? (사용할 수 있는 디스크 크기와 다른건? )
      totalNumberOfFreeBytes = freeBytesAvailable;
      // 성공
      return DokanResult.Success;
    }
    // 파일 정보 취득
    public NtStatus GetFileInformation(string fileName, out FileInformation fileInfo, IDokanFileInfo info)
    {
      // 물리 파일 경로
      var filePath = GetPath(fileName);
      // 파일 Info 할당
      FileSystemInfo finfo = new FileInfo(filePath);
      // 파일이 아니면
      if (!finfo.Exists)
      {
        // 디렉토리 설정
        finfo = new DirectoryInfo(filePath);
      }
      // 파일 정보 만들기
      fileInfo = new FileInformation
      {
        FileName = fileName,
        Attributes = finfo.Attributes,
        CreationTime = finfo.CreationTime,
        LastAccessTime = finfo.LastAccessTime,
        LastWriteTime = finfo.LastWriteTime,
        Length = (finfo as FileInfo)?.Length ?? 0,
      };
      // 성공
      return DokanResult.Success;
    }
    // 파일 보안 정보
    public NtStatus GetFileSecurity(string fileName, out FileSystemSecurity security, AccessControlSections sections, IDokanFileInfo info)
    {
      try
      {
        // Window에서만 사용되는 보안 정보
        security = info.IsDirectory ? (FileSystemSecurity)Directory.GetAccessControl(GetPath(fileName)) : File.GetAccessControl(GetPath(fileName));
        // 성공
        return DokanResult.Success;
      }
      catch (UnauthorizedAccessException)
      {
        // 정보는 없다.
        security = null;
        // 접근 근지 에러
        return DokanResult.AccessDenied;
      }
    }
    // 내컴퓨터에 생성되는 볼륨 정보
    public NtStatus GetVolumeInformation(out string volumeLabel, out FileSystemFeatures features, out string fileSystemName, out uint maximumComponentLength, IDokanFileInfo info)
    {
      // 디스크 이름
      volumeLabel = "NOWONBUN";
      // 파일 시스템
      fileSystemName = "NTFS";
      // 파일 이름 길이
      maximumComponentLength = 256;
      // 볼륨에서 활성화 된 기능(?)
      features = FileSystemFeatures.CasePreservedNames | FileSystemFeatures.CaseSensitiveSearch |
             FileSystemFeatures.PersistentAcls | FileSystemFeatures.SupportsRemoteStorage |
             FileSystemFeatures.UnicodeOnDisk;
      // 성공
      return DokanResult.Success;
    }
    // 파일 이동
    public NtStatus MoveFile(string oldName, string newName, bool replace, IDokanFileInfo info)
    {
      // 이동 전의 파일 경로
      var oldpath = GetPath(oldName);
      // 이동 후의 파일 경로
      var newpath = GetPath(newName);
      // 스트림이 묶여 있으면 해제한다.
      (info.Context as FileStream)?.Dispose();
      info.Context = null;
      // 디렉토리나 파일이 존재하면.
      var exist = info.IsDirectory ? Directory.Exists(newpath) : File.Exists(newpath);
      try
      {
        // 존재하지 않으면
        if (!exist)
        {
          // 디렉토리라면
          if (info.IsDirectory)
          {
            // 디렉토리를 이동한다.
            Directory.Move(oldpath, newpath);
          }
          else
          {
            // 파일 이동한다.
            File.Move(oldpath, newpath);
          }
          // 성공
          return DokanResult.Success;
        }
        // 파일이 존재한다면
        else if (replace)
        {
          // 디렉토리라면
          if (info.IsDirectory)
          {
            // 접근 권한없음
            return DokanResult.AccessDenied;
          }
          // 파일 삭제하고
          File.Delete(newpath);
          // 파일을 이동한다.
          File.Move(oldpath, newpath);
          // 성공
          return DokanResult.Success;
        }
      }
      catch (UnauthorizedAccessException)
      {
        // 권한없음
        return DokanResult.AccessDenied;
      }
      // 파일 존재
      return DokanResult.FileExists;
    }
    // 파일 읽기
    public NtStatus ReadFile(string fileName, byte[] buffer, out int bytesRead, long offset, IDokanFileInfo info)
    {
      // 스트림이 설정되지 않으면
      if (info.Context == null)
      {
        // 스트림 만들어 주자.
        using (var stream = new FileStream(GetPath(fileName), FileMode.Open, System.IO.FileAccess.Read))
        {
          stream.Position = offset;
          bytesRead = stream.Read(buffer, 0, buffer.Length);
        }
      }
      else
      {
        // 이미 스트림이 존재하면
        var stream = info.Context as FileStream;
        lock (stream)
        {
          // 버퍼를 다시 설정한다.
          stream.Position = offset;
          bytesRead = stream.Read(buffer, 0, buffer.Length);
        }
      }
      // 성공
      return DokanResult.Success;
    }
    // 파일 쓰기
    public NtStatus WriteFile(string fileName, byte[] buffer, out int bytesWritten, long offset, IDokanFileInfo info)
    {
      // 오프섹 설정
      var append = offset == -1;
      // 스트림이 없으면
      if (info.Context == null)
      {
        // 스트림 생ㅅ어
        using (var stream = new FileStream(GetPath(fileName), append ? FileMode.Append : FileMode.Open, System.IO.FileAccess.Write))
        {
          // 추가가 아니면,
          if (!append)
          {
            // 오프셋 초기화
            stream.Position = offset;
          }
          // 스트림 쓰기
          stream.Write(buffer, 0, buffer.Length);
          // 파일 크기
          bytesWritten = buffer.Length;
        }
      }
      else
      {
        // 스트림이 존재하면
        var stream = info.Context as FileStream;
        // lock을 걸고
        lock (stream)
        {
          // 추가라면
          if (append)
          {
            // Seek를 사용하면
            if (stream.CanSeek)
            {
              // 끝으로 이동
              stream.Seek(0, SeekOrigin.End);
            }
            else
            {
              // Seek이 사용안되면.. .에러지..
              bytesWritten = 0;
              return DokanResult.Error;
            }
          }
          else
          {
            // 오프셋 설정
            stream.Position = offset;
          }
          // 파일 쓰기
          stream.Write(buffer, 0, buffer.Length);
        }
        // 파일 크기 설정
        bytesWritten = buffer.Length;
      }
      // 성공
      return DokanResult.Success;
    }
    // 할당 크기
    public NtStatus SetAllocationSize(string fileName, long length, IDokanFileInfo info)
    {
      try
      {
        // 스트림의 크기를 설정한다.
        ((FileStream)(info.Context)).SetLength(length);
        // 성공
        return DokanResult.Success;
      }
      catch (IOException)
      {
        // 디스크 꽉 참 에러
        return DokanResult.DiskFull;
      }
    }
    // 파일 크기가 변경되면
    public NtStatus SetEndOfFile(string fileName, long length, IDokanFileInfo info)
    {
      try
      {
        // 스트림 버퍼 크기 재 설정
        ((FileStream)(info.Context)).SetLength(length);
        // 성공
        return DokanResult.Success;
      }
      catch (IOException)
      {
        // 디스크 꽉 참 에러
        return DokanResult.DiskFull;
      }
    }
    // 파일 정보 수정
    public NtStatus SetFileAttributes(string fileName, FileAttributes attributes, IDokanFileInfo info)
    {
      try
      {
        if (attributes != 0)
        {
          // 옵션 설정
          File.SetAttributes(GetPath(fileName), attributes);
        }
        // 성공
        return DokanResult.Success;
      }
      catch (UnauthorizedAccessException)
      {
        // 접근 권한 에러
        return DokanResult.AccessDenied;
      }
      catch (FileNotFoundException)
      {
        // 파일 없음 에러
        return DokanResult.FileNotFound;
      }
      catch (DirectoryNotFoundException)
      {
        // 경로 없음 에러
        return DokanResult.PathNotFound;
      }
    }
    // 파일 보안 설정
    public NtStatus SetFileSecurity(string fileName, FileSystemSecurity security, AccessControlSections sections, IDokanFileInfo info)
    {
      try
      {
        // 디렉토리라면
        if (info.IsDirectory)
        {
          // 접근 권한 설정
          Directory.SetAccessControl(GetPath(fileName), (DirectorySecurity)security);
        }
        else
        {
          // 파일 권한 설정
          File.SetAccessControl(GetPath(fileName), (FileSecurity)security);
        }
        // 성공
        return DokanResult.Success;
      }
      catch (UnauthorizedAccessException)
      {
        // 접근 금지 에러
        return DokanResult.AccessDenied;
      }
    }
    // 파일 시간 설정
    public NtStatus SetFileTime(string fileName, DateTime? creationTime, DateTime? lastAccessTime, DateTime? lastWriteTime, IDokanFileInfo info)
    {
      try
      {
        // 스트림이 있다면
        if (info.Context is FileStream stream)
        {
          // 생성 날짜
          var ct = creationTime?.ToFileTime() ?? 0;
          // 마지막 접근 날짜
          var lat = lastAccessTime?.ToFileTime() ?? 0;
          // 마지막 수정 날짜
          var lwt = lastWriteTime?.ToFileTime() ?? 0;
          // 성공
          return DokanResult.Success;
        }
        // 물리 파일 경로
        var filePath = GetPath(fileName);
        // 작성 시간 값이 존재하면
        if (creationTime.HasValue)
        {
          // 작성 시간 설정
          File.SetCreationTime(filePath, creationTime.Value);
        }
        // 접근 시간 값이 존재하면
        if (lastAccessTime.HasValue)
        {
          // 접근 시간 설정
          File.SetLastAccessTime(filePath, lastAccessTime.Value);
        }
        // 마지막 수정 시간 존재하면
        if (lastWriteTime.HasValue)
        {
          // 마지막 수정 시간 설정
          File.SetLastWriteTime(filePath, lastWriteTime.Value);
        }
        // 설공
        return DokanResult.Success;
      }
      catch (UnauthorizedAccessException)
      {
        // 접근 권한 없음 에러
        return DokanResult.AccessDenied;
      }
      catch (FileNotFoundException)
      {
        // 파일이 없음 에러
        return DokanResult.FileNotFound;
      }
    }
    // 파일을 Lock 걸기(예를 들면 Stream 커넥션이 걸린 Open이라던가??)
    public NtStatus LockFile(string fileName, long offset, long length, IDokanFileInfo info)
    {
      try
      {
        // 스트림을 가져와서 Lock 걸기
        ((FileStream)(info.Context)).Lock(offset, length);
        // 성공
        return DokanResult.Success;
      }
      catch (IOException)
      {
        // 에러가 발생하면 접근 권한 없음.
        return DokanResult.AccessDenied;
      }
    }
    // 파일 Lock 해제
    public NtStatus UnlockFile(string fileName, long offset, long length, IDokanFileInfo info)
    {
      try
      {
        // 스트림에서 Lock 해제
        ((FileStream)(info.Context)).Unlock(offset, length);
        // 성공
        return DokanResult.Success;
      }
      catch (IOException)
      {
        // 에러가 발생하면 접근 권한 없음.
        return DokanResult.AccessDenied;
      }
    }
    // 드라이브가 Mount되면..
    public NtStatus Mounted(IDokanFileInfo info)
    {
      // 성공
      return DokanResult.Success;
    }
    // 드라이브가 Unmount되면...
    public NtStatus Unmounted(IDokanFileInfo info)
    {
      // 성공
      return DokanResult.Success;
    }
  }
  // 실행 클래스
  public class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Dokan의 물리적 클래스
      var mirror = new DokanOperations(@"D:\Dokan");
      // 등록합시다.
      mirror.Mount(@"N:\", DokanOptions.DebugMode | DokanOptions.EnableNotificationAPI, 5);
      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

기본적인 커널 드라이브를 만드는 것은 라이브러리에서 처리가 되서, 그 라이브러리에 데이터를 넘겨주는 형태인데 그것을 IDokanOperations 인터페이스를 상속받은 클래서로 넘기면 됩니다.

IDokanOperations의 인터페이스에는 기본적인 볼륨의 생성 크기와 파일 리스트, 파일의 리소스를 관리하는 것이 대부분입니다.

저도 레퍼런스를 참고해서 만들었습니다.

링크 - https://dokan-dev.github.io/dokan-dotnet-doc/html/interface_dokan_net_1_1_i_dokan_operations.html


소스를 기동시켜 보겠습니다.

기동을 시키고 이제 익스플로워를 기동해서 드라이브를 확인해 보겠습니다.

프로그램에 설정한 볼륨 크기로 설정이 되었습니다. 참고로 위 볼륨은 데이터를 저장하게 되면 d:\dokan에 저장하게 끔 설정했습니다.

그러면 이제 파일을 복사해보겠습니다.

N드라이브에 파일을 복사하면 d:/dokan 폴더에 파일이 복사가 되는 것을 확인할 수 있습니다.


Dokan으로 커널 드라이브를 생성하고 파일을 복사 및 삭제등도 확인이 되었습니다. 저는 파일 시스템으로 만들었지만 ftp나 smb를 사용하게 되면 이것이 cloud가 되겠네요.

google drive api를 이용하면 google drive도 NDrive처럼 사용할 수도 있겠네요.

WriteFile함수나 ReadFile 함수에 NPOI를 이용하면 아까 이야기했던 드라이브로 Excel Cloud를 사용할 수도 있겠네요... 또는 데이터 베이스의 값을 Excel로 표현할 수도 있습니다.

조금 더 응용하면 여러가지 목적으로 사용할 수 있을 것 같습니다.


여기까지 C#으로 커널 드라이브 생성(Dokan)하는 방법(Cloud 프로젝트)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.