我有一个用C ++编写的DLL,它包装FindFirstFile
/ FindNextFile
/ FindClose
来提供文件搜索功能:
std::vector<std::wstring> ELFindFilesInFolder(std::wstring folder, std::wstring fileMask = TEXT(""), bool fullPath = false);
此函数返回std::vector
,其中包含与给定文件掩码匹配的给定文件夹中的文件名列表。到现在为止还挺好;该功能按预期工作。
我需要在这个库周围写一个C包装器,因为I can't pass a vector across DLL boundaries。这导致头痛无穷。
我最初认为我只会设置一个函数来接收一个二维wchar_t
数组,修改它以包含文件名列表,然后返回它:
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t* filesBuffer[], size_t* filesBufferSize);
然而,这被证明是一个坏主意,at least the second dimension's size has to be known at compile-time。我想我可以只是强制调用者创建第二个维MAX_PATH
(所以函数会收到一个可变长度的文件名缓冲区列表,每个MAX_PATH
长),但是这对我来说似乎很混乱。
我认为是Windows API风格的包装器:
bool ELFindNextFileInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t* fileBuffer, size_t* fileBufferSize, HANDLE* searchToken);
这将执行搜索,返回找到的第一个文件名,并保存FindFirstFile
提供的搜索句柄。未来对ELFindNextFileInFolder
的调用将提供此搜索句柄,从而可以轻松获取上次调用停止的位置:FindNextFile
只会获取已保存的搜索句柄。但是,这些句柄需要通过FindClose
关闭,而C似乎没有智能指针的C ++概念,因此我不能保证searchToken
将被关闭。当FindNextFile
表示没有更多结果时,我可以自己关闭一些HANDLEs,但如果调用者在该点之前放弃搜索,则会有一个浮动HANDLE保持打开状态。我非常喜欢我的图书馆表现良好而且不会随处泄漏HANDLEs,所以这就好了。我也不想提供ELCloseSearchHandle
函数,因为我不确定我是否可以信任调用者正确使用它。
是否有一个很好的,最好是单功能的方式来包装这些Windows API,或者我只是从一个不完美的解决方案列表中选择一个?
答案 0 :(得分:2)
这样的事情怎么样?
在DLL module
:
#include <windows.h>
#include <vector>
#include <unordered_map>
unsigned int global_file_count; //just a counter..
std::unordered_map<unsigned int, std::vector<std::wstring>> global_file_holder; //holds vectors of strings for us.
/** Example file finder C++ code (not exported) **/
std::vector<std::wstring> Find_Files(std::wstring FileName)
{
std::vector<std::wstring> Result;
WIN32_FIND_DATAW hFound = {0};
HANDLE hFile = FindFirstFileW(FileName.c_str(), &hFound);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
Result.emplace_back(hFound.cFileName);
} while(FindNextFileW(hFile, &hFound));
}
FindClose(hFile);
return Result;
}
/** C Export **/
extern "C" __declspec(dllexport) unsigned int GetFindFiles(const wchar_t* FileName)
{
global_file_holder.insert(std::make_pair(++global_file_count, Find_Files(FileName)));
return global_file_count;
}
/** C Export **/
extern "C" __declspec(dllexport) int RemoveFindFiles(unsigned int handle)
{
auto it = global_file_holder.find(handle);
if (it != global_file_holder.end())
{
global_file_holder.erase(it);
return 1;
}
return 0;
}
/** C Export **/
extern "C" __declspec(dllexport) const wchar_t* File_Get(unsigned int handle, unsigned int index, unsigned int* len)
{
auto& ref = global_file_holder.find(handle)->second;
if (ref.size() > index)
{
*len = ref[index].size();
return ref[index].c_str();
}
*len = 0;
return nullptr;
}
/** C Export (really crappy lol.. maybe clear and reset is better) **/
extern "C" __declspec(dllexport) void File_ResetReferenceCount()
{
global_file_count = 0;
//global_file_holder.clear();
}
extern "C" __declspec(dllexport) bool __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, void* lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hinstDLL);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return true;
}
然后在C code
中你可以使用它:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
HMODULE module = LoadLibrary("CModule.dll");
if (module)
{
unsigned int (__cdecl *GetFindFiles)(const wchar_t* FileName) = (void*)GetProcAddress(module, "GetFindFiles");
int (__cdecl *RemoveFindFiles)(unsigned int handle) = (void*)GetProcAddress(module, "RemoveFindFiles");
const wchar_t* (__cdecl *File_Get)(unsigned int handle, unsigned int index, unsigned int* len) = (void*)GetProcAddress(module, "File_Get");
void (__cdecl *File_ResetReferenceCount)() = (void*)GetProcAddress(module, "File_ResetReferenceCount");
unsigned int index = 0, len = 0;
const wchar_t* file_name = NULL;
unsigned int handle = GetFindFiles(L"C:/Modules/*.dll"); //not an actual handle!
while((file_name = File_Get(handle, index++, &len)) != NULL)
{
if (len)
{
wprintf(L"%s\n", file_name);
}
}
RemoveFindFiles(handle); //Optional..
File_ResetReferenceCount(); //Optional..
/** The above two functions marked optional only need to be called
if you used FindFiles a LOT! Why? Because you'd be having a ton
of vectors not in use. Not calling it has no "leaks" or "bad side-effects".
Over time it may. (example is having 500+ (large) vectors of large strings) **/
FreeLibrary(module);
}
return 0;
}
说实话似乎有点脏,但我真的不知道任何“惊人”的做法。这就是我的方式..大部分工作都是在C ++方面完成的,你不必担心泄漏。即使导出一个函数来清除map
也会很好。
如果将C导出添加到模板类然后导出其中的每一个都会更好..这会使它对大多数C ++容器都可以重用..我想...
答案 1 :(得分:1)
将wchar_t* filesBuffer[]
更改为wchar_t** *filesBuffer
,然后调用者可以传入指向wchar_t**
变量的指针来接收数组,并且在编译时不需要知道任何有关任何边界的信息。对于数组本身,DLL可以分配一个wchar_t*
指针的一维数组,指向以空字符结尾的字符串。这样,您的size_t* filesBufferSize
参数仍然相关 - 它接收数组中的字符串数。
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t** *filesBuffer, size_t* filesBufferSize);
wchar_t **files;
size_t numFiles;
if (ELFindFilesInFolder(..., &files, &numFiles))
{
for(size_t i = 0; i < numFiles; ++i)
{
// use files[i] as needed ...
}
// pass files back to DLL to be freed ...
}
另一种选择是做类似于WM_DROPFILES
的事情。让ELFindFilesInFolder()
返回一个指向内部列表的不透明指针,然后公开一个单独的函数,该函数可以检索该列表中给定索引处的文件名。
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, void** filesBuffer, size_t* filesBufferSize);
bool ELGetFile(const wchar_t* fileName, size_t fileNameSize, void* filesBuffer, size_t fileIndex);
void *files;
size_t numFiles;
wchar_t fileName[MAX_PATH + 1];
if (ELFindFilesInFolder(..., &files, &numFiles))
{
for(size_t i = 0; i < numFiles; ++i)
{
ELGetFile(fileName, MAX_PATH, files, i);
// use fileName as needed ...
}
// pass files back to DLL to be freed ...
}
你这样做,DLL必须管理内存,所以你必须将某种状态信息传递给调用者,然后将其传递回DLL以便释放。在C中没有很多方法,除非DLL在内部跟踪状态信息(但是你必须担心线程安全,重入等)并在检索完最后一个文件后释放它。但这需要调用者到达最后一个文件,而其他方法允许调用者在需要时提前完成。