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!

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


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.


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

Наверх

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