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-27 21:07:00) [21]

Но ответа на вопрос №55 я так и не нашел :-(

> Почему именно этот злополучный метод стал thiscall?


GrayFace   (2008-09-27 20:01:00) [22]

А, нет, туплю - там же взвращается структура, соответственно, передается на нее указатель, так что это __thiscall.


ПЗ   (2008-09-25 20:45:00) [23]


> >Пиши везде stdcall и не мучайся. :) Никаких наворотов не
> надо.


Уже писал. Не работает. Стек сдвигается.
Нормально работает только cdecl. Кроме ClassID, в который кроме cdecl пришлось воткнуть ret 4. Вот такая загогулина.


GrayFace   (2008-09-25 00:57:00) [24]

Блин, до меня только сейчас дошло про ret 4. Пиши везде stdcall и не мучайся. :) Никаких наворотов не надо.

ПЗ   (24.09.08 22:12) [66]
Это будет одна запись или две подряд?

Не знаю, позволит ли он конкретно это, но overload-методы - это, конечно, разные записи. Наверняка подряд, но я не проверял.


ПЗ   (2008-09-24 22:12:00) [25]


> Дак она же без параметров.

Дак и Class_ID был без параметров:
ClassDesc=class
...
function SuperClassID():SClass_ID;virtual;cdecl;
function ClassID():MClass_ID;virtual;cdecl;
...
end;
Ы?


> TSome3DSMethod = function(p1, p2:int; this:Pointer; Параметры
> в обратном порядке):тип_возвращаемого_значения register;
>

Настоящие герои всегда идут в обход? :-)
Для начала я попробую объявить его как положено:
Max_Interface=class
...
   function AddRollupPage(hInst:HINSTANCE; dlgTemplate:PChar; dlgProc:DLGPROC; title:PChar; param:LPARAM=0; flags:DWORD=0; category:Integer= ROLLUP_CAT_STANDARD ):HWND;virtual; cdecl; abstract;
...
end;
Я как раз закончил PHP-скрипт, который перевел мне заголовочник SDK с С++ *.H в *.PAS. Осталось ручками порядок навести и можно будет проверить.

Вопрос: как в VMT отражаются overload-методы?
function F(a:Integer):Boolean;
function F(a:Word):Boolean;

Это будет одна запись или две подряд?


GrayFace   (2008-09-24 02:56:00) [26]

Slym ©   (21.09.08 17:15) [63]
и получится менее наглядная реализация Slym[26]

Зато меньше писать :)

ПЗ   (22.09.08 20:52) [64]
function ClassDesc.SuperClassID():SClass_ID;cdecl;

Дак она же без параметров.

ПЗ   (22.09.08 20:52) [64]
Теперь мне в одном из методов TUtility, приняв указатель от 3ds надо вызвать метод его, 3ds, внутреннего объекта.

объявляй так:
TSome3DSMethod = function(p1, p2:int; this:Pointer; Параметры в обратном порядке):тип_возвращаемого_значения register;
в качестве p1, p2 подавай любые числа, например, нули.


ПЗ   (2008-09-22 20:52:00) [27]

>Потому что все методы классов - thiscall по умолчанию.
Идея понятна, если нынешняя реализация сбойнет, пойду этим путем.
Но почему нестыковки по стеку у меня вышли только с одним методом (а там их туева хуча)? Остальные работают идентично как в С, так и в Делфи. Они у меня помечены cdecl. И только ClassID() сбойнул. Например –
function ClassDesc.SuperClassID():SClass_ID;cdecl;
begin
 Result:=UTILITY_CLASS_ID;
end;

Сча я пошел дальше. Среди прочих есть
function ClassDesc.MyUtilCreate(loading:Boolean=FALSE):Pointer;cdecl;   // return a pointer to an instance of the class.
begin
 myUtil:=TUtility.Create;
 Result:=myUtil;
end;

Он возвращает в 3ds экземпляр TUtility, из которого 3ds вызывает уже другие методы (тоже мои, на Делфи) . Пока всё ОК. Теперь мне в одном из методов TUtility, приняв указатель от 3ds надо вызвать метод его, 3ds, внутреннего объекта. То есть, обратная задача. Сча над ней работаю.


Slym   (2008-09-21 17:15:00) [28]

GrayFace ©   (21.09.08 2:05) [61]
и получится менее наглядная реализация Slym[26]


GrayFace   (2008-09-21 02:06:00) [29]

*И подавай ему @MyObject в качестве объекта.


GrayFace   (2008-09-21 02:05:00) [30]

ПЗ   (17.09.08 21:04) [55]
Почему именно этот злополучный метод стал thiscall?

Потому что все методы классов - thiscall по умолчанию.
Надо просто делать функции stdcall и создавать из них массив. Примерно так:

MClass_ID = packedrecord
a,b:LongWord;
end;

procedure ClassID():MClass_ID;
begin
  Result:=m_id;
end;

var MyVMT: packed array[0..0] of Pointer =
(
@ClassID // здесь через запятую все виртуальные методы.
);

const MyObject: Pointer = @MyVMT[0];

И подавай ему MyObject в качестве объекта.


ПЗ   (2008-09-19 22:33:00) [31]

