| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Visual C++/MFC/WTL > [VC++]импорт методов |
| Автор: GoldFinch 16.5.2009, 20:21 | ||||||
| Есть длл, написанная на VС++, экспортирующая методы классов (возможно сами классы). Заголовочных файлов к этой длл нет. При помощи dumpbin.exe были получены экспорты длл, при помощи lib.exe был получен .lib файл, при помощи undname.exe были получены имена и прототипы методов, при помощи IDA был восстановлен порядок методов в таблицах виртуальных методов (ВМТ) Тестовый код "UObject o; o.AddRef;" показал что метод UObject::AddRef() импортируется корректно и генерирует корректный код вызова
Теперь надо получить адрес этого метода. Допустим надо получить адрес простой импортированной функции. Пишем int foo(){return int(&ExitProcess);} получаем mov eax, [__imp__ExitProcess@4] Для метода, пробуем написать int foo(){return int(&UObject::AddRef);} получаем error C2440: приведение типов: невозможно преобразовать 'unsigned long (__stdcall UObject::* )(void)' в 'int' Не существует контекста, в котором такое преобразование возможно Пишем шаблон для преобразования
код int foo(){return int_cast(&UObject::AddRef);} компилится успешно, однако получаем адрес функции которая вызывает метод через таблицу методов
а хочется совсем не этого, хочется получить реальный адрес этого метода, т.е. [__imp_?AddRef@UObject@@UAGKXZ] как это сделать? |
| Автор: Earnest 16.5.2009, 21:27 |
| А метод случаем не виртуальный? Когда ты просишь адрес виртуального метода, то тебе компилятор подсовывает адрес именно функции-переходника, т.к. предполоагается полиморфный вызов в зависимости от this. И еще один вопрос: неужели банальный GetProcAddress не поможет (с истинным декорированным именем, конечно)? Если функция импортируется, то GetProcAddress должна возвратить ее, а метод с точки зрения линкера - та же функция... С преобразованием типа тут тоже возможны проблемы, конечно... |
| Автор: GoldFinch 16.5.2009, 21:53 | ||
| Earnest, метод виртуальный. сделать его невиртуальным невозможно, т.к. не будет соответствовать манглинг. GetProcAddress я както использовал, но потом от этого отказался вроде изза большего объема кода
проблем с преобразованием типа нет, а вот вызов выглядит не очень красиво |
| Автор: Earnest 16.5.2009, 22:14 |
| Я и не предлагала сделать его невиртуальным, просто скзала, как компилятор интерпретирует такие вызовы (и заодно указатели). GetProcAddress - это стандартный метод получения адресов функций, чего тут мудрить. А если не нравится код, напиши обертку к импортируемым классам (чтобы для клиента выглядело все как у "нормальных"). И не совсем понимаю, каким образом твой первый метод решал эту проблему (некрасивого кода вызова). Кстати, ты ведь вроде говорил, что смог построить lib-файл. Так что тогда мешает подключить билиотеку стандартным оразом? Ну, заголовочный файл напиши руками, ты ведь вроде выяснил сигнатуры... Насколько я понимаю, цель именно такая: использовать библиотеку как если бы у нее были открыт интерфейсы (т.е. lib + h)? |
| Автор: GoldFinch 17.5.2009, 00:08 |
| Earnest, я уже писал в 1м посте, что вызовы методов работают нормально, obj->met(), мне же надо получить адрес метода, не для того чтобы его вызвать, а для последующей работы с этим адресом. |
| Автор: Earnest 17.5.2009, 00:12 |
| Наверное, пропустила. В общем, адрес метода - это совсем не адрес с точки зрения компилятора и языка, так что просто &CMyObj::Method будет возвращать адрес стаба, как у тебя и получается. А вот GetProcAddress про C++ и прочий полиморфизм не в курсе, должна выдавать ровно то, что просят. Насчет длинного и некрасивого кода - как писал кто-то из гуру, любую проблему в программировании можно решить с помощью правильно спроектированных прокладок. А на C++ они к тому же почти бесплатны в смысле издержек. |
| Автор: GoldFinch 17.5.2009, 00:58 |
| Earnest, даже если забыть об издержках работы программы, появляются издержки при ее разработке: для того чтобы найти декорированное имя метода для GetProcAddress надо свернуть с MSVC и искать его в таблице экспорта длл, что несколько неудобно. Если же задуматься об издержках на импорт, то получается что при статическом импорте компилятор удаляет все неиспользуемые импорты и они импортируются поиском в таблице экспорта длл при загрузке программы. При вызове метода через указатель на объект (obj->met) адрес метода берется из ВМТ. При динамическом импорте через GetProcAddress импортирование происходит поиском в таблице экспорта длл в момент вызова GetProcAddres. Будут ли удалены лишние вызовы GetProcAddress - неизвестно. При вызове метода адрес метода берется из переменной с адресом. Однако у меня конкретная длл, которая не будет меняться за все время использования моей программы. Поэтому для импорта можно забить готовые адреса методов сразу на этапе компиляции, они не изменятся. При этом получится наибольшая скорость вызова функций, хотя не очень понятно как это будет выглядеть в коде, и как генерировать этот код из длл. |
| Автор: GoldFinch 17.5.2009, 13:11 |
| mes, вызов через ВМТ |
| Автор: mes 17.5.2009, 13:38 |
тогда то, что Вы получили в первом примере, относится к оптимизации и получено вследствие того, что компилятору известен реальный тип объекта в компил-тайм. И задача (без знания реального имени функции-реализации), имхо, не выполнима |
| Автор: GoldFinch 19.6.2009, 21:18 | ||||
ап!
попробовал написать обертку
есть какие-нибудь идеи как сделать это короче? |
| Автор: Earnest 22.6.2009, 09:39 |
| GoldFinch, а зачем? Ты код буста видел? Цель в том, чтобы был короче-яснее клиентский код, а обслуживающий... ну, по возможности. Насчет сократить, я бы попробовала так (имея в виду, что методов сильно не 3). Во-первых, таблица вместо перечисления методов, причем не только имя, но и закодированная сигнатура. Во-вторых, набор типизированных шаблонов для вызова методов по сигнатуре. Шаблоны нужно делать по числу аргументов: для 0, 1, 2, 3 и сколько там по максимуму. Методы имлементировать придется явно, как у и у тебя, но через шаблоны. И еще не поняла, зачем тебе в обертке имитировать тело настоящего класса, все эти VMT, члены-данные... Т.е. если ты можешь правильно установить VMT, но нафига вообще огород городить - напиши объявление класса с правильно расположенными виртуальными функциями. |
| Автор: GoldFinch 22.6.2009, 13:18 |
| Earnest, обертка чтобы спрятать указатели на методы, и она превращает виртуальные методы в длл в невиртуальные, это позволяет вызывать их не зная их порядок в ВМТ (хотя они в оригинале виртуальные, полиморфизм там вроде не используется) количество методов в 1 классе - до сотен (200-300), классы и длл писал не я код буста не видел, но мне надо будет скорее всего писать все это руками, по мере необходимости добавляя новые методы |
| Автор: mes 22.6.2009, 13:52 | ||||||
с певого взгляда бросилось в глаза
Зачем сразу ограничивать себя, принуждая дата-члены класса делать статическими. Этот момент интуитивно напрашивается в таком исполнении :
1. Выполнил как функцию, так как таблица принадлежит только этому классу, но если не подходит можно вернуть к "классическому" виду инициализации таблица произойдет при первом вызове функции PtrTable. Если нужно ручное управление инициализацией, то можно добавить функцию: static void Init () { PtrTable(); } или поместить пустой вызов PtrTable() в конструктор в зависимости от требований. ну а красиво скрыть это : (this->*table().Met1) я уверен Вы справитесь сами |
| Автор: GoldFinch 17.7.2009, 12:13 | ||||
| ап! почитал про static, действительно оно обладает замечательным свойством инициализировать переменные только 1 раз получается что отдельный класс PtrTable и "static PtrTable& table() { static PtrTable table; return table; }" можно заменить на статическую переменную с адесом метода прямо внутри обертки метода при этом эту переменную можно сделать классом, в конструкторе которого и импортировать метод
получается что вся информация о методе сосредоточена в одном месте, и ее можно завернуть в компактный макрос |
| Автор: GoldFinch 17.7.2009, 12:49 | ||||
а еще лучше выкинуть класс Imp_ из метода:
Добавлено через 14 минут и 30 секунд хм... только вот не получается получить адрес конструктора и деструктора =\ |
| Автор: GoldFinch 17.7.2009, 13:19 | ||
значит всетаки так:
|
| Автор: mes 17.7.2009, 14:36 | ||
добавьте в структуру Imp_ :
и отпадет надобность писать .addr |
| Автор: GoldFinch 17.7.2009, 16:33 | ||||
mes, я решил пойти еще дальше, и написать эту структуру так:
тогда ее можно использовать так:
|
| Автор: GoldFinch 17.7.2009, 17:17 | ||||
| объявляю эту тему форума репозиторием версий кода импорта методов =) перенес аргумент шаблона retT из класса в operator()
в стл или бусте есть класс template<typename T> struct какое_нибудь_имя{}; ? Добавлено через 7 минут и 15 секунд и есть ли гденить в бусте (в стл нет) аналог такой функции?
(нужна для приведения указателей на члены классов, т.к. они не приводятся приведеним типов в С-стиле) |
| Автор: mes 17.7.2009, 20:22 |
Mне кажется Вас опять относит в сторону - снова перекладываете работу по контролю типов с компилятора на свои плечи... |
| Автор: GoldFinch 18.7.2009, 10:22 | ||||||
| mes, если компилятор не умеет присваивать такие указатели, мне приходится делать это за него. переписал с использованием boost.preprocessor :
оригинальный класс:
Добавлено через 48 секунд косушки расползлись =\ |
| Автор: mes 18.7.2009, 14:09 | ||||
я так понял, что это проблема возникает при присвоении результата импорта. так ? импортом я никогда не занимался , но мне полагаю, что результат этой функции можно рассматривать, как указатель на обычную функцию. Тогда не возникнет проблем, в случае разных компиляторов (this как первый параметр или последний.) Вобщем я так вижу
Сейчас времени больше нет, приду если что не понятно поподробней опишу. |
| Автор: GoldFinch 18.7.2009, 19:07 |
| mes, там __thiscall, this передается в ecx |
| Автор: mes 18.7.2009, 20:05 |
теперь тогда понятно в чем загвоздка. Имхо импортировать такую функцию как метод не красиво,хотя бы потому что работать будет работать только если Ваш код и длл откомпилированы однотипным компилятором. Как решение предполагаю, сделать адаптер, который при вызове импортируемой функции будет создавать для нее нужный контекст. Без низкоуровневого кода здесь не обойдется (тут я умываю руки), зато все зависимости останутся в этой обертке. |
| Автор: GoldFinch 18.7.2009, 20:21 |
| mes, так и есть, нужная мне длл, и мой код компилятся одним компилятором |
| Автор: mes 18.7.2009, 20:50 | ||||
в таком случае самым простым способом будет все таки union... (и программист как гарант типо-преобразований) мне кажется только сам union было бы удобно спрятать в Вашем адаптере (Imp_) :
ну и добавить в него необходимые проверки, в частности на размер данных юниона, и на соответсвитствие нужному компилятору. |
| Автор: GoldFinch 6.8.2009, 14:28 | ||||||||
| ап. практически всю информацию о методе можно выдрать из его типа. в msvc есть интринсик _AddressOfReturnAddress() возвращающий указатель на фрейм стека поэтому возможен следующий код
где
Добавлено через 9 минут и 56 секунд с макросами это будет
вместо
и никакого BOOST_PP |
| Автор: GoldFinch 7.8.2009, 14:45 |
| а вот адрес конструктора получить нельзя =\ Добавлено @ 14:49 другое дело, что конструктор можно объявить как обычный метод, и вызывать его явно. а с деструктором проблем нет, т.к. его сигнатура всегда известна |