Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Классы в DLL на Delphi и MSVC++ (бой продолжается)...


Страницы:   1 2 3 4 5

ПЗ ©   (2008-09-13 21:58:00) 

Продолжаю исследовать возможности написания плагинов для 3d Studio MAX на Delphi.
Плагин сидит в DLL  в виде класса заданной структуры. 3ds загружает DLL, получает
ссылку на экземпляр класса (не COM) и вызывает его методы. Когда класс написан на MSVC
все ОК, когда на Delphi - есть сбои. Путем сравнения процессов вызова в отладчиках
установил аномалию.
Есть метод класса.
На С++:

class MyUtilityClassDesc
{
...
 virtual SClassID ClassID()
 {
return sid;
 }
}
где
struct SClassID
{
ULONG a,b;
}sid;
...
sid.a=0xFF00FF;
sid.b=0xAABBEE;

То же на Delphi:

ClassDesc=class
...
function ClassID():MClass_ID;virtual;cdecl;
end;
function ClassDesc.ClassID():MClass_ID;cdecl;

begin
 Result:=m_id;
end;
где
MClass_ID=record
 a,b:LongWord;
end;
...
m_id:MClass_ID;
m_id.a:=$FF00FF;
m_id.b:=$AABBEE;

Аномалия в том, что после возврата из ClassID() на С++, указатель стека смщается, а на Delphi - нет!
На С++:
Перед вызовом
ESP = 0х0012DD34
После вызова
ESP = 0х0012DD38

На Delphi
Перед вызовом
ESP = $0012DD34
После вызова
ESP = $0012DD34 <=!!!

Получается, что на С++ вершина стека оказывается непосредственно на возвращенном результате, а на Delphi - ПОСЛЕ результата функции(метода), что потом приводит к AV!

Знатоки, научите, как такое может быть, если вызывающий код один и тот же и результат возвращается корректно?


ПЗ   (2008-09-16 20:53:00) [41]

У меня три вопроса:
1. Вчера выкладывал шот той же функции ClassID() на VС без дебаг-опций:

mov ecx, dword ptr [sid]
mov [eax], dword ptr [esp+4]
mov dword ptr [eax], ecx
mov edx, dword ptr [sid+4]
mov dword ptr [eax+4],edx
ret 4

Здесь нет этих наворотов с  
push       eax  
lea         ecx,[ebp-8]
Это нормально?

2. А нельзя внутри этого же метода на делфи тупо увеличить add esp,4 ? this все равно внутри не используется.

3. У меня ведь куча методов в этом классе. Как определить, какие из них thiscall, а какие – cdecl ? Вот, например, перед ClassID() вызывается аналогичный SuperClassID(), который возвращает число. Он работает одинаково хорошо что на С что на Делфи и стек никуда не смещается. Есть некоторые другие методы, к которым у меня нет вопросов. Неужели методом тыка трассировать все?


oxffff   (2008-09-16 15:55:00) [42]

Например вот функция ты передаешь ей sdtcall функцию,  оно возвращает обертку для преобразования из __thiscall вызова.
Пользуясь ею генерируешь свою таблицу VMT у объекта обертки реального объекта. Особо не тестил, так что прошу сильно не бить.

function Generate__ThisCallStub(StdCallee:pointer):pointer;
Label StubStartLabel,StubEndLabel;
var  StubStart:pointer;
    StubSize:DWORD;
    fixOffset:DWORD;
    OffsetValue:DWORD;
begin
try
result:=nil;
 asm
 mov StubStart,offset StubStartLabel;
 mov StubSize,offset StubEndLabel;
 sub StubSize,offset StubStartLabel;
 mov fixOffset,Offset StubEndLabel;
 sub fixOffset,offset StubStartLabel;
 end;