>Дельфийский что ли класс ?
>Ну ты и кулибин, однако)
------------------------------------------------
Что значит дейльфийский и почему кулибин - я? Autodesk хочет, чтобы плагины были на С, а я хочу на Делфи :-) Соответственно, Autodesk дает заголовочники С, в которых определен класс Class_ID – та же структура с полями a,b плюс несколько методов для доступа к ним. Методы не виртуальные (!) и в реальности она их не вызывает (проверил). Передача через стек по-значению, на выходе в стеке лежит тот же результат, что и со структурой (тоже проверил). Record нормально заглатывается что на С, что на Делфи.


Сергей М.   (2008-09-18 21:16:00) [32]


> он не структуру, а класс должен возвращать


Дельфийский что ли класс ?
Ну ты и кулибин, однако)


ПЗ   (2008-09-18 20:52:00) [33]

>Покажи передачу параметров в С++ примере на ASM.
----------------------------------------
Каких параметров? Возвращаемый результат идет через стек:

После возврата из ClassID()
0x0012DD38   ff 00 ff 00 ÿ.ÿ. Есть моменты которые делает Delphi за тебя, а именно корректно разматывает SEH фреймы(конструкции try\except\finally),а также финализирует переменные.
---------------------------------------
Я исключения в DLL не использую, если только внутренняя реализация Делфи. Но тут вопрос: а что я нарушил вместо RET сделав RET 4 ? Состояние стека у меня как раз пришло в норму. Это я не оправдываюсь, а сам пытаюсь понять, чего накуролесил :-)

> с ручным ret динамические структуры типа строк и дин массивов не сфинализируются, что приведет к утечке памяти
--------------------------------------
Динамические массивы я не использую, классы убивает сам MAX, а ANSI-строки… Хм. Память выделяется, память очищается. Надо понаблюдать, но думаю, в таких монстрах как МАХ утечек вряд ли удается избежать. Как RET 4 мог повилять?


Slym   (2008-09-18 08:37:00) [34]

oxffff ©   (17.09.08 23:23) [56]
а также финализирует переменные

+1
с ручным ret динамические структуры типа строк и дин массивов не сфинализируются, что приведет к утечке памяти


oxffff   (2008-09-17 23:23:00) [35]


> Если уж разбираться досконально, то надо иметь ответы на
> вышепоставленные вопросы. Почему именно этот злополучный
> метод стал thiscall? В реальности, кстати, он не структуру,
>  а класс должен возвращать (по-значению, т.е., через стек).
>


Покажи передачу параметров в С++ примере на ASM.


> if ... then Exit(code).


Есть моменты которые делает Delphi за тебя, а именно корректно разматывает SEH фреймы(конструкции try\except\finally),а также финализирует переменные.


ПЗ   (2008-09-17 21:04:00) [36]

Если уж разбираться досконально, то надо иметь ответы на вышепоставленные вопросы. Почему именно этот злополучный метод стал thiscall? В реальности, кстати, он не структуру, а класс должен возвращать (по-значению, т.е., через стек).
Я добавил
pop ebp
ret 4

не от балды, а так, как это получается на С.
Теперь ClassID() имеет вид:

push ebp
mov  ebp,esp
mov eax,[ebp+$08]
mov edx,[m_id]
mov [eax],edx
mov edx,[m_id+$4]
mov [eax+$04],edx

pop ebp <=мой код
ret ;004 <=мой код

pop ebp
ret

Кримина лишь в том, что я не могу досконально разобраться, почему так, а не иначе. Насчет второго ret, спорно – ведь мы не боимся делать в функциях if ... then Exit(code). Зато теперь стек у меня пришел в то состояние, в котором должен быть (я сличал) . Работы еще много, пойду пока дальше, там посмотрим как будет работать. В случае сбоев буду кумекать с переадресацией вызова. Из-за одного метода переделывать все мне что-то не хочется.


Slym   (2008-09-17 05:03:00) [37]

ПЗ   (16.09.08 21:29) [51]
pop ebp

огребеш...
вольному/бездумному изменению регистров используй eax,edx,ecx; остальное для отпиливание ножек у табуретки с петлей на шее


Германн   (2008-09-17 02:05:00) [38]

Долго пытался удержаться, но не могу более.
Увидел asm инструкцию RET внутри Паскалевской процедуры...
Нет слов.


oxffff   (2008-09-16 23:24:00) [39]


> Дамы и господа, одержана первая победа над гегемонией С++!


К сожалению нет. Так ты себе еще больше проблем создашь!!!
Рекомендую досконально разобраться в форме вызова на стороне С++, и сделать соответствие на delphi.


ПЗ   (2008-09-16 21:29:00) [40]

Oh yeah!

Дамы и господа, одержана первая победа над гегемонией С++!
хттп://i032.radikal.ru/0809/cc/e077a7bb68f5.jpg

Ларчик открылся просто:
function ClassDesc.ClassID():MClass_ID;cdecl;
begin
 Result:=m_id;
 asm
   pop ebp
   ret 4
 end;

end;
Пока я не уверен, что это корректный способ, но он сработал. 3ds загрузилась и увидела делфёвый плагин. Бой выигран, но впереди еще тяжелое сражение за функциональность данного плагина. Надеюсь на дальнейшее плодотворное сотрудничество. Всех мастеров благодарю за консультации!


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

Наверх

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