【问题标题】:Wrapping FindFirstFile/FindNextFile/FindClose in a DLL在 DLL 中包装 FindFirstFile/FindNextFile/FindClose
【发布时间】:2014-05-26 09:17:00
【问题描述】:

我有一个用 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 long),但这似乎很混乱我。

我考虑过 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 表示没有更多结果时,我可以自己关闭一些句柄,但如果调用者在此之前放弃搜索,则会有一个浮动句柄保持打开状态。我非常希望我的图书馆表现得很好,并且不会到处泄漏 HANDLE,所以这已经结束了。我也不想提供ELCloseSearchHandle 函数,因为我不确定我是否可以信任调用者正确使用它。

有没有一种很好的、​​最好是单一功能的方法来包装这些 Windows API,还是我只需要从不完善的解决方案列表中选择一个?

【问题讨论】:

    标签: c++ c winapi dll


    【解决方案1】:

    这样的事情呢?

    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++ 容器。我认为......

    【讨论】:

    • 这并不能解决调用者在循环结束时需要关闭句柄的问题。这正是 OP 试图避免的场景之一。
    • 关闭什么句柄?这不是一个实际的句柄:S 我的代码中的 handle 只是一个 int(我想不出更好的名字),它是向量映射中的一个索引。它只是用于在unordered_map.
    • 我添加了一些 cmets 解释它不是一个实际的句柄,不需要关闭或删除(可以选择调用 RemoveFindFiles 手动清理未使用的 vectors)。还添加了哪些部分是“可选的”,哪些部分不是。对于 OP 来说应该是安全的。希望。
    • 如果调用者没有调用RemoveFindFiles(),那么内存不一定是“泄漏”的,因为 DLL 在卸载时会回收它,但向量仍然在浪费内存。直到重复使用。内存泄漏和句柄泄漏之间的区别相同。而且这段代码也不是线程安全的。
    • 虽然这是真的,是的,它是在 dll 卸载时收集的,你宁愿让调用者给你一个指针,你为每个字符串分配内存,最后仍然必须记住全部免费?或者你宁愿有某种安全网。它不像 RAII 有任何更好的选择。如果您真的需要线程安全,那么是什么阻止您添加 std::lock_guard 来搜索、添加和删除。你正在寻找可以抱怨的事情。它是C.. OP 只需要记住在有很多向量或不再需要向量时调用 remove
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-19
      • 2013-07-22
      • 1970-01-01
      相关资源
      最近更新 更多