Указатели функций и неизвестное число аргументов в C++

я наткнулся на такой странный кусок кода.Представьте, что у вас есть следующий typedef:

typedef int (*MyFunctionPointer)(int param_1, int param_2);

и затем , в функции , мы пытаемся запустить функцию из DLL следующим образом:

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

проблема в том, что нет никакого способа узнать, если функции, чей адрес мы получили с помощью функции LoadLibrary принимает два целочисленных аргументов.Имя dll предоставляется пользователем во время выполнения, затем перечислены имена экспортируемых функций и пользователь выбирает тот, который нужно протестировать (опять же, во время выполнения :S:S ). Итак, выполняя вызов функции в последней строке, разве мы не открываем дверь для возможного повреждения стека? Я знаю, что это компилируется, но какая ошибка времени выполнения произойдет в случае, если мы передаем неправильные аргументы функции, на которую мы указываем?

8 ответов


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

  • если соглашение о вызове отличается, неправильные значения параметров будут считаны
  • если функция действительно ожидает больше параметров, чем задано, случайные значения будут использоваться в качестве параметров (я позволю вам представить последствия, если задействованы указатели)
  • в любом случае, обратный адрес будет полная фигня, так случайный код со случайными данными будет запущен, как только функция вернется.

в двух словах: Undefined behavior


Я боюсь, что нет способа узнать-программист должен знать прототип заранее при получении указателя функции и его использовании.

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


если это функция _ _ stdcall и они оставили имя mangling нетронутым (оба больших ifs, но, конечно, возможно, тем не менее) имя будет иметь @nn в конце, где nn - число. Это число-количество байтов, которое функция ожидает в качестве аргументов и очистит стек до его возвращения.

Итак, если это серьезная проблема, вы можете посмотреть на необработанное имя функции и проверить, что объем данных, которые вы помещаете на стек соответствует количеству данных, которые он собирается очистить стек.

обратите внимание, что это все еще только защита от Мерфи, а не Макиавелли. При создании библиотеки DLL можно использовать файл экспорта для изменения имен функций. Это часто используется для удаления имени mangling - но я уверен, что это также позволит вам переименовать функцию от xxx@12 до xxx@16 (или что-то еще), чтобы ввести читателя в заблуждение о параметрах, которые он ожидает.

Edit: (в первую очередь в ответ на комментарий msalters): это правда, что вы не можете применить __stdcall к чему-то вроде функции-члена, но вы можете использовать его на таких вещах, как глобальные функции, независимо от того, написаны ли они на C или c++.

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


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

используя PE Explorer, например, вы можете узнать, какие функции используются и изучить дамп разборки...

надеюсь, это поможет, С уважением, Том.


Это будет либо сбой в коде DLL (так как он передал поврежденные данные), либо: я думаю, что Visual C++ добавляет код в отладочные сборки для обнаружения этого типа проблемы. Он скажет что-то вроде: "значение ESP не было сохранено при вызове функции" и укажет на код рядом с вызовом. Это помогает, Но не совсем надежно - я не думаю, что это остановит вас от неправильного, но одинакового аргумента (например. int вместо параметра char* на x86). Как говорят другие ответы, вы просто должны знать, действительно.


общего ответа нет. Стандарт предписывает, чтобы определенные исключения были брошены в определенных обстоятельствах, но помимо этого описывает, как будет выполняться соответствующая программа, и иногда говорит, что определенные нарушения должны привести к диагностике. (Здесь или там может быть что-то более конкретное, но я, конечно, не помню.)

то, что делает код, не соответствует стандарту, и поскольку есть приведение, компилятор имеет право идти вперед и делать все, что захочет программист, без жалоб. Поэтому это будет вопросом осуществления.

вы можете проверить свою документацию по реализации, но ее, вероятно, тоже нет. Вы можете экспериментировать или изучать, как выполняются вызовы функций в вашей реализации.

к сожалению, ответ, скорее всего, будет в том, что он что-то испортит, не будучи сразу очевидным.


обычно, если вы вызываете LoadLibrary и GetProcByAddrees, у вас есть документация, которая сообщает вам прототип. Еще чаще, как со всеми окнами.dll вам предоставляется файл заголовка. Хотя это приведет к ошибке, если неправильно его обычно очень легко наблюдать, а не ошибка, которая будет проникать в производство.


большинство компиляторов C / C++ заставляют вызывающего абонента настраивать стек перед вызовом,а затем перенастраивать указатель стека. Если вызываемая функция не использует аргументы указателя или ссылки, повреждение памяти не произойдет, хотя результаты будут бесполезными. И, как говорит rerun, ошибки указателя / ссылки почти всегда появляются с небольшим количеством тестирования.