![]() |
| ||
|
|
Новости |
Поиск |
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!
Знатоки, научите, как такое может быть, если вызывающий код один и тот же и результат возвращается корректно?
oxffff (2008-10-04 23:07:00) [1]
> GrayFace © (04.10.08 22:18) [88]
Есть еще VirtualProtect.
oxffff (2008-10-04 23:04:00) [2]
> GrayFace © (04.10.08 22:18) [88]
Если процессор не поддерживает NX бит страницы, то увсе будет ок.
GrayFace (2008-10-04 22:18:00) [3]oxffff © (16.09.08 15:55) [49]
Data Execution Protection будет ругаться. Для таких вещей приходится использовать VirtualAlloc с указанием флага EXECUTE.
ПЗ (2008-10-04 20:24:00) [4]Все понял,в сем спасибо.
Slym (2008-10-03 04:52:00) [5]oxffff © (02.10.08 21:32) [85]
И не нужно будет задумываться.
У него научный интерес... а доводы окружающих это простой и ненаучный рефератизм
oxffff (2008-10-02 21:32:00) [6]
> ПЗ (02.10.08 21:12) [84]
С++ вызов вызывается thisCall.
virtual SClassID ClassID()
{
return sid;
}
2 параметра.
Ecx-указатель на экземпляр.
и указатель на возврат значения в стеке. Таким образом нужно вычистить только 4 байта. Поэтому ret 4.
Если бы результат мог уместиться в 4 байта, то возврат был бы через регистр.
В Delphi
TClassID =class
a,b:LongWord
end;
В Delphi в стек ложится только только self, а возврат осуществляется через регистр, поскольку умещается в регистре 4 байта.
Если ты сделаешь
TClassID =record
a,b:LongWord
end;
В Delphi в стек ложится , и указатель на результат поскольку не помещается в регистр, поэтому будет 8.
P.S. Я тебе функцию-обертку написал.
oxffff © (16.09.08 15:55) [49]
Используй ее. И не нужно будет задумываться.
ПЗ (2008-10-02 21:12:00) [7]Да не в стек результат кладется !
Кладется он по адресу, переданному 4-мя байтами, положенными в стек непосредственно перед вызовом. Эти 4 байта и удаляются с вершины стека при ret 4.
А как же тогда:
struct SClassID
{
ULONG a,b; //8 байт!
}sid;
...
sid.a=0xFF00FF;
sid.b=0xAABBEE;
virtual SClassID ClassID()
{
return sid;
}
Стек перед вызовом:
ESP = 0012DD34
0x0012DD34 38 dd 12 00
0x0012DD38 4d 88 7b 65
0x0012DD3C 20 10 00 00
Стек после вызова:
ESP= 0012DD38
0x0012DD38 ff 00 ff 00 <=sid.a
0x0012DD3C ee bb aa 00 <=sid.b
0x0012DD40 00 00 00 00
0x0012DD44 e8 82 47 0d
Отчетливо наблюдаю свои FF00FF и AABBEE на вершине стека, при этом метод имеет RET 4.
В то же время, если паскалевский вариант с stdcall перешить на возврат класса (делфи их всегда возвращает как указатели):
function ClassID():TClassID;virtual;stdcall;
где
TClassID =class
a,b:LongWord
end;
То при компиляции имеем Ret 4 (self?) и на вершине стека остается лежать 4 байта – указатель на экземпляр класса TClassID, который возвращается по ссылке.:
ESP=$0012DD38
0012DD40 00000000
0012DD3С 00001020
0012DD38 657B884D <=Указатель на экземпляр TClassID
Прошу сильно не пинать, если в очередной раз глупость спрашиваю.
oxffff (2008-10-02 19:57:00) [8]Бой окончен.
Еще с того момента как
Slym © (15.09.08 12:41) [25].
oxffff © (16.09.08 15:55) [49]
Сергей М. (2008-10-01 23:14:00) [9]
> Однако на сях thiscall в этом же случае ставит ret 4, хотя
> в стек кладет тот же 8-байтовый результат.
Да не в стек результат кладется !
Кладется он по адресу, переданному 4-мя байтами, положенными в стек непосредственно перед вызовом. Эти 4 байта и удаляются с вершины стека при ret 4.
Т.е. _thiscall - это, получается, жуткая смесь под названием "Кошмар дельфиста", состоящая из
- C-fastcall - параметр this передается через РОН ecx, подобно тому как Делфи при pascal-fastacall передает self через РОН ebx
- stdcall - прочие параметры передаются через стек справа налево по ссылке или по значению, в зависимости от контекста, балансировкой стека заведует вызываемый код
Сергей М. (2008-10-01 22:44:00) [10]
> То есть, он хочет освободить 8 байт стека, якобы выделенных
> под результат?
С какого перепугу ?
Тебе ж устали уже долдонить - в стек при этом кладется 4 байта со значением self (это отнюдь не результат) и еще 4 байта со значением адреса, по которому следует записать 8-байтовый результат !
Вот эти 4 байта + 4 байта = 8 байт и составляют ту самую 8-ку, которая фигурирует в ret 8 при том самом stdcall...
ПЗ (2008-10-01 22:28:00) [11]Проще. Но я преследую чисто исследовательские цели. Буду продираться.
Вот например, если вышеупомянутый ClassID сделать stdcall то при возврате структуры m_id по-значению (a,b:Integer) компилятор вставляет в конце ret 8. То есть, он хочет освободить 8 байт стека, якобы выделенных под результат? Однако на сях thiscall в этом же случае ставит ret 4, хотя в стек кладет тот же 8-байтовый результат. Нельзя ли их как-нибудь помирить малой кровью?
Dimka Maslov (2008-10-01 13:38:00) [12]В данном случае проще написать библиотеку-оболочку на С++, которая в свою очередь будет уже вызывать дельфийскую
GrayFace (2008-10-01 10:08:00) [13]ПЗ (29.09.08 21:36) [75]
Вопрос: КТО должен класть THIS в регистр ECX? Если верить отладчику, вызывающая программа у меня этого не делает.
должна она.
ПЗ (29.09.08 21:36) [75]
Как выкрутиться?
Как в [61] или [26]
Slym (2008-09-30 04:12:00) [14]
procedure TUtility.BeginEditParams(param2:pionter);cdecl;
var ip:TInterface; iu:IUtil;
begin
ip:=TInterface({param1}self);
iu:=IUtil(param2);
end;
Slym (2008-09-30 04:10:00) [15]ПЗ (29.09.08 21:36) [75]
а iu не передает вообще
procedure TUtility.BeginEditParams(ip:TInterface; iu:IUtil);cdecl;
begin
self.ip:=ip;
end;
да потомучто selfпотомучно procedure of object;
потомучто дельфи ждет не 2 параметра как ты хочешь, аТРИ
procedure BeginEditParams(self:TUtility;ip:TInterface; iu:IUtil);cdecl;
ПЗ (2008-09-29 21:36:00) [16]А как он тогда экспортируется?
Возвращаясь к thiscall и ret 4. В MSDN написано: [i] This 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. [/i] где под vararg понимается [i] Indicates a variable number of arguments. [/i] то есть, thiscall – стандартное соглашение о вызовах для всех методов, кроме тех, что используют переменное число параметров. У меня нет ни одного такого метода. То есть, [b]ВСЕ[/b] методы должны быть thiscall? Сегодня проверил, self внутри них не определен. Вопрос: КТО должен класть THIS в регистр ECX? Если верить отладчику, вызывающая программа у меня этого не делает. Может неявно?
Внутри методов мне self не нужен и на ECX мне тож начхать. Ничем другим thiscall от stdcall не отличается. Но stdcall после возврата сдвигает мне стек на ячейку. Как выкрутиться?
Вот еще пример:
procedure TUtility.BeginEditParams(ip:TInterface; iu:IUtil);cdecl;
begin
self.ip:=ip;
end;
При вызове 3ds передает ему через регистр указатель на объект ip, а iu не передает вообще. Проверил на сях, все работает. Дизассемблировал на делфях. Вызывающая сторона (3ds):
mov edx,[edi] <– адрес метода BeginEditParams
push eax <– eax=$035BD508 – адрес объекта ip
call edx
.... <- код, не связанный с очисткой стека
Тело метода BeginEditParams:
push ebp
mov ebp,esp
mov eax,[ebp+$0c] <- self.ip:=ip;
mov edx,[ebp+$08]
mov[edx+$08],eax
pop ebp
ret
Как нетрудно видеть, в стеке по адресу [ebp+$0c] параметр ip лежать никак не может. Что не так?
GrayFace (2008-09-29 20:37:00) [17]ПЗ (28.09.08 20:02) [72]
То есть, метод либо экспортируется, либо импортируется из DLL. ВОПРОС: CoreExport в этом случае помещает метод в VMT ?
нет, он же не virtual.
Slym (2008-09-29 04:18:00) [18]ПЗ (28.09.08 20:02) [72]
А как проверить, правильно ли они определяют self?
например так :)
function Creat(loading:Boolean=FALSE):Pointer;virtual;cdecl;
begin
result:=self.Creat(loading);
end;
если СтекОверфлоу значед self рабочий если АцесВиолейшн значед нерабочий :)
ПЗ (2008-09-28 20:02:00) [19]Из тех методов, которые 3ds реально вызывает на моей практике с параметром например
function Creat(loading:Boolean=FALSE):Pointer;virtual;cdecl;
А как проверить, правильно ли они определяют self?
Попутно возник еще вопрос. Многие методы Autodesk в своих заголовчниках определяет как
CoreExport void SetClassDesc(ClassDesc* i_cd);
где
#ifdef BLD_CORE
#define CoreExport __declspec( dllexport )
#else
#define CoreExport __declspec( dllimport )
#endif
То есть, метод либо экспортируется, либо импортируется из DLL. ВОПРОС: CoreExport в этом случае помещает метод в VMT ?
GrayFace (2008-09-28 10:50:00) [20]Они все должны быть thiscall. Среди других есть методы с параметрами или возвращающие структуру? Видимо нет, или в их случае сдвиг esp не приводит к проблемам. Можешь проверить - если self они определяют неправильно, то thiscall.