2017-03-03 45 views
1

我使用的WinAPI GetLogicalDriveStrings()函数需要LPWSTR,并且想知道是否有更安全的方法来确保没有内存泄漏。更安全的构建LPWSTR的方法

目前我构建初始指向缓冲区buf使用:

auto buf = GetLogicalDriveStrings(0, nullptr); 

然后,我创建使用作为代替空指针在我的实际通话所用的LPWSTR:

auto driveStrings = static_cast<LPWSTR>(malloc((buf + 1) * sizeof(WCHAR))); 

接下来,我创建一个指向driveStrings的指针,稍后将其释放。在检查driveStrings是空指针还是缓冲区(buf)为NULL(在无法分配内存的情况下)后,我使用driveStrings调用GetLogicalDriveStrings()

当我得到结果我手动free() LPWSTR使用我分配它后指针。

我该如何使用LPWSTR的智能指针,所以我不必使用malloc()free(),但它仍然可以与GetLogicalDriveStrings()函数一起使用?

工作的最小值例如:

auto buf = GetLogicalDriveStrings(0, nullptr); 

    auto driveStrings = static_cast<LPWSTR>(malloc((buf + 1) * sizeof(WCHAR))); 
    auto pDriveStrings = driveStrings; 

    if (driveStrings == nullptr || buf == NULL) 
    { 
     std::stringstream msg; 
     msg << "Can't allocate memory for drive list: "; 
     msg << GetLastError(); 
     throw std::runtime_error(msg.str()); 
    } 

    // get drive strings 
    if (GetLogicalDriveStrings(buf, driveStrings) == NULL) 
    { 
     std::stringstream msg; 
     msg << "GetLogicalDriveStrings error: "; 
     msg << GetLastError(); 
     throw std::runtime_error(msg.str()); 
    } 

    // iterate over results 
    while (*driveStrings) 
    { 
     // GetDriveType() requires a LPCWSTR 
     if (GetDriveType(driveStrings) == DRIVE_FIXED || GetDriveType(driveStrings) == DRIVE_REMOVABLE) 
     { 
      std::wcout << driveStrings << std::endl; 
     } 
     driveStrings += lstrlen(driveStrings) + 1; 
    } 

    free(pDriveStrings); 

如果我使用一个std::wstring,我无法弄清楚如何通过在driveStrings缓冲每个字符串进行迭代。如果我使用std::vector<WCHAR>,我无法弄清楚如何将每个元素投射到LPCWSTR的GetDriveType()

这工作正常,但有没有更好/更安全的方式来做到这一点?我愿意接受任何改进。

+5

'的std ::矢量缓冲器(BUF + 1);'' – IInspectable

+2

的std :: wstring'也是安全的在C++ 11如果使用得当。 – chris

+2

