C++ 如何在SHGetKnownFolderPath API中使用CString对象获取程序数据路径

C++ 如何在SHGetKnownFolderPath API中使用CString对象获取程序数据路径,c++,mfc,C++,Mfc,我使用下面的代码使用SHGetKnowFolderPath获取ProgramData路径 你能告诉我这是否是使用CString的正确方法吗?如果没有,那么使用SHGetKnownFolderPath获取ProgramData路径的最佳解决方案是什么 我使用了下面的两个示例,两个都有效,但我不确定它们是否正确 CString szProgramDataPath; szProgramDataPath.GetBuffer(MAX_PATH); if (FAILED(SHGetKnownFold

我使用下面的代码使用SHGetKnowFolderPath获取ProgramData路径

你能告诉我这是否是使用CString的正确方法吗?如果没有,那么使用SHGetKnownFolderPath获取ProgramData路径的最佳解决方案是什么

我使用了下面的两个示例,两个都有效,但我不确定它们是否正确

CString szProgramDataPath;  
szProgramDataPath.GetBuffer(MAX_PATH);  
if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, (PWSTR*)&szProgramDataPath)))
{
    cout << _T("Failed, error is: ") << GetLastError();
}
或:

语法是:

HRESULT SHGetKnownFolderPath(
   _In_     REFKNOWNFOLDERID rfid,
   _In_     DWORD            dwFlags,
   _In_opt_ HANDLE           hToken,
   _Out_    PWSTR            *ppszPath
 );
注意,第四个参数是PWSTR*而不是CString

发件人:

ppszPath[out] 类型:PWSTR*

当此方法返回时,包含 指向以null结尾的Unicode字符串的指针的地址 指定已知文件夹的路径。调用过程是 负责在用户不再需要此资源时释放此资源 呼叫CoTaskMemFree。返回的路径不包括尾随路径 反斜杠。例如,返回的是C:\Users,而不是 C:\Users\

您需要传入一个PWSTR变量。之后,您需要释放指向的资源:

LPWSTR path = NULL;  //LP = "Long pointer" -- same as PWSTR *path;  
SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &path);

...

CoTaskMemFree(path);
在检查SHGetKnownFolderPath是否成功返回后,可以从LPWSTR path变量直接将CString分配给

CString szProgramDataPath = path;
语法是:

HRESULT SHGetKnownFolderPath(
   _In_     REFKNOWNFOLDERID rfid,
   _In_     DWORD            dwFlags,
   _In_opt_ HANDLE           hToken,
   _Out_    PWSTR            *ppszPath
 );
注意,第四个参数是PWSTR*而不是CString

发件人:

ppszPath[out] 类型:PWSTR*

当此方法返回时,包含 指向以null结尾的Unicode字符串的指针的地址 指定已知文件夹的路径。调用过程是 负责在用户不再需要此资源时释放此资源 呼叫CoTaskMemFree。返回的路径不包括尾随路径 反斜杠。例如,返回的是C:\Users,而不是 C:\Users\

您需要传入一个PWSTR变量。之后,您需要释放指向的资源:

LPWSTR path = NULL;  //LP = "Long pointer" -- same as PWSTR *path;  
SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &path);

...

CoTaskMemFree(path);
在检查SHGetKnownFolderPath是否成功返回后,可以从LPWSTR path变量直接将CString分配给

CString szProgramDataPath = path;
这两种方法都不正确

SHGetKnownFolderPath返回指向新分配字符串的指针,在使用完该字符串后,必须使用CoTaskMemFree释放该字符串

这两个代码示例都试图使用operator&直接访问CString的内部指针,指向其字符数据,希望CString将拥有SHGetKnownFolderPath返回的指针

这是错误的,非常危险!有几个原因

假设该内部指针的地址与CString对象本身的地址相同。CString不会重写运算符&返回其内部缓冲区指针的地址,就像您假设的那样

即使它这样做了,第一个代码示例仍然被破坏,因为它存在内存泄漏。您正在使用GetBuffer预分配字符缓冲区,之后不调用ReleaseBuffer。SHGetKnownFolderPath在将指针重新分配到返回的内存块之前不会释放该缓冲区,因此最终会泄漏先前的内存块

假设CStrings析构函数使用CoTaskMemFree或compatible函数来释放其内部字符缓冲区。这是不安全的假设

假设CStrings内部字符缓冲区的内存布局与SHGetKnownFolderPath返回的内存块的内存布局兼容。事实并非如此,因为CString为其字符数据和其他内容实现了一个引用计数器。你认为那个计数器存放在哪里?对,在角色缓冲区内部!由于SHGetKnownFolderPath不返回与CString的数据格式兼容的内存缓冲区,因此您将损坏CString数据,并在以后尝试访问内存中不存在的内容时导致问题。有关“CString”内部详细信息,请参阅

简而言之,CString的设计并不是为了按您尝试的方式使用。您正在做出一些非常危险的假设,从而导致代码中出现未定义的行为

您不能直接访问CString的内部缓冲区指针以将其重新分配到其他地址。正确的解决方案看起来更像这样:

CString szProgramDataPath;  
LPWSTR pProgramDataPath;

