2016-04-04 3 views
1

Я знаю, что это обсуждалось несколько раз, но моя ситуация немного отличается.Метод динамического метода вызова C++

У меня есть сторонняя dll, экспортирующая некоторые классы. К сожалению, файл заголовка недоступен. По-прежнему можно вызвать экспортируемые функции. Но я не могу обойти передачу правильного «этого» указателя (который передается в регистре RCX).

Сначала я использую dumpbin/exports для извлечения имен функций (имена изменены, поскольку сторонняя библиотека и имена функций являются конфиденциальными).

 4873 1308 0018B380 [email protected]@[email protected]@QEBAJXZ = [email protected]@[email protected]@QEBAJXZ (public: long __cdecl ThirdPartyNamespace::ThirdPartyClass::GetId(void)const) 

Теперь API позволяет мне зарегистрировать свою функцию обратного вызова, которая получает указатель на ThirdPartyNamespace :: ThirdPartyClass (есть только вперед декларация ThirdPartyClass).

Вот как я пытаюсь вызвать ThirdPartyNamespace :: ThirdPartyClass :: GetId():

long (ThirdPartyNamespace::ThirdPartyClass::*_pFnGetId)() const; 
HMODULE hModule = GetModuleHandle("ThirdPartyDLL.dll"); 
*(FARPROC*)&_pFnGetId= GetProcAddress(hModule, "[email protected]@[email protected]@QEBAJXZ"); 

long id = (ptr->*_pFnGetId)(); 

Все выглядит нормально (то есть, если я шаг в - я действительно внутри метода ThirdPartyClass :: GetId Но. этот указатель не является хорошим Хотя PTR хорошо, и если в отладчике я вручную изменить RCX на PTR - это отлично работает, но компилятор не пропускает PTR по какой-то причине Вот разборки:.

long id = (ptr->*_pFnGetId)(); 
000000005C882362 movsxd  rax,dword ptr [rdi+30h] 
000000005C882366 test  eax,eax 
000000005C882368 jne   MyClass::MyCallback+223h (05C882373h) 
000000005C88236A movsxd  rcx,dword ptr [rdi+28h] 
000000005C88236E add   rcx,rsi 
000000005C882371 jmp   MyClass::MyCallback+240h (05C882390h) 
000000005C882373 movsxd  r8,dword ptr [rdi+2Ch] 
000000005C882377 mov   rcx,rax 
000000005C88237A mov   rax,qword ptr [r8+rsi] 
000000005C88237E movsxd  rdx,dword ptr [rax+rcx] 
000000005C882382 movsxd  rcx,dword ptr [rdi+28h] 
000000005C882386 lea   rax,[r8+rdx] 
000000005C88238A add   rcx,rax 
000000005C88238D add   rcx,rsi 
000000005C882390 call  qword ptr [rdi+20h] 
000000005C882393 mov   ebp,eax 

Перед выполнением.. эти команды, rsi содержит указатель на объект ThirdPartyC (т. е. ptr), но вместо того, чтобы напрямую передавать его в rcx, на нем выполняется некоторая арифметика, и в результате этот указатель становится совершенно неправильным.

некоторые следы, которые я не понимаю, почему компилятор делает это, как это в конечном итоге вызова ThirdPartyClass невиртуальные функции :: GetId():

000000005C88237A mov   rax,qword ptr [r8+rsi] 
    R8 0000000000000000  
    RSI 000000004C691AA0 // good pointer to ThirdPartyClass object 
    RAX 0000000008E87728 // this gets pointer to virtual functions table of ThirdPartyClass 
000000005C88237E movsxd  rdx,dword ptr [rax+rcx] 
    RAX 0000000008E87728  
    RCX FFFFFFFFFFFFFFFF  
    RDX FFFFFFFFC0F3C600 
000000005C882382 movsxd  rcx,dword ptr [rdi+28h] 
    RCX 0000000000000000  
    RDI 000000005C9BE690  
000000005C882386 lea   rax,[r8+rdx] 
    RAX FFFFFFFFC0F3C600  
    RDX FFFFFFFFC0F3C600  
    R8 0000000000000000  
000000005C88238A add   rcx,rax 
    RAX FFFFFFFFC0F3C600  
    RCX FFFFFFFFC0F3C600  
000000005C88238D add   rcx,rsi 
    RCX 000000000D5CE0A0  
    RSI 000000004C691AA0  
000000005C882390 call  qword ptr [rdi+20h] 

На мой взгляд, это должно быть так просто, как

long id = (ptr->*_pFnGetId)(); 
mov   rcx,rsi 
call  qword ptr [rdi+20h] 
mov   ebp,eax 

И если я устанавливаю RCX равным RSi до вызова QWORD PTR [RDI + 20h] возвращает мне ожидаемое значение.

Я делаю что-то совершенно не так? Спасибо заранее.

ответ

0

Хорошо, я нашел решение, по инциденту (как я уже использовал подобный подход, и она работала в несколько иной ситуации.

Решение обмануть компилятор, определяя поддельный класс и вызова метода члена по указателю , но притворяясь, что это указатель на известный класс (компилятор).

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

class FakeCall 
{ 
private: 
    FakeCall(){} 
    virtual ~FakeCall(){} 
}; 

Остальное, как в исходном коде, кроме как только мелочью, вместо вызова PTR -> * _ pFnGetId (где PTR является указателем на неизвестное, вперед объявленного класса ThirdPartyNamespace :: ThirdPartyClass), я притворяться, я звоню метод состоит в моем классе FakeCall:

FakeCall * fake = (FakeCall*)ptr; 
    long sico = (fake->*_pFnGetId)(); 

Демонтажные выглядит точно так же, как и ожидалось:

long sico = (fake->*_pFnGetSico)(); 
000000005A612096 mov   rcx,rax 
000000005A612099 call  qword ptr [r12+20h] 
000000005A61209E mov   esi,eax 

И это прекрасно работает!

Некоторые наблюдения:

  1. Метод член указатель, как я сначала подумал, не более, чем обычный указатель на функцию.
  2. Microsoft компилятор (по крайней мере, VS2008) сходит с ума, если вызывает метод участника для не определенного класса (т. Е. Только форвардное объявление имени).
+0

Хороший улов. MSVC использует указатели разного размера для функций-членов в зависимости от класса, от 8 до 24 байтов на 64-битной платформе. См. Http://www.agner.org/optimize/calling_conventions.pdf –

+0

Спасибо за ссылку! На самом деле у него есть ссылка с полным объяснением (все еще актуально с 2015 года). http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible Это действительно ум. Мне повезло, что у меня есть одно наследование в классах ThirdParty в моем случае. – Jurys