GetMem(result,StubSize);
try
CopyMemory(result,StubStart,StubSize);
OffsetValue:=DWORD(StdCallee)-DWORD(result)-fixOffset;
asm
mov eax,fixOffset
lea eax,[eax-4];
add eax,result;
mov edx,OffsetValue;
mov [eax],edx;
end;
except
FreeMem(result);
raise;
end;
except
end;
exit;
///////////////////////////////////////////////////////////////////////////////
//Generic code
///////////////////////////////////////////////////////////////////////////////
asm
StubStartLabel:
push [esp];
mov ecx,[ecx+4];
mov [esp+4],ecx;
DB $E9;
DD $0;
StubEndLabel:
end;
end


oxffff   (2008-09-16 15:09:00) [43]


> Slym ©   (16.09.08 14:04) [47]


Ты должен передать С++ коду указатель на экземпляр  класса, с коим код С++ будет взаимодейстовать вызывая его виртуальные методы через VMT с __thiscall.
Механизм вызова у них идентичен, за исключение того что в С++ VMT плавающий.
Здесь нет речи про Inline. Здесь косвенный вызов, нет прямой линковки.

function GetClassID:TClassID;stdcall;
var this:PClassDesc;
begin
asm
  mov this, ecx
end;
result:=this^.FClassID;
end;

Теперь я понял, что это у тебя обычный метод. Однако в твоем случае нет гарантии того, что в ecx то, что должно быть.
+ нужно писать код вручную.
Я предлагаю этот процесс автоматизировать. То есть автор пишет методы в delphi  c stdcall, а обертка транслирует вызовы __thiscall в stdcall.


Slym   (2008-09-16 14:04:00) [44]

oxffff ©   (16.09.08 10:32) [46]
Код вызывается из С++ с __thiscall calling convention. А не из delphi

этим я эмулирую thiscall -
asm
  lea ecx, ClassDesc
end;
ClassID:=ClassDesc.ClassID;

oxffff ©   (16.09.08 10:32) [46]
При stdcall функция очистит на стек на 4 больше, чем при __thiscall

чистит ровно настолькоже т.к. это не метод класса и нет скрытого параметра self - чистит только 4 (указатель на результат)
oxffff ©   (16.09.08 10:32) [46]
Ну и главное обращение по VMT косвенное

не факт что VMT ждет, а скорее всего на структуру с фиксированными точками входа в процедуры, т.к. с++ ничего низнает о дельфевой VMT
oxffff ©   (16.09.08 10:32) [46]
Зачем писать код для каждой функции вручную, если можно его генерировать на ходу автоматически

Си позволяет инлайнить код объявленных методов в месте обявления, а дельфи при определении записи (record) нет... тат нужно типа хелперов как в старших дельфях, но в 7 их нету


oxffff   (2008-09-16 10:32:00) [45]


> Slym ©   (16.09.08 10:13) [45]


Ты кажется не в теме. Точнее не понял тему.

Код вызывается из С++ с __thiscall calling convention. А не из delphi

Во вторых твой код не корректный.

При stdcall функция очистит на стек на 4 больше, чем при __thiscall.

Ну и главное обращение по VMT косвенное, то если ты передаш в С++ код PClassDesc. То код свалиться, поскольку ждет вместо

IsPublic: function:LongBool;stdcall;

указатель на VMT.

P.S. Зачем писать код для каждой функции вручную, если можно его генерировать на ходу автоматически. Представь если у автора таких функций >10.



Далее


Slym   (2008-09-16 10:13:00) [46]

oxffff ©   (16.09.08 0:14) [43]
Подсказка тебе просто необходимо генерировать код на лету.

проще сделать так

type
 TClassID=packed record
  a,b:LongWord;
 end;
 PClassDesc=^TClassDesc;
 TClassDesc=packed record
  IsPublic: function:LongBool;stdcall;
  Create: function(const Loading:LongBool):pointer;stdcall;
  ClassName:function :PChar;stdcall;
  SuperClassID:function:TClassID;stdcall;
  ClassID:function:TClassID;stdcall;
  Category:function :PChar;stdcall;
  FClassID:TClassID;
  OtherData:Pointer;