无论如何,代码都有问题。在你打电话给'free()'之前,你正在使用'malloc',并且可能会抛出''''。 – PaulMcKenzie

回答

2

我如何使用LPWSTR智能指针代替,所以我不必 使用malloc()和free(),但这样它仍然会与 GetLogicalDriveStrings()函数的工作?

您可以使用std::unique_ptr。它可以被用于分配字符数组这样的:

std::unique_ptr<wchar_t[]> buffer(new wchar_t[ size ]); 

示出如何与GetLogicalDriveStrings()使用它的一个例子如下。该示例还显示如何正确呼叫GetLastError()。必须在设置最后一个错误值的函数之后立即调用它。之间的任何其他系统调用(可能隐藏在C或C++标准代码中)可能会使最后一个错误值无效。为了便于使用,我已将它包装到ThrowLastError()函数中,但规则仍适用。

#include <Windows.h> 
#include <iostream> 
#include <string> 
#include <set> 
#include <memory> 

void ThrowLastError(const char* msg) { 
    DWORD err = ::GetLastError(); 
    throw std::system_error(static_cast<int>(err), std::system_category(), msg); 
} 

std::set<std::wstring> GetLogicalDriveSet() { 
    // Call GetLogicalDriveStrings() to get required buffer size. 
    DWORD bufSize = ::GetLogicalDriveStrings(0, nullptr); 
    if(bufSize == 0) 
     ThrowLastError("Could not get logical drives"); 

    // Allocate an array of wchar_t and manage it using unique_ptr. 
    // Make sure to allocate space for last '\0'. 
    std::unique_ptr<wchar_t[]> buffer(new wchar_t[ bufSize + 1 ]); 

    // Call GetLogicalDriveStrings() 2nd time to actually receive the strings. 
    DWORD len = ::GetLogicalDriveStrings(bufSize, buffer.get()); 
    if(len == 0) 
     ThrowLastError("Could not get logical drives"); 

    // In a rare case the number of drives may have changed after 
    // the first call to GetLogicalDriveStrings(). 
    if(len > bufSize) 
     throw std::runtime_error("Could not get logical drives - buffer size mismatch"); 

    std::set<std::wstring> result; 

    // Split the string returned by GetLogicalDriveStrings() at '\0'. 
    auto p = buffer.get(); 
    while(*p) { 
     std::wstring path(p); 
     result.insert(path); 
     p += path.size() + 1; 
    } 

    return result; 
} 

int main(int argc, char* argv[]) { 
    std::set<std::wstring> drives; 
    try { 
     drives = GetLogicalDriveSet(); 
    } 
    catch(std::exception& e) { 
     std::cout << "Error: " << e.what() << std::endl; 
     return 1; 
    } 

    std::cout << "Fixed and removable drives:\n"; 
    for(const auto& drv : drives) { 
     DWORD driveType = ::GetDriveType(drv.c_str()); 
     if(driveType == DRIVE_FIXED || driveType == DRIVE_REMOVABLE){ 
      std::wcout << drv << std::endl; 
     } 
    } 
    return 0; 
} 

就我个人而言,我会去与GetLogicalDrives()尽管这完全避免了缓冲区管理的麻烦。此外,错误处理简化了,因为您只需调用一次该函数。为了完整起见,我提供了一个示例,如何使用下面的GetLogicalDrives()

#include <Windows.h> 
#include <iostream> 
#include <string> 
#include <set> 

void ThrowLastError(const char* msg) { 
    DWORD err = ::GetLastError(); 
    throw std::system_error(static_cast<int>(err), std::system_category(), msg); 
} 

std::set<std::wstring> GetLogicalDriveSet() { 
    std::set<std::wstring> result; 

    DWORD mask = GetLogicalDrives(); 
    if(mask == 0) 
     ThrowLastError("Could not get logical drives");  

    for(wchar_t drive = 'A'; drive <= 'Z'; ++drive) { 
     if(mask & 1) { 
      // Build a complete root path like "C:\\" that can be used 
      // with GetDriveType(). 
      wchar_t path[]{ drive, ':', '\\', 0 }; 
      result.insert(path); 
     } 
     // Shift all bits to the right so next "mask & 1" will test for 
     // next drive letter. 
     mask >>= 1; 
    } 

    return result; 
} 

int main(int argc, char* argv[]){ 
    std::set<std::wstring> drives; 
    try { 
     drives = GetLogicalDriveSet(); 
    } 
    catch(std::exception& e){ 
     std::cout << "Error: " << e.what() << std::endl; 
     return 1; 
    } 

    std::cout << "Fixed and removable drives:\n"; 
    for(const auto& drv : drives) { 
     DWORD driveType = ::GetDriveType(drv.c_str()); 
     if(driveType == DRIVE_FIXED || driveType == DRIVE_REMOVABLE){ 
      std::wcout << drv << std::endl; 
     } 
    } 

    return 0; 
} 
+0

请解释你的downvote,以便我可以从我的错误中学习。 – zett42

+0

这不是OP所要求的。 – IInspectable

+1

为什么不能使原始问题过时? OP的问题来自于使用具有易于使用的备选方案的API,因此它应该是建议该API并展示如何使用的有效答案。 – zett42

2

我想我会做这样的事情:

std::wstring s(buf+1, '\0'); 

auto len = GetLogicalDriveStrings(buf, &s[0]); 
s.resize(len); 

这将创建一个包含完全无效wstring,然后GetLogicalDriveStrings覆盖与它所产生的内容。最后,我们将字符串大小调整为GetLogicalDriveStrings实际写入的字符数。

从那里,我们有一个完全正常的字符串,当它超出范围时,它将释放它的内存,就像任何其他字符串一样。

+0

这是有道理的,我目前使用while循环'while(* driveStrings){driveStrings + = lstrlen(driveStrings)+1}'迭代返回的值。我将如何遍历'std :: wstring'?我可以按NUL字节分割吗?或者我需要使用'std :: vector '来做到这一点? – Dan

+0

您可以迭代字符串中的字符 –

+1

您不应该调用'GetLogicalDriveStrings(buf + 1,&s [0])',而是调用GetLogicalDriveStrings(buf,&s [0])'因为文档声明该函数期望没有终止空字符的字符数。 – zett42