【问题标题】:How do I recursively create a folder in Win32?如何在 Win32 中递归创建文件夹?
【发布时间】:2010-12-04 14:01:24
【问题描述】:

我正在尝试创建一个采用目录名称(C:\foo\bar..\foo\bar\..\baz\\someserver\foo\bar)的函数,并根据需要创建目录以便创建整个路径。

我自己正在尝试一个非常幼稚的实现,这似乎是一个字符串处理的噩梦。有/\,有以\\ 开头的网络共享的特殊情况(你也不能尝试 mkdir() 路径的前两级,即机器名和共享名) ,并且有\.\类型的废话可以存在路径中。

是否存在在 C++ 中执行此操作的简单方法?

【问题讨论】:

  • 为什么不直接使用 Win32 API 呢?
  • 使用 Boost::filesystem 这个链接会帮助你:) boost.org/doc/libs/1_40_0/libs/filesystem/doc/index.htm
  • @JonathanFeinberg:在版本 8 之前的 Windows 中,路径名的解析受到严格限制(MAX_PATH 长度限制,不支持“\\?\”前缀等)。您的评论确实适用于 Windows仅限 8 及更高版本。
  • 这是我有生以来第一次看到接受了 -10 票的答案,而且它是由拥有 17k 声誉的用户撰写的。
  • 如果您不想做噩梦,请使用 GetFullPathName 规范化路径字符串。

标签: c++ winapi directory


【解决方案1】:

如果您不需要支持 Windows 2000 之前的 Windows 版本,您可以为此使用 SHCreateDirectoryEx function。考虑一下:

int createDirectoryRecursively( LPCTSTR path )
{
    return SHCreateDirectoryEx( NULL, path, NULL );
}

// ...
if ( createDirectoryRecursively( T("C:\\Foo\\Bar\\Baz") ) == ERROR_SUCCESS ) {
   // Bingo!
} 

如果使用这样的 shell32.dll API 成为问题,您总是可以用其他东西(可能是手动循环)重新实现上面的 createDirectoryRecursively 函数。

【讨论】:

  • 警告 SHCreateDirectoryEx 在处理隐藏目录时具有特殊行为,可能会向用户显示用户界面对话框。
  • 它只在传递了 hwnd 引用时显示用户界面对话框,而不是在它为 NULL 时。
  • @Joakim:即使 HWND 为 NULL,您也可能会大吃一惊,因为它可以处理非排队消息。
  • 另外SHCreateDirectoryEx 不能处理超过MAX_PATH(前缀"\\?\")的路径。
  • @zett42:SHCreateDirectoryEx()是支持Windows 10's removal of the MAX_PATH limitation的功能之一吗? “从 Windows 10 版本 1607 开始,MAX_PATH 限制已从常见的 Win32 文件和目录函数中删除。但是,您必须选择加入新行为。”本文仅列出了具有已更新,但没有任何 Shell 函数。
【解决方案2】:

这是一个不使用外部库的版本,因此仅限 Win32,并且在所有版本的 Windows(包括 Windows CE,我需要它)中都可以使用:

wchar_t *path = GetYourPathFromWherever();

wchar_t folder[MAX_PATH];
wchar_t *end;
ZeroMemory(folder, MAX_PATH * sizeof(wchar_t));

end = wcschr(path, L'\\');

while(end != NULL)
{
    wcsncpy(folder, path, end - path + 1);
    if(!CreateDirectory(folder, NULL))
    {
        DWORD err = GetLastError();

        if(err != ERROR_ALREADY_EXISTS)
        {
            // do whatever handling you'd like
        }
    }
    end = wcschr(++end, L'\\');
}

【讨论】:

  • 看起来不错,但如果我没看错的话,它要求路径后面有一个“\”,否则像“C:\folder\subfolder”这样的路径不会创建“子文件夹” , 但 "C:\folder\subfolder\" 会。循环完成后,您可能需要额外调用 CreateDirectory(path)。
  • 这旨在获取完全限定的文件路径,并确保目标文件夹存在。如果你只想传递一个文件夹路径,那么是的,它需要进一步的工作
【解决方案3】:

这是我编写的一个函数,它迭代地创建一个文件夹树。这是主要功能:

#include <io.h>
#include <string>
#include <direct.h>
#include <list>

// Returns false on success, true on error
bool createFolder(std::string folderName) {
    list<std::string> folderLevels;
    char* c_str = (char*)folderName.c_str();

    // Point to end of the string
    char* strPtr = &c_str[strlen(c_str) - 1];

    // Create a list of the folders which do not currently exist
    do {
        if (folderExists(c_str)) {
            break;
        }
        // Break off the last folder name, store in folderLevels list
        do {
            strPtr--;
        } while ((*strPtr != '\\') && (*strPtr != '/') && (strPtr >= c_str));
        folderLevels.push_front(string(strPtr + 1));
        strPtr[1] = 0;
    } while (strPtr >= c_str);

    if (_chdir(c_str)) {
        return true;
    }

    // Create the folders iteratively
    for (list<std::string>::iterator it = folderLevels.begin(); it != folderLevels.end(); it++) {
        if (CreateDirectory(it->c_str(), NULL) == 0) {
            return true;
        }
        _chdir(it->c_str());
    }

    return false;
}

folderExists 例程如下:

// Return true if the folder exists, false otherwise
bool folderExists(const char* folderName) {
    if (_access(folderName, 0) == -1) {
        //File not found
        return false;
    }

    DWORD attr = GetFileAttributes((LPCSTR)folderName);
    if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
        // File is not a directory
        return false;
    }

    return true;
}

