如何在 Win32 中递归创建文件夹?

Posted

技术标签:

【中文标题】如何在 Win32 中递归创建文件夹?【英文标题】:How do I recursively create a folder in 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 规范化路径字符串。 【参考方案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 个字符的罕见 情况下,会出现一个很好的缓冲区溢出,并且您的答案会在删除时增加价值。

以上是关于如何在 Win32 中递归创建文件夹?的主要内容,如果未能解决你的问题,请参考以下文章

Python获取或修改 Windows 系统中文件的创建时间修改时间和访问时间(os | win32file)

Python获取或修改 Windows 系统中文件的创建时间修改时间和访问时间(os | win32file)

Python获取或修改 Windows 系统中文件的创建时间修改时间和访问时间(os | win32file)

尝试使用 Win32 WASAPI C++ 中的“捕获流”创建 wav 文件

c++中,如何获得文件属性(创建时间,修改时间,访问时间)?

转 mongodb win7 32位系统安装以及配置