end;

function GetClassID:TClassID;stdcall;
var this:PClassDesc;
begin
  asm
   mov this, ecx
 end;

 result:=this^.FClassID;
end;

function LibClassDesc(const i:integer):TClassDesc;
begin
ZeroMemory(@result,SizeOf(result));
result.FClassID.a:=$FF00FF;
result.FClassID.b:=$AABBEE;

result.ClassID:=@GetClassID;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
ClassDesc:TClassDesc;
ClassID:TClassID;
begin
ClassDesc:=LibClassDesc(1);
asm
   lea ecx, ClassDesc
end;
ClassID:=ClassDesc.ClassID;
if (ClassID.a<>$FF00FF) or (ClassID.b<>$AABBEE) then raise exception.Create('Bad ClassID');
end;


Германн   (2008-09-16 00:28:00) [47]

<offtop>
Термин "плагин" в моих глазах упал ещё ниже. Плинтус уже где-то рядом. :)
</offtop>


oxffff   (2008-09-16 00:14:00) [48]


> Процесс можно автоматизировать.
> Это оставляем тебе в качестве домашнего задания. :)


Подсказка тебе просто необходимо генерировать код на лету.
Заменяя только смещение в jmp stdcallMethodImpl на лету.
То есть берешь Delphi объект и проходишь всю его VMT с генерацией кода.


oxffff   (2008-09-16 00:06:00) [49]

Для автора.
Если тебе все же необходимо делать этот вызов __thiscall для delphi нужно немного сообразительности. Идея очень простая. Тебе нужно в delphi сделать фиктивный объект транслятор __thiscall в __stdcall в delphi.  

То есть создаешь некий объект вида

thisCallTostdcallWrapper=record
VMT:PVMT;
instance:DelphiInstance;
end;

VMT=array[1..MethodsCount] of pointer;
pVMT=^VMT;

где каждый указатель это указатель на код.

asm
push [esp];
mov ecx,[ecx+4];
mov [esp+4],ecx;
jmp stdcallMethodImpl;
end;

Процесс можно автоматизировать.
Это оставляем тебе в качестве домашнего задания. :)


oxffff   (2008-09-15 23:45:00) [50]

У автора темы вызов __thiscall
The __thiscall calling convention is used on member functions and is the default calling convention used by C++ member functions that do not use variable arguments. Under __thiscall, the callee cleans the stack, which is impossible for vararg functions. Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.


oxffff   (2008-09-15 23:39:00) [51]

Так, не поленился открыть VC.
ecx содержит указатель на экземпляр.
mov  dword ptr [ebp-8],ecx
То просто сохраняем в локальной переменной указатель на экземпляр.
То есть пароноидальное сохранение.

Отсюда вывод.

Автор у тебя соглашение не cdecl.

Вот твой вызов

lea         eax,[ebp+FFFFFF28h]  <- ccылка для результата
push       eax  
lea         ecx,[ebp-8]               <- указатель на экземпляр  
call        004111FE

Вот вызов для cdecl

virtual  SClassID __cdeclClassID()

lea         eax,[ebp+FFFFFF28h]
push        eax  
lea         ecx,[ebp-8]
push        ecx  
call        00411203
add         esp,8


Сергей М.   (2008-09-15 22:09:00) [52]


> А какой смысл автору искажать код?


А ты собери С-проект с выключенными опциями отладки, тогда будет понятно где "шняга" - то ли у тебя, то ли у 0xfffd )


> Аномалия происходит при переходе от ret 4 к вызывающему
> коду


Ты русский понимаешь ?

тебе ж говорят - проблема в передаче self)

Ты хоть из штанов выпрыгни, но Делфи будетего передавать в данном контексте. При Pascal fastcall - через eax, при cdecl, stdcall, safecall - через стек первым параметром (с соответствующей стороны).


ПЗ   (2008-09-15 21:48:00) [53]