if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, &pProgramDataPath)))
{
    // DON'T use _T() here! std::cout expects char* strings only...
    cout << "Failed, error is: " << GetLastError();
}
else
{
    szProgramDataPath = pProgramDataPath;  
    CoTaskMemFree(pProgramDataPath);
}
这两种方法都不正确

SHGetKnownFolderPath返回指向新分配字符串的指针,在使用完该字符串后,必须使用CoTaskMemFree释放该字符串

这两个代码示例都试图使用operator&直接访问CString的内部指针,指向其字符数据,希望CString将拥有SHGetKnownFolderPath返回的指针

这是错误的,非常危险!有几个原因

假设该内部指针的地址与CString对象本身的地址相同。CString不会重写运算符&返回其内部缓冲区指针的地址,就像您假设的那样

甚至 如果是这样,第一个代码示例仍然会被破坏,因为它存在内存泄漏。您正在使用GetBuffer预分配字符缓冲区,之后不调用ReleaseBuffer。SHGetKnownFolderPath在将指针重新分配到返回的内存块之前不会释放该缓冲区,因此最终会泄漏先前的内存块

假设CStrings析构函数使用CoTaskMemFree或compatible函数来释放其内部字符缓冲区。这是不安全的假设

假设CStrings内部字符缓冲区的内存布局与SHGetKnownFolderPath返回的内存块的内存布局兼容。事实并非如此,因为CString为其字符数据和其他内容实现了一个引用计数器。你认为那个计数器存放在哪里?对,在角色缓冲区内部!由于SHGetKnownFolderPath不返回与CString的数据格式兼容的内存缓冲区,因此您将损坏CString数据,并在以后尝试访问内存中不存在的内容时导致问题。有关“CString”内部详细信息,请参阅

简而言之,CString的设计并不是为了按您尝试的方式使用。您正在做出一些非常危险的假设,从而导致代码中出现未定义的行为

您不能直接访问CString的内部缓冲区指针以将其重新分配到其他地址。正确的解决方案看起来更像这样:

CString szProgramDataPath;  
LPWSTR pProgramDataPath;

if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, &pProgramDataPath)))
{
    // DON'T use _T() here! std::cout expects char* strings only...
    cout << "Failed, error is: " << GetLastError();
}
else
{
    szProgramDataPath = pProgramDataPath;  
    CoTaskMemFree(pProgramDataPath);
}

如果我有你的问题,你需要看看mbstowcs和wcstombs函数,它们在wchar*和char*之间转换,反之亦然

如果我有你的问题,你需要看看mbstowcs和wcstombs函数,它们在wchar*和char*之间转换,反之亦然,确保以后调用ReleaseBuffer。@从技术上讲,只有当缓冲区的内容发生更改且新字符串长度与更改前不同时,才需要调用ReleaseBuffer。否则调用ReleaseBuffer是可选的,因为它基本上是不可操作的。但是,在GetBuffer之后调用ReleaseBuffer总是一种好的做法,以防万一。也许有一天MS会重新设计GetBuffer来分配一个临时内存块,然后ReleaseBuffer将其合并到主字符串中。谁知道呢。@RemyLebeau又对了:用这种方式使用GetBuffer时,一定要在以后调用ReleaseBuffer。@Danny\d从技术上讲,只有当缓冲区的内容发生变化并且新的字符串长度与变化前不同时,才需要调用ReleaseBuffer。否则调用ReleaseBuffer是可选的,因为它基本上是不可操作的。但是,在GetBuffer之后调用ReleaseBuffer总是一种好的做法,以防万一。也许有一天MS会重新设计GetBuffer来分配一个临时内存块,然后ReleaseBuffer将其合并到主字符串中。谁知道呢。@RemyLebeau又对了:很好的答案+1,但这有点让人困惑:你认为计数器存储在哪里?对,在字符缓冲区内部,因为计数器和大小等是类的一个成员,在指向内部字符缓冲区的指针旁边。@Danny_不符合我在回答中链接到的一篇简单文章中的CString。除非微软在那篇文章发表后重新设计了CString。但是,将引用计数和其他数据属性移到数据缓冲区之外是没有意义的,这会使整个重新计数系统和复制构造函数/赋值更难管理。您像往常一样是对的:。我记得在过去几次阅读源代码,一定是忽略了或忘记了这一点。谢谢你让我头脑清醒!很好的答案+1,但这有点让人困惑:你认为计数器存储在哪里?对,在字符缓冲区内部,因为计数器和大小等是类的一个成员,在指向内部字符缓冲区的指针旁边。@Danny_不符合我在回答中链接到的一篇简单文章中的CString。除非微软在那篇文章发表后重新设计了CString。但是,将引用计数和其他数据属性移到数据缓冲区之外是没有意义的,这会使整个重新计数系统和复制构造函数/赋值更难管理。您像往常一样是对的:。我记得在过去几次阅读源代码,一定是忽略了或忘记了这一点。谢谢你让我头脑清醒!您所说的在技术上是正确的,但并没有解决实际提出的如何以正确的方式将返回路径导入CString的问题。您所说的在技术上是正确的,但没有解决实际提出的如何以正确的方式将返回路径导入CString的问题。