![]() |
| ||
|
|
Новости |
Поиск |
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-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 загрузилась и увидела делфёвый плагин. Бой выигран, но впереди еще тяжелое сражение за функциональность данного плагина. Надеюсь на дальнейшее плодотворное сотрудничество. Всех мастеров благодарю за консультации!