![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Классы в DLL на Delphi и MSVC++ (бой продолжается)...
ПЗ © (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]
Сказал, потом напился ?)
Твоя пунктуация как всегда вне всяких похвал)