在DLL中包装FindFirstFile / FindNextFile / FindClose

时间:2014-04-12 03:31:48

标签: c++ c winapi dll

我有一个用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,或者我只是从一个不完美的解决方案列表中选择一个?

2 个答案:

答案 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在内部跟踪状态信息(但是你必须担心线程安全,重入等)并在检索完最后一个文件后释放它。但这需要调用者到达最后一个文件,而其他方法允许调用者在需要时提前完成。