Как правильно передать данный параметр vararg другой функции? - PullRequest
2 голосов
/ 03 июля 2019

Я начал работу над системой ловушек / событий в C ++.Предполагается, что эта система обрабатывает все виды событий, о которых сообщают другие части приложения.

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

Как правило, это должно выглядеть так:

CHookReturn* bInitializationStatus = Hook::Run("Initialize", gGame);
CHookReturn* bThinkSuccessful = Hook::Run("Think");



Однако я столкнулся с одной проблемой.Я настроил его таким образом, чтобы функция Run в пространстве имен Hook, вызывающая функцию Run структуры CHookData_t, должна была передавать varargs.Я не мог найти другой путь.Вот как это закончилось:

union CHookReturn
{
    const char* m_pszValue;
    int m_iValue;
    float m_flValue;
    double m_dlValue;
    bool m_bValue;
};

struct CHookData_t
{
    virtual void Run(CHookReturn* ret, ...) = 0;
};

namespace Hook
{
    std::unordered_map<const char*, std::unordered_map<const char*, CHookData_t*>> umHookList;

    bool Add(const char*, const char*, CHookData_t*);
    bool Exists(const char*, const char*);
    bool Remove(const char*, const char*);
    int Count(const char*);
    CHookReturn* Run(const char*, ...);
};

Сегмент файла CPP функции Hook :: Run:

CHookReturn* Hook::Run(const char* eventName, ...)
{
    // FIXME: Look into alternative execution.
    // This code seems more like a workaround
    // than what I originally wanted it to be.

    int count = Hook::Count(eventName);
    CHookReturn* returnValues = new CHookReturn[count];
    int c = 0;

    unordered_map<const char*, CHookData_t*>::iterator itr;
    unordered_map<const char*, CHookData_t*> res;
    res = umHookList.at(eventName);

    va_list valist;
    void* args;
    va_copy(args, valist);

    for (itr = res.begin(); itr != res.end(); itr++)
    {
        CHookReturn returnData;
        itr->second->Run(&returnData, args);

        returnValues[c] = returnData;
        ++c;
    }

    return returnValues;
}

Приведенный выше код вызывает два предупреждения, которые заставляют меня задаться вопросом, если этохорошая идея, чтобы выполнить это таким образом, а также есть ли какие-либо альтернативы, которые я должен рассмотреть.

Полученные предупреждения были:
Warning C6001 Using uninitialized memory 'valist'.
Warning C6386 Buffer overrun while writing to 'returnValues': the writable size is 'count*8' bytes, but '16' bytes might be written.

Есть ли лучший способ сделать это?

1 Ответ

1 голос
/ 04 июля 2019

Исправление кода с помощью va_list:

struct CHookData_t
{
    virtual ~CHookData_t() {}
    virtual void Run(CHookReturn* ret, va_list arg) = 0;
};

namespace Hook
{
    using HooksList = std::unordered_map<
         std::string, 
         std::unordered_map<std::string, std::unique_ptr<CHookData_t>>;

    HooksList umHookList;
    ...
}

std::vector<CHookReturn> Hook::Run(const std::string& eventName, ....)
{
    va_list valist;
    va_start(valist, eventName);

    auto result = Hook::RunVarg(eventName, valist);

    va_end(valist);

    return result;
}

std::vector<CHookReturn> Hook::RunVarg(const std::string& eventName, va_list arg)
{
    int count = Hook::Count(eventName);
    std::vector<CHookReturn> returnValues(count);

    size_t c = 0;
    for (auto& item : umHookList.at(eventName))
    {
        va_list arg_copy;
        va_copy(arg_copy, arg);
        item.second->Run(&returnValues[c], arg_copy);
        va_end(arg_copy);
        ++c;
    }

    return returnValues;
}

Я понятия не имею, на что указывают аргументы Hook::Run, на что указывает va_list, поэтому я не могу предоставить вам хорошее решение C ++.

Обратите внимание, что va_copy требуется внутри цикла, так как некоторые компиляторы (не помню, какой, возможно, msvc) va_list ведут себя как указатель, и чтение аргументов из них будет влиять на каждую итерацию. На других компиляторах va_list ведет себя как значение, а va_copy ничего не меняет.

offtopic : ваш код слишком много C, вы не должны использовать const char*, но std::string или std::string_view, если вы используете C ++ 17, вместо va_args это будет лучше использовать шаблон переменной или std::initializer_list, избегать необработанных указателей в пользу std::unique_ptr и std::shared_ptr. Я немного подправил твой код, чтобы покрыть это.
Кроме того, Hook не должен быть namespace, по виду функций и переменных, которые он содержит, он должен быть классом, поэтому вы должны исправить это тоже.

...