我发现这条代码在线承诺将数据加载到缓冲区中,我需要这样,所以我可以在屏幕上单独显示每个.bmp图像。
BOOL OpenBmpFile(char* filePath, char* fileName, int* offset, HWND hwnd)
{
OPENFILENAME ofn;
char szFileName[256];
char szFilePath[256];
BOOL FileOK;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = TEXT("Bitmap Files (*.bmp)\0*.bmp\0\0");
ofn.nFilterIndex = 1;
strcpy(szFilePath, "*.bmp");
ofn.lpstrFile = (LPWSTR)szFilePath;
//
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
//
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFilePath);
ofn.lpstrFileTitle = (LPWSTR)szFileName;
ofn.nMaxFileTitle = sizeof(szFileName);
ofn.lpstrTitle = TEXT("Open BMP File");
ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_LONGNAMES | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
// show the common dialog "Open BMP File"
FileOK = GetOpenFileName(&ofn);
// if cancel, exit
if (!FileOK)
return FALSE;
// else store the selected filename
strcpy(fileName, szFileName);
//I use this because strcpy stops after the first NULL
memcpy(filePath, szFilePath, sizeof(szFilePath));
*offset = ofn.nFileOffset;
if(szFilePath[ofn.nFileOffset-1] != '\0')
{
MessageBox(hwnd,L"Single Selection",L"Open Debug 1",MB_OK);
}
else
{
MessageBox(hwnd,L"Multiple Selection",L"Open Debug 2",MB_OK);
}
return TRUE;
}
但是,每次使用以下行调用此函数都会导致错误:
OpenBmpFile((char*)file, (char*)file2, pTest, hWnd);
错误:pTest是nullptr;
我想我的问题是,如何有效地使用此功能来显示我的图像?
答案 0 :(得分:0)
nFileOffset
是从lpstrFile
开头到文件名的偏移量:
如果
lpstrFile
指向以下字符串"c:\dir1\dir2\file.ext"
,则此成员包含值13以指示"file.ext"
字符串的偏移量。如果用户选择多个文件,nFileOffset
是第一个文件名的偏移量。
根据这一点,szFilePath[ofn.nFileOffset-1]
将指向\
。 lpStrFile
定义为:
...如果设置了
OFN_ALLOWMULTISELECT
标志并且用户选择了多个文件,则缓冲区包含当前目录,后跟所选文件的文件名。对于资源管理器样式的对话框,目录和文件名字符串是NULL分隔的,在最后一个文件名后面有一个额外的NULL字符。
因此,要确定用户是否选择了多个文件,请转到lpstrFile
的末尾(即第一个空值),然后检查其后是否有另一个空值,如果没有,则用户选择了多个文件。
要构造每个文件的完整路径和文件名,请重复使用最多nFileOffset
的部分并连接每个文件名。返回时,使用调试器检查ofn
以获取所有详细信息。
答案 1 :(得分:0)
Microsoft建议使用更现代的Common Item Dialog API(自Windows Vista以来可用)而不是GetOpenFileName().
使用此API,如果提供的缓冲区太小(这将要求您使用更大的缓冲区再次调用API),则没有分割文件名缓冲区和处理错误条件的麻烦。在您的代码中,缓冲区(szFilePath)太小用于多个选择结果,因此如果用户选择了许多文件,您很快就会遇到此错误情况。
我首先提供一个简短示例,仅显示使用IFileOpenDialog
进行多项选择的API调用序列。
为简洁起见,以下代码根本没有错误处理!您应该检查每个COM API调用的HRESULT是否失败,我将在完整的示例中显示。
// Prepare the file open dialog.
CComPtr<IFileOpenDialog> dlg;
dlg.CoCreateInstance( CLSID_FileOpenDialog );
dlg->SetOptions( fos | FOS_ALLOWMULTISELECT );
// Show the file open dialog.
dlg->Show( hwndOwner );
if( hr == S_OK ) // If user clicked OK button...
{
CComPtr<IShellItemArray> items;
dlg->GetResults( &items );
DWORD numItems = 0;
items->GetCount( &numItems );
// Loop over all files selected by the user.
for( DWORD i = 0; i < numItems; ++i )
{
CComPtr<IShellItem> item;
items->GetItemAt( i, &item );
CComHeapPtr<WCHAR> path;
item->GetDisplayName( SIGDN_FILESYSPATH, &path );
std::wcout << std::wstring( path ) << std::endl;
}
}
以下代码显示了如何使用IFileOpenDialog
以及如何弥合C风格错误报告(HRESULT)与C ++执行方式(例外)之间的差距。
首先,我们定义一个包含ShowFileOpenDialog()
的函数IFileOpenDialog
,使其更易于使用。它返回std::vector
,其中包含用户选择的文件的绝对路径。如果用户单击“取消”,则向量将为空。如果出现任何错误,则会引发std::system_error
异常。
#include <atlbase.h>
#include <atlcom.h> // CComHeapPtr
#include <atlcomcli.h> // CComPtr
#include <Shobjidl.h> // IFileOpenDialog
#include <system_error>
#include <vector>
#include <string>
void ThrowOnFail( HRESULT hr, char const* reason )
{
if( FAILED(hr) )
throw std::system_error( hr, std::system_category(), std::string("Could not ") + reason );
}
std::vector<std::wstring> ShowFileOpenDialog(
HWND hwndOwner, const std::vector<COMDLG_FILTERSPEC>& filter = {},
FILEOPENDIALOGOPTIONS options = 0 )
{
// Using CComPtr to automatically call IFileOpenDialog::Release() when scope ends.
CComPtr<IFileOpenDialog> dlg;
ThrowOnFail( dlg.CoCreateInstance( CLSID_FileOpenDialog ), "instanciate IFileOpenDialog" );
if( !filter.empty() )
ThrowOnFail( dlg->SetFileTypes( filter.size(), filter.data() ), "set filetypes filter" );
ThrowOnFail( dlg->SetOptions( options ), "set options" );
HRESULT hr = dlg->Show( hwndOwner );
if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
return {};
ThrowOnFail( hr, "show IFileOpenDialog");
CComPtr<IShellItemArray> items;
ThrowOnFail( dlg->GetResults( &items ), "get results" );
DWORD numItems = 0;
ThrowOnFail( items->GetCount( &numItems ), "get result count" );
std::vector<std::wstring> result;
result.reserve( numItems );
for( DWORD i = 0; i < numItems; ++i )
{
CComPtr<IShellItem> item;
ThrowOnFail( items->GetItemAt( i, &item ), "get result item" );
// Using CComHeapPtr to automatically call ::CoTaskMemFree() when scope ends.
CComHeapPtr<WCHAR> path;
ThrowOnFail( item->GetDisplayName( SIGDN_FILESYSPATH, &path ), "get result item display name" );
// Construct std::wstring directly in the vector.
result.emplace_back( path );
}
return result;
}
如何调用此代码并处理异常:
#include <iostream>
int main()
{
::CoInitialize(0); // call once at application startup
try
{
HWND hwnd = nullptr; // In a GUI app, specify handle of parent window instead.
auto paths = ShowFileOpenDialog( hwnd, {{ L"Bitmap Files (*.bmp)", L"*.bmp" }},
FOS_ALLOWMULTISELECT );
if( paths.empty() )
{
// Cancel button clicked.
std::cout << "No file(s) selected.\n";
}
else
{
// OK button clicked.
for( const auto& path : paths )
std::wcout << path << L"\n";
}
}
catch( std::system_error& e )
{
std::cout
<< "Could not show 'file open dialog'."
<< "\n\nCause: " << e.what()
<< "\nError code: " << e.code() << "\n";
}
::CoUninitialize(); // match call of ::CoInitialize()
return 0;
}
答案 2 :(得分:0)
您犯的最大错误是将ANSI和Unicode混合在一起。您正在使用char[]
缓冲区并将它们输入到LPWSTR
指针,以便将它们分配到OPENFILENAME
字段。由于您使用的是TCHAR
版本的API,这意味着您的项目是针对Unicode编译的,而不是针对ANSI编译的。因此,API期望Unicode缓冲区,并将输出Unicode字符串。这也意味着您告诉API 两次您的缓冲区分配的空间可用于接收字符,因为您要将ofn.nMaxFile
和ofn.nMaxFileTitle
字段设置为字节计算而不是字符计数。所以你可以导致缓冲区溢出。
您不能仅仅将8位缓冲区强制转换为16位数据类型。您必须首先使用正确的缓冲区数据类型,并摆脱类型转换。在这种情况下,这意味着使用WCHAR
/ wchar_t
(或至少TCHAR
)缓冲区而不是char
缓冲区。但是,由于您在函数参数中使用char
,因此应使用API的ANSI版本而不是TCHAR
/ Unicode版本。
选择多个文件时,尤其是具有长文件名的文件时,生成的字符数据很容易超出固定长度缓冲区的大小。正如OPENFILENAME
documentation所述:
lpstrFile
键入:LPTSTR用于初始化文件名编辑控件的文件名。如果不需要初始化,则此缓冲区的第一个字符必须为NULL。当
GetOpenFileName
或GetSaveFileName
函数成功返回时,此缓冲区包含驱动器指示符,路径,文件名和所选文件的扩展名。如果设置了
OFN_ALLOWMULTISELECT
标志并且用户选择了多个文件,则缓冲区包含当前目录,后跟所选文件的文件名。对于资源管理器样式的对话框,目录和文件名字符串是NULL分隔的,在最后一个文件名后面有一个额外的NULL字符。对于旧式对话框,字符串是空格分隔的,并且该函数使用带有空格的文件名的短文件名。您可以使用FindFirstFile
函数在长文件名和短文件名之间进行转换。如果用户只选择一个文件,则lpstrFile
字符串在路径和文件名之间没有分隔符。如果缓冲区太小,则函数返回
FALSE
,CommDlgExtendedError
函数返回FNERR_BUFFERTOOSMALL
。在这种情况下,lpstrFile
缓冲区的前两个字节包含所需的大小,以字节或字符为单位。nMaxFile
键入:DWORD
lpstrFile
指向的缓冲区的大小(以字符为单位)。缓冲区必须足够大,以存储路径和文件名字符串或字符串,包括终止NULL字符。 如果缓冲区太小而无法包含文件信息,GetOpenFileName
和GetSaveFileName
函数将返回FALSE
。缓冲区长度至少为256个字符。
你没有考虑到这一点。 256(更好地使用260,又名MAX_PATH
)可以选择单个文件,但可能不适合选择多个文件。如果GetOpenFileName()
因FNERR_BUFFERTOOSMALL
失败,您必须重新分配缓冲区并再次致电GetOpenFileName()
。
话虽如此,尝试更像这样的事情:
BOOL OpenBmpFiles(char **filePath, char** fileNames, HWND hwnd)
{
*filePath = NULL;
*fileNames = NULL;
size_t iMaxFileSize = MAX_PATH;
char *lpFileBuffer = (char*) malloc(iMaxFileSize);
if (!lpFileBuffer)
return FALSE;
char szFileTitle[MAX_PATH];
BOOL bResult = FALSE;
OPENFILENAMEA ofn;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Bitmap Files (*.bmp)\0*.bmp\0\0";
ofn.nFilterIndex = 1;
ofn.lpstrFile = lpFileBuffer;
ofn.nMaxFile = iMaxFileSize;
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = "Open BMP File";
ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_LONGNAMES | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
do
{
//
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of lpstrFile to initialize itself.
//
ofn.lpstrFile[0] = '\0';
// show the common dialog "Open BMP File"
if (GetOpenFileNameA(&ofn))
break;
// if cancel, exit
if (CommDlgExtendedError() != FNERR_BUFFERTOOSMALL)
goto cleanup;
// reallocate the buffer and try again
iMaxFileSize = * (WORD*) lpFileBuffer;
char *lpNewFileBuffer = (char*) realloc(lpFileBuffer, iMaxFileSize);
if (!lpNewFileBuffer)
goto cleanup;
lpFileBuffer = lpNewFileBuffer;
ofn.lpstrFile = lpFileBuffer;
ofn.nMaxFile = iMaxFileSize;
}
while (true);
if (lpFileBuffer[ofn.nFileOffset-1] != '\0')
{
MessageBox(hwnd, TEXT("Single Selection"), TEXT("Open Debug 1"), MB_OK);
// copy the single filename and make sure it is double-null terminated
size_t len = strlen(&lpFileBuffer[ofn.nFileOffset]) + 2;
*fileNames = (char*) malloc(len);
if (!*fileNames)
goto cleanup;
strncpy(*fileNames, &lpFileBuffer[ofn.nFileOffset], len);
// copy the directory path and make sure it is null terminated
lpFileBuffer[ofn.nFileOffset] = '\0';
*filePath = strdup(lpFileBuffer);
if (!*filePath)
{
free(*fileNames);
*fileNames = NULL;
goto cleanup;
}
}
else
{
MessageBox(hwnd, TEXT("Multiple Selection"), TEXT("Open Debug 2"), MB_OK);
// copy the directory path, it is already null terminated
*filePath = strdup(lpFileBuffer);
if (!*filePath)
goto cleanup;
// copy the multiple filenames, they are already double-null terminated
size_t len = (ofn.nMaxFile - ofn.nFileOffset);
*fileNames = (char*) malloc(len);
if (!*fileNames)
{
free(*filePath);
*filePath = NULL;
goto cleanup;
}
// have to use memcpy() since the filenames are null-separated
memcpy(*fileNames, &lpFileBuffer[ofn.nFileOffset], len);
}
bResult = TRUE;
cleanup:
free(lpFileBuffer);
return bResult;
}
然后你可以像这样使用它:
char *path, *filenames;
if (OpenBmpFiles(&path, &filenames, hwnd))
{
char *filename = filenames;
do
{
// use path + filename as needed...
/*
char *fullpath = (char*) malloc(strlen(path)+strlen(filename)+1);
PathCombineA(fullpath, path, filename);
doSomethingWith(fullpath);
free(fullpath);
*/
filename += (strlen(filename) + 1);
}
while (*filename != '\0');
free(path);
free(filenames);
}
UPDATE :或者,为了简化返回文件名的使用,您可以执行以下操作:
BOOL OpenBmpFiles(char** fileNames, HWND hwnd)
{
*fileNames = NULL;
size_t iMaxFileSize = MAX_PATH;
char *lpFileBuffer = (char*) malloc(iMaxFileSize);
if (!lpFileBuffer)
return FALSE;
char szFileTitle[MAX_PATH];
BOOL bResult = FALSE;
OPENFILENAMEA ofn;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Bitmap Files (*.bmp)\0*.bmp\0\0";
ofn.nFilterIndex = 1;
ofn.lpstrFile = lpFileBuffer;
ofn.nMaxFile = iMaxFileSize;
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = "Open BMP File";
ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_LONGNAMES | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
do
{
//
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of lpstrFile to initialize itself.
//
ofn.lpstrFile[0] = '\0';
// show the common dialog "Open BMP File"
if (GetOpenFileNameA(&ofn))
break;
// if cancel, exit
if (CommDlgExtendedError() != FNERR_BUFFERTOOSMALL)
goto cleanup;
// reallocate the buffer and try again
iMaxFileSize = * (WORD*) lpFileBuffer;
char *lpNewFileBuffer = (char*) realloc(lpFileBuffer, iMaxFileSize);
if (!lpNewFileBuffer)
goto cleanup;
lpFileBuffer = lpNewFileBuffer;
ofn.lpstrFile = lpFileBuffer;
ofn.nMaxFile = iMaxFileSize;
}
while (true);
if (lpFileBuffer[ofn.nFileOffset-1] != '\0')
{
MessageBox(hwnd, TEXT("Single Selection"), TEXT("Open Debug 1"), MB_OK);
// copy the single filename and make sure it is double-null terminated
size_t len = strlen(lpFileBuffer) + 2;
*fileNames = (char*) malloc(len);
if (!*fileNames)
goto cleanup;
strncpy(*fileNames, lpFileBuffer, len);
}
else
{
MessageBox(hwnd, TEXT("Multiple Selection"), TEXT("Open Debug 2"), MB_OK);
// calculate the output buffer size
char *path = lpFileBuffer;
size_t pathLen = strlen(path);
bool slashNeeded = ((path[pathLen-1] != '\\') && (path[pathLen-1] != '/'));
size_t len = 1;
char *filename = &lpFileBuffer[ofn.nFileOffset];
while (*filename != '\0')
{
int filenameLen = strlen(filename);
len += (pathLen + filenameLen + 1);
if (slashNeeded) ++len;
filename += (filenameLen + 1);
}
// copy the filenames and make sure they are double-null terminated
*fileNames = (char*) malloc(len);
if (!*fileNames)
goto cleanup;
char *out = *fileNames;
filename = &lpFileBuffer[ofn.nFileOffset];
while (*filename != '\0')
{
strncpy(out, path, pathLen);
out += pathLen;
if (slashNeeded) *out++ = '\\';
int filenameLen = strlen(filename);
strncpy(out, filename, filenameLen);
out += filenameLen;
*out++ = '\0';
filename += (filenameLen + 1);
}
*out = '\0';
}
bResult = TRUE;
cleanup:
free(lpFileBuffer);
return bResult;
}
char *filenames;
if (OpenBmpFiles(&filenames, hwnd))
{
char *filename = filenames;
do
{
// use filename as needed...
/*
doSomethingWith(filename);
*/
filename += (strlen(filename) + 1);
}
while (*filename != '\0');
free(filenames);
}