Млин, оно ссылки не принимает :-(

Вчера штудировал MSDN и наткнулсяна такую штуку как __inline. Не тут ли собака порылась? Ведь в С++ ClassID() именно inline, а в делфях инлайна нет. Там что-то писалось про ESP, но как-то туманно. По-дефолту в настройках компилера VC у меня стоит _cdecl.При явном указании _stdcall метод не выполняется, вызывая ошибку при попытке записи по адресу [eax].

PS. Еще раз повторю – здесь не интерфейсы, а КЛАССЫ (без AddRef, GetInterface…). И макс получает их экземпляры по адресу, возвращаемому специальной функцией из DLL. Это не я придумал, а Autodesk. Кто не верит – могу выложить справку к SDK. Метод ув. Slym интересен, но с наскока у меня он не заработал. Если с классами не выйдет, буду пробовать этот вариант.


ПЗ   (2008-09-15 21:47:00) [54]

>Автор что-то напутал, наверно ..
А какой смысл автору искажать код? Автору нужно решение, а не спор. Вот снимок окна отладчика на С:
До вызова хттп://s61.radikal.ru/i171/0809/98/978bb95720ec.jpg
Внутри ClassID() хттп://i079.radikal.ru/0809/a3/a9e2f90a384d.jpg
После возврата  хттп://s42.radikal.ru/i098/0809/79/5af60beaf3cd.jpg
Тот же метод, скомпилированный в режиме release: хттп://s48.radikal.ru/i122/0809/80/1ba33df2eed9.jpg

На Делфи:
До вызова - хттп://s44.radikal.ru/i103/0809/31/f0dc87123522.jpg
Внутри - хттп://s56.radikal.ru/i154/0809/24/6b7eb6b27488.jpg
После - хттп://s50.radikal.ru/i128/0809/ba/a20d64593b82.jpg


ПЗ   (2008-09-15 21:45:00) [55]

>Сергей М.
>Ну а где у тебя реализация этой функции ?
Реализация функции была приведена в моем посте №1.

>Покажи конкретную строчку дельфийского кода, при исполнении которой происходит >эта самая "аномалия" ..
Не покажу. Аномалия происходит при переходе от ret 4 к вызывающему коду (call edx). Сама функция нормально выполняется и на С и на Delphi. Но из-за неправильного ESP на Delphi, креш происходит впоследствии. Результат в обоих случаях возвращается через стек – могу дампы выложить. Причем, возвращается не адрес структуры,а сами ее значения (a,b).


ПЗ   (2008-09-15 21:43:00) [56]

чего-то не отправляется


Сергей М.   (2008-09-15 20:20:00) [57]


> oxffff ©   (15.09.08 19:36) [32]
> Вы писали компилятор VC?


Разве я болен на всю голову ?)
Нет, конечно)


> Тем не менее он так делает


Значит, болен точно не я)


> Зачем это делается - XЗ.


Ну мало ли народу, начинающего раб.день с большого бодуна)..

Тот кто это писал, возможно, с него и начал раб.день, увековеченный ныне вот в таком "хз"-коде))


Anatoly Podgoretsky   (2008-09-15 19:59:00) [58]

> Сергей М.  (15.09.2008 18:51:31)  [31]

Это джентельмены удачи.


oxffff   (2008-09-15 19:36:00) [59]


> Сергей М. ©   (15.09.08 15:44) [29]


Вы писали компилятор VC?
:)
Тем не менее он так делает.

Можете здесь ознакомиться с комментариями.

http://www.experts-exchange.com/Programming/Languages/CPP/Q_20722695.html

Что касается конкретно строки.
mov   dword ptr [ebp-8],ecx


Зачем это делается - XЗ.


Сергей М.   (2008-09-15 18:51:00) [60]


> Anatoly Podgoretsky ©   (15.09.08 16:33) [30]


Сказал, потом напился ?)

Твоя пунктуация как всегда вне всяких похвал)


Страницы:   1 2 3 4 5

Наверх

Титульная страница Поиск, карта сайта Написать письмо