我测试上述函数的一个示例调用如下(并且有效):

createFolder("C:\\a\\b\\c\\d\\e\\f\\g\\h\\i\\j\\k\\l\\m\\n\\o\\p\\q\\r\\s\\t\\u\\v\\w\\x\\y\\z");

此功能尚未经过非常彻底的测试,我不确定它是否适用于其他操作系统(但可能与一些修改兼容)。我目前正在使用Visual Studio 2010Windows 7.

【讨论】:

  • 乍一看确实有效,但它会将当前目录更改为新创建的目录,如果使用相对路径,您可能不希望发生这种情况。
  • 不使用 Unicode 是一种死罪,并且在整个千年中一直如此。不知道为什么要将GetFileAttributes 的第一个参数转换为与其传递的完全相同的类型。
【解决方案4】:

SHCreateDirectory 函数可以做到这一点。但该文档指出它可能会在更高版本的 Windows 中被弃用。

来自 MSDN

注意这个功能是 可通过 Windows XP 服务获得 包 2 (SP2) 和 Microsoft Windows Server 2003。它可能会被更改或 在后续版本中不可用 窗户。

【讨论】:

    【解决方案5】:

    在 C++17 中,使用 std::filesystem::create_directories() 可以很容易地做到这一点。

    例子:

    #include <filesystem>
    ...
    
    const char* path = "C:\\foo\\bar";
    std::filesystem::create_directories(path);
    

    【讨论】:

      【解决方案6】:

      好例子:

      #ifndef UNICODE
      #define UNICODE
      #define UNICODE_WAS_UNDEFINED
      #endif
      
      #include <Windows.h>
      
      #ifdef UNICODE_WAS_UNDEFINED
      #undef UNICODE
      #endif
      
      #include <string>
      
      BOOL DirectoryExists(LPCTSTR szPath)
      {
        DWORD dwAttrib = GetFileAttributes(szPath);
      
        return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
          (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
      }
      
      void createDirectoryRecursively(std::wstring path)
      {
        signed int pos = 0;
        do
        {
          pos = path.find_first_of(L"\\/", pos + 1);
          CreateDirectory(path.substr(0, pos).c_str(), NULL);
        } while (pos != std::wstring::npos);
      }
      
      //in application
      int main()
      {
        std::wstring directory = L"../temp/dir";
        if (DirectoryExists(directory.c_str()) == FALSE)
          createDirectoryRecursively(directory);
        return 0;
      }
      

      【讨论】:

      • 这是错误的。如果../temp 已经存在怎么办?
      • @FrankHB 没关系。
      【解决方案7】:

      适用于 Windows XP 及更高版本。期望以 Widechar 为空终止的字符串和递归操作的数量作为参数。尚未测试超过 1 个级别。

      注意:路径分隔符必须是 '\'

      bool CreateRecursiveDirectoryW(const wchar_t* filepath, const int max_level)
      {
          bool result = false;
          wchar_t path_copy[MAX_PATH] = {0};
          wcscat_s(path_copy, MAX_PATH, filepath);
          std::vector<std::wstring> path_collection;
      
          for(int level=0; PathRemoveFileSpecW(path_copy) && level < max_level; level++)
          {
              path_collection.push_back(path_copy);
          }
          for(int i=path_collection.size()-1; i >= 0; i--)
          {
              if(!PathIsDirectoryW(path_collection[i].c_str()))
                  if(CreateDirectoryW(path_collection[i].c_str(), NULL))
                      result = true;
          }
          return result;
      };
      

      【讨论】:

        【解决方案8】:

        ctacke 你忘记了最后一段。例如'\aa\bb\"cc"' 以下是对 ctacke 的修改:

        //---------------------------------------------------------------------
        int isfexist(char *fn)
        {
            struct stat stbuf;
            extern int errno;
        
            if (stat(fn, &stbuf)) {
                if (errno == ENOENT) return(0);
                else {
                    printf("isfexist: stat");
                    return(0);
                }
            } else {
                if (stbuf.st_mode & S_IFDIR) return(2);
                else return(1);
            }
        }
        //---------------------------------------------------------------------
        int MakeDirTree(char *path)
        {
            char *end1, *end2;
        
            if (path[0] == '\\') end1 = path + 1;       // Case '\aa\bb'
            else if (path[1] == ':' && path[2] == '\\') end1 = path + 3;    // Case 'C:\\aa\\bb'
            else end1 = path;
        
            for(;;) {
                end2 = strchr(end1, '\\');
                if (end2 == NULL) {
                    // Case '\aa\bb\'
                    if (*end1 == 0) break;
                    // Last segment '\aa\bb\"cc"' not yet proceed
                } else *end2 = 0;
                if (isfexist(path) <= 0) mkdir(path);
                if (end2 == NULL) break;    // Last segment finished
                else {
                    *end2 = '\\';
                    end1 = end2 + 1;
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          我正在修改一个旧的 Windows CE 应用程序,这是我打算使用的。也应该在 Windows CE 中工作。这实际上也是递归的:

          static void createPath(const CString& p)
          {
             // only create directories that don't exist
             if (::GetFileAttributes(p) == INVALID_FILE_ATTRIBUTES)
             {
                // check if our parent needs to be created, too...
                int i = p.ReverseFind('\\');
                if (i > 0)
                {
                   // ...yes, create the parent (recursively)
                   createPath(p.Left(i));
                }
          
                // finally, actually create the directory in p
                ::CreateDirectory(p, NULL);
             }
          }
          

          【讨论】:

          • 我实际上有机会在 Win32 和 WinCE 上进行测试,似乎工作正常。
          【解决方案10】:
          UnicodeString path = "C:\\Test\\Test\\Test\\";
          TStringList *list = new TStringList();
          
          try
          {
              list->Delimiter = '\\';
              list->StrictDelimiter = true;
              list->DelimitedText = path;
              path = list->Strings[0]; \\drive letter
              for(int i = 1; i < list->Count - 1; i++)
              {
                  try
                  {
                      path += "\\" + list->Strings[i];
                      CreateDirectory(path.w_str(), NULL);
                  }
                  catch(...) { }
              }
          }
          catch(...) { }
          delete list;
          

          【讨论】:

            【解决方案11】:

            如果您使用的是 Microsoft 的 Windows Implementation Libraries (WIL),则可以使用 the wil::CreateDirectoryDeep function。如果没有,您可能要考虑使用它,或者借用代码。

            【讨论】:

              【解决方案12】:

              来自http://www.cplusplus.com/reference/string/string/find_last_of/

              // string::find_last_of
              #include <iostream>
              #include <string>
              using namespace std;
              
              void SplitFilename (const string& str)
              {
                size_t found;
                cout << "Splitting: " << str << endl;
                found=str.find_last_of("/\\");
                cout << " folder: " << str.substr(0,found) << endl;
                cout << " file: " << str.substr(found+1) << endl;
              }
              
              int main ()
              {
                string str1 ("/usr/bin/man");
                string str2 ("c:\\windows\\winhelp.exe");
              
                SplitFilename (str1);
                SplitFilename (str2);
              
                return 0;
              

              这应该让您了解如何处理路径字符串。然后,您需要做的就是遍历从驱动器开始到最深文件夹的路径。检查文件夹是否存在,如果不存在,则创建它。

              【讨论】:

                【解决方案13】:

                这是我的代码示例(复制自How can I create directory tree in C++/Linux?)。也许它不符合第一篇文章的所有要求,但非常好,它适用于 Windows 和 Linux:

                #include <iostream>
                #include <string>
                #include <sys/stat.h> // stat
                #include <errno.h>    // errno, ENOENT, EEXIST
                #if defined(_WIN32)
                #include <direct.h>   // _mkdir
                #endif
                
                bool isDirExist(const std::string& path)
                {
                #if defined(_WIN32)
                    struct _stat info;
                    if (_stat(path.c_str(), &info) != 0)
                    {
                        return false;
                    }
                    return (info.st_mode & _S_IFDIR) != 0;
                #else 
                    struct stat info;
                    if (stat(path.c_str(), &info) != 0)
                    {
                        return false;
                    }
                    return (info.st_mode & S_IFDIR) != 0;
                #endif
                }
                
                bool makePath(const std::string& path)
                {
                #if defined(_WIN32)
                    int ret = _mkdir(path.c_str());
                #else
                    mode_t mode = 0755;
                    int ret = mkdir(path.c_str(), mode);
                #endif
                    if (ret == 0)
                        return true;
                
                    switch (errno)
                    {
                    case ENOENT:
                        // parent didn't exist, try to create it
                        {
                            int pos = path.find_last_of('/');
                            if (pos == std::string::npos)
                #if defined(_WIN32)
                                pos = path.find_last_of('\\');
                            if (pos == std::string::npos)
                #endif
                                return false;
                            if (!makePath( path.substr(0, pos) ))
                                return false;
                        }
                        // now, try to create again
                #if defined(_WIN32)
                        return 0 == _mkdir(path.c_str());
                #else 
                        return 0 == mkdir(path.c_str(), mode);
                #endif
                
                    case EEXIST:
                        // done!
                        return isDirExist(path);
                
                    default:
                        return false;
                    }
                }
                
                int main(int argc, char* ARGV[])
                {
                    for (int i=1; i<argc; i++)
                    {
                        std::cout << "creating " << ARGV[i] << " ... " << (makePath(ARGV[i]) ? "OK" : "failed") << std::endl;
                    }
                    return 0;
                }
                

                用法:

                d:\Work\c++\make_path> makePath 1/2 folderA/folderB/folderC
                creating 1/2 ... OK
                creating folderA/folderB/folderC ... OK
                

                【讨论】:

                • 如果没有 Unicode 支持,这在 Windows 中绝对行不通。
                • 根据错误创建父母,我不知道我是否应该不喜欢这种方法。但是使用_mkdir 是个好主意。
                【解决方案14】:

                下面的方法帮助我创建了几个目录,直到实现了整个路径。

                假设你有:

                C:\d1 并且您将 C:\d1\d2\d3 作为参数传递。

                此函数将继续在d1 内创建d2 并在d2 内创建d3。此外,它不会与您的 d1 目录中已经存在的文件混淆。

                // HRESULT CreateDirectoryStructure(directoryPath)
                
                std::wstring directoryPath;
                directoryPath= L"C:\\d1\\d2\\d3\\";
                hr = CreateDirectoryStructure(directoryPath);
                
                //----------------------------------------------//
                
                HRESULT
                CreateDirectoryStructure(
                    _In_ const std::wstring& directory_path
                )
                /*++
                
                Routine Description:
                
                    Creates the directory and all parent directories specified by the
                    directory_path. The path should end with backslash.
                
                Arguments:
                
                    directory_path - path of the directory to be created.
                
                Return Value:
                
                    S_OK on success. On failure appropriate HRESULT is returned.
                
                --*/
                {
                    HRESULT hr = S_OK;
                    DWORD error = ERROR_SUCCESS;
                    bool result = false;
                    PWSTR normalized_path = NULL;
                    PWSTR last_path = NULL;
                
                    if (directory_path.size() == 0)
                    {
                        hr = ERROR_INVALID_PARAMETER;
                        goto Cleanup;
                    }
                    normalized_path = _wcsdup(directory_path.c_str());
                    //
                    // Find the first directory separator since this returns the system root.
                    //
                    last_path = wcschr(normalized_path, L'\\');
                    if (last_path != NULL)
                    {
                        last_path++;
                    }
                
                    //
                    // Create all directories and subdirectories in the path.
                    //
                    while ((last_path = wcschr(last_path, L'\\')) != NULL)
                    {
                        *last_path = '\0';
                        result = CreateDirectory(
                            normalized_path,
                            NULL);
                        *last_path = L'\\';
                        last_path++;
                        if (result == false)
                        {
                            error = GetLastError();
                            if(error != ERROR_ALREADY_EXISTS)
                            {
                                hr = HRESULT_FROM_WIN32(error);
                                goto Cleanup;
                            }
                        }
                    }
                
                Cleanup:
                    return hr;
                }
                

                【讨论】:

                • 如何命名函数完全没有意义。一个有用的答案将包括代码。这个建议的答案没有用。
                • @IInspectable 感谢您的建议,我通过添加代码示例解决了您的评论。希望这些更改使答案对您有用。
                • 这没有帮助。 CreateDirectoryStructure 不是系统 API 调用,也不是 C++ 标准库调用。这个提议的答案对无法访问所述功能的实现的任何人都没有用。
                • 对于@IInspectable 的困惑,我深表歉意。我已经添加了整个代码,希望对您有所帮助!
                【解决方案15】:
                void createFolders(const std::string &s, char delim) {
                    std::stringstream ss(s);
                    std::string item;
                    char combinedName[50]={'\0'};
                    while (std::getline(ss, item, delim)) { 
                        sprintf(combinedName,"%s%s%c",combinedName,item.c_str(),delim);
                        cout<<combinedName<<endl;
                
                        struct stat st = {0};
                                 if (stat(combinedName,&st)==-1)
                                 { 
                            #if REDHAT
                                     mkdir(combinedName,0777);
                            #else
                                      CreateDirectory(combinedName,NULL);
                            #endif
                                 }
                
                
                     }
                }
                

                【讨论】:

                • 你能添加一些文字来解释你的答案吗?
                • createFloders("a/b/c/d/e",'/');
                • 不支持 Unicode 并且任意限制为 49 个字符。此外,在完整路径超过 49 个字符的罕见 情况下,会出现一个很好的缓冲区溢出,并且您的答案会在删除时增加价值。
                最近更新 更多