Обертывание FindFirstFile / FindNextFile / FindClose в DLL

0

У меня есть DLL, написанная в C++, которая обертывает FindFirstFile/ FindNextFile/ FindClose для предоставления функции поиска файлов:

std::vector<std::wstring> ELFindFilesInFolder(std::wstring folder, std::wstring fileMask = TEXT(""), bool fullPath = false);

Эта функция возвращает std::vector содержащий список имен файлов в данной папке, соответствующих заданной файловой маске. Все идет нормально; функция работает должным образом.

Мне нужно написать обертку C вокруг этой библиотеки, хотя, потому что я не могу передать вектор через границы DLL. Это приводит к прекращению головных болей.

Первоначально я думал, что просто создам функцию, которая получит двухмерный массив wchar_t, изменит его, чтобы он содержал список имен файлов, и вернул его:

bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t* filesBuffer[], size_t* filesBufferSize);

Однако это оказалось плохой идеей, так как по крайней мере второй размер измерения должен быть известен во время компиляции. Полагаю, я мог просто заставить вызывающего сделать второй размер 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 предоставили бы этот дескриптор поиска, что ELFindNextFileInFolder бы ELFindNextFileInFolder где остановился последний вызов: FindNextFile просто получит сохраненный дескриптор поиска. Однако такие ручки должны быть закрыты с помощью FindClose, и C, похоже, не имеет концепции C++ умного указателя, поэтому я не могу гарантировать, что searchToken когда-либо будет закрыт. Я могу закрыть некоторые из HANDLEs, когда FindNextFile указывает, что результатов больше нет, но если вызывающий абонент откажется от поиска до этой точки, появится плавающая РУЧКА, оставшаяся открытой. Мне очень хотелось бы, чтобы моя библиотека была хорошо себя вести, а не повредила РУЧКИ повсюду, так что это не так. Я также предпочел бы не предоставлять функцию ELCloseSearchHandle, так как я не уверен, что могу доверять вызывающим абонентам, чтобы использовать их правильно.

Есть ли хороший, желательно однофункциональный способ обернуть эти API Windows, или мне просто нужно выбрать один из списка несовершенных решений?

Теги:
dll
winapi

2 ответа

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++. Я думаю..

  • 0
    Это не решает проблему того, что вызывающему абоненту необходимо закрывать дескриптор после завершения цикла. Это как раз один из сценариев, которых ОП пытался избежать.
  • 0
    Закрыть какую ручку? Это не настоящий дескриптор: S Этот handle в моем коде - просто int (я не могу придумать лучшего имени), который является индексом на карте векторов. Он просто используется для поиска в unordered_map .
Показать ещё 4 комментария
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 не отслеживает информацию о состоянии внутри (но тогда вам нужно беспокоиться о безопасности потоков, повторной установке и т.д.) И освобождает ее после получения последнего файла. Но для этого требуется, чтобы вызывающий абонент достиг последнего файла, тогда как другие подходы позволяют вызывающему абоненту закончить раньше, если это необходимо.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню