Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2009.11.08;
Скачать: [xml.tar.bz2];

Вниз

Классы в DLL на Delphi и MSVC++ (бой продолжается)...   Найти похожие ветки 

 
ПЗ   (2008-09-13 21:58) [0]

Продолжаю исследовать возможности написания плагинов для 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-13 22:00) [1]

На всякий случай, скомпилированный вид данного метода на обоих языках:
С++

 virtual SClassID ClassID()
 {
push        ebp  
mov         ebp,esp
sub         esp,0CCh
push        ebx  
push        esi  
push        edi  
push        ecx  
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx  
mov         dword ptr [ebp-8],ecx

return sid;
mov         eax,dword ptr [ebp+8]
mov         ecx,dword ptr [sid (0D2CF734h)]
mov         dword ptr [eax],ecx
mov         edx,dword ptr [sid+4 (0D2CF738h)]
mov         dword ptr [eax+4],edx
mov         eax,dword ptr [ebp+8]
 }
pop         edi  
pop         esi  
pop         ebx  
mov         esp,ebp
pop         ebp  
ret         4    


Delphi

ClassDesc.pas.244: begin
push ebp
mov ebp,esp

mov eax,[ebp+$08]
mov edx,[m_id]    <=== адрес возвращаемой структуры (m_id.a)
mov [eax],edx
mov edx,[m_id+$4] <=== m_id.b
mov [eax+$04],edx

ClassDesc.pas.244: end;
pop ebp
ret

Настораживают разночтения у скомпилированных функций. Что означает sub esp,0CCh в С++ - варианте?


 
Сергей М. ©   (2008-09-13 22:49) [2]


> Перед вызовом
> ESP = 0х0012DD34
> После вызова
> ESP = 0х0012DD38


Это говорит о том, что соглашение о вызове однозначно не cdecl.


> На Delphi
> Перед вызовом
> ESP = $0012DD34
> После вызова
> ESP = $0012DD34 <=!!!


Это говорит о том, что вызываемой cdecl-подпрограмме не был передан ни один входной параметр.


 
ПЗ   (2008-09-14 20:29) [3]

Да вроде и нет у него параметров. self разве что....
На всякий случай, попробовал stdcallв Delphi-реализации.
После возврата указатель стека перебрасывается аж в $0012DD3C. А надо, чтобы был $0012DD38.
safecall и pascal дают AV. Как быть?


 
Сергей М. ©   (2008-09-14 21:16) [4]


> нет у него параметров. self разве что


У кого "у него" ?

Покажи конкретную строчку дельфийского кода, при исполнении которой происходит эта самая "аномалия" ..


 
Сергей М. ©   (2008-09-14 21:18) [5]


> после возврата из ClassID()


Ну а где у тебя реализация этой функции ?


 
Сергей М. ©   (2008-09-14 22:14) [6]


> return sid;
> mov         eax,dword ptr [ebp+8]
> mov         ecx,dword ptr [sid (0D2CF734h)]
> mov         dword ptr [eax],ecx
> mov         edx,dword ptr [sid+4 (0D2CF738h)]
> mov         dword ptr [eax+4],edx
> mov         eax,dword ptr [ebp+8]
>  }
> pop         edi  
> pop         esi  
> pop         ebx  
> mov         esp,ebp
> pop         ebp  
> ret         4    
>


Это говорит о том, что на вершину стека перед вызовом ф-ции компилятором был положен параметр - адрес структуры, которую ф-ция должна заполнить, фактически скопировать.
Он же, этот адрес, возвращается в кач-ве рез-та в EAX.

RET 4 говорит об stdcall - стек балансируется вызываемой ф-цией.


> Как быть?


Либо извращаться на BASM"е либо бросить затею скрестить ежа с ужом - об этом тебе, кстати, уже неоднократоно говорили.


 
oxffff ©   (2008-09-14 22:42) [7]


> Сергей М. ©   (14.09.08 22:14) [6]


Уже лучше.


 
oxffff ©   (2008-09-14 22:44) [8]


> либо бросить затею скрестить ежа с ужом - об этом тебе,
> кстати, уже неоднократоно говорили.


The cdecl convention is useful when you call functions from shared libraries written in C or C++.

Скрещивать можно все. Нужно только немного знаний. Самую малость.


 
Anatoly Podgoretsky ©   (2008-09-14 23:09) [9]

> Сергей М.  (14.09.2008 22:14:06)  [6]

Вообще то об этом говорит "mov         eax,dword ptr [ebp+8]", а не "ret 4"


 
oxffff ©   (2008-09-14 23:23) [10]


> Anatoly Podgoretsky ©   (14.09.08 23:09) [9]


-1.

:)

И cdecl и stdcall использует указатель на слот для результата размером >4

The following conventions are used for returning function result values.

Ordinal results are returned, when possible, in a CPU register. Bytes are returned in AL, words are returned in AX, and double-words are returned in EAX.
Real results are returned in the floating-point coprocessor"s top-of-stack register (ST(0)). For function results of type Currency, the value in ST(0) is scaled by 10000. For example, the Currency value 1.234 is returned in ST(0) as 12340.
For a string, dynamic array, method pointer, or variant result, the effects are the same as if the function result were declared as an additional var parameter following the declared parameters. In other words, the caller passes an additional 32-bit pointer that points to a variable in which to return the function result.

Int64 is returned in EDX:EAX.
Pointer, class, class-reference, and procedure-pointer results are returned in EAX.
For static-array, record, and set results, if the value occupies one byte it is returned in AL; if the value occupies two bytes it is returned in AX; and if the value occupies four bytes it is returned in EAX. Otherwise, the result is returned in an additional var parameter that is passed to the function after the declared parameters..


 
Anatoly Podgoretsky ©   (2008-09-15 00:09) [11]

> oxffff  (14.09.2008 23:23:10)  [10]

По другому более точно - ret 4 говорит, что это не cdecl, а вот что одначно решить нельзя, толи это stdcall, толи pascal, толи safecal, но не register, тема в справке Calling conventions.


 
Сергей М. ©   (2008-09-15 09:01) [12]


> Anatoly Podgoretsky ©   (15.09.08 00:09) [11]


> но не register


Конечно, не register, но как объяснить

virtual SClassID ClassID()
{
push        ebp  
mov         ebp,esp
sub         esp,0CCh
push        ebx  
push        esi  
push        edi  
push        ecx  
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx  
mov         dword ptr [ebp-8],ecx


??


> После возврата указатель стека перебрасывается аж в $0012DD3C.
>  А надо, чтобы был $0012DD38.


Это из-за self"а - он первым заталкивается в стек, следом заталкивается указатель на результат.

Соотв-но на выходе ты увидишь RET 8 вместо ожидаемых RET 4


 
oxffff ©   (2008-09-15 09:09) [13]


> Сергей М. ©   (15.09.08 09:01) [12]
>
> > Anatoly Podgoretsky ©   (15.09.08 00:09) [11]
>
>
> > но не register
>
>
> Конечно, не register, но как объяснить


Это вообще код-шняга. Он делает ровным счетом ничего.
Рабочий код начинается с return sid;


 
Сергей М. ©   (2008-09-15 09:43) [14]


> oxffff ©   (15.09.08 09:09) [13]


Я тоже не понимаю, с какой луны он свалился)
Автор что-то напутал, наверно ..


 
Anatoly Podgoretsky ©   (2008-09-15 10:09) [15]

> Сергей М.  (15.09.2008 9:01:12)  [12]

Из отмеченого жирным, регистр ECX сохраняется в стеке поскольку он нужен неиспорченым в последеней строке.


 
Anatoly Podgoretsky ©   (2008-09-15 10:10) [16]

> Сергей М.  (15.09.2008 9:01:12)  [12]

esi, edi сохраняются в соответствии с соглашением по использованию регистров.


 
Сергей М. ©   (2008-09-15 11:01) [17]


> Anatoly Podgoretsky ©   (15.09.08 10:09) [15]


Бесспорно.
Но если этот код не "шняга", значит, в ECX передается один из вх.параметров ..


 
oxffff ©   (2008-09-15 11:10) [18]


> Сергей М. ©   (15.09.08 11:01) [17]
>
> > Anatoly Podgoretsky ©   (15.09.08 10:09) [15]
>
>
> Бесспорно.
> Но если этот код не "шняга", значит, в ECX передается один
> из вх.параметров ..


Неа. Этот код

push        ecx  
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx  
mov         dword ptr [ebp-8],ecx

Эта муть. Вставляется в VS в debug версии.
Отношения к коду не имеет.
Для проверки инициализированности перед использованием __RTC_UninitUse


 
Slym ©   (2008-09-15 11:20) [19]

А мне кажется что здесь не объекты, а интерфейсы... ибо как MAX находит точку входа в методы? по VMT чтоли? дак она у всех разная...


 
Anatoly Podgoretsky ©   (2008-09-15 11:26) [20]


> Сергей М. ©   (15.09.08 11:01) [17]

Видимо так, поскольку в последней строчке используется ECX, а инициализации не видать, в связи с этим перед этим сохраняется ECX в последней строчке. Но для точного диагноза надо знать соглашения о вызовах.


 
Сергей М. ©   (2008-09-15 11:41) [21]


> oxffff ©   (15.09.08 11:10) [18]


> Для проверки инициализированности


Инициализированности чего ?


 
Slym ©   (2008-09-15 11:46) [22]

например так может?
type
 TMClass_ID=packed record
   a,b:LongWord;
 end;

 IClassDesc = interface
 ["{9275252B-2D15-4C04-8317-158591B1EE85}"]
   function ClassID():TMClass_ID;cdecl;
 end;

 TClassDesc=class(TObject, IClassDesc);
   function ClassID():TMClass_ID;cdecl;
 end;
var ClassDesc:TClassDesc;

function LibClassDesc(const i:integer):TClassDesc
begin
 ClassDesc.GetInterface(IClassDesc,result);
end;


 
oxffff ©   (2008-09-15 12:01) [23]


> Сергей М. ©   (15.09.08 11:41) [21]


VС++ использует эту технику для проверки доступа к неинициализированным локальным переменным(те самые которые по отрицательным смещениям от ebp). В данном случае инициализация в 0CCCCCCCCh.

Но поскольку локальных переменных здесь нет, то следующий код - это шняга

push        ecx  
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx  
mov         dword ptr [ebp-8],ecx


 
Сергей М. ©   (2008-09-15 12:37) [24]


> oxffff ©   (15.09.08 12:01) [23]


> проверки доступа к неинициализированным локальным переменным


Зачем их "проверять" ? Не понятно ..
Либо они есть , либо их нет - разве компилятор об этом не знает ?


 
Slym ©   (2008-09-15 12:41) [25]

Метод Передача параметров Кто чистит стек Где применяется
__cdecl В стек, справа налево Вызывающий По умолчанию в C и C++
__stdcall В стек, справа налево Сама функция Вызовы почти всех системных функций, а также по умолчанию внутренние функции Visual Basic
__fastcall Два первых параметра  в регистрах ECX и EDX, остальные  в стек, справа налево Сама функция По умолчанию в Borland Delphi
thiscall Указатель this передается в регистре ECX, параметры  в стек, справа налево. Сама функция Функциями-методами классов без списка аргументов переменной длины. Не может быть задан явно.
похоже на то


 
Slym ©   (2008-09-15 13:06) [26]

попробуй так:
type
 TClassID=packed record
   a,b:LongWord;
 end;

 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;
 end;

const m_id:TClassID=(a:$FF00FF;b:$AABBEE);

function ClassID:TClassID;stdcall;
begin
 result:=m_id;
end;

function LibClassDesc(const i:integer):TClassDesc;
begin
 ZeroMemory(@result,SizeOf(result));
 result.ClassID:=@ClassID;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 ClassDesc:TClassDesc;
 ClassID:TClassID;
begin
 ClassDesc:=LibClassDesc(1);
 ClassID:=ClassDesc.ClassID;
end;


 
oxffff ©   (2008-09-15 14:20) [27]


> Либо они есть , либо их нет - разве компилятор об этом не
> знает ?


В данном случае их нет, но компилятор не знает.
:O)


 
oxffff ©   (2008-09-15 14:33) [28]


> Slym ©   (15.09.08 11:20) [19]


Интерфейс - это просто указатель на абстрактный класс с самантикой подсчета ссылок и запроса других абстрактных классов.


 
Сергей М. ©   (2008-09-15 15:44) [29]


> но компилятор не знает


Та к не бывает - тут знаю, а тут не знаю)


 
Anatoly Podgoretsky ©   (2008-09-15 16:33) [30]

> Сергей М.  (15.09.2008 15:44:29)  [29]

Классик сказал, напился, тут помню, а тут нет.


 
Сергей М. ©   (2008-09-15 18:51) [31]


> Anatoly Podgoretsky ©   (15.09.08 16:33) [30]


Сказал, потом напился ?)

Твоя пунктуация как всегда вне всяких похвал)


 
oxffff ©   (2008-09-15 19:36) [32]


> Сергей М. ©   (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З.


 
Anatoly Podgoretsky ©   (2008-09-15 19:59) [33]

> Сергей М.  (15.09.2008 18:51:31)  [31]

Это джентельмены удачи.


 
Сергей М. ©   (2008-09-15 20:20) [34]


> oxffff ©   (15.09.08 19:36) [32]
> Вы писали компилятор VC?


Разве я болен на всю голову ?)
Нет, конечно)


> Тем не менее он так делает


Значит, болен точно не я)


> Зачем это делается - XЗ.


Ну мало ли народу, начинающего раб.день с большого бодуна)..

Тот кто это писал, возможно, с него и начал раб.день, увековеченный ныне вот в таком "хз"-коде))


 
ПЗ   (2008-09-15 21:43) [35]

чего-то не отправляется


 
ПЗ   (2008-09-15 21:45) [36]

>Сергей М.
>Ну а где у тебя реализация этой функции ?
Реализация функции была приведена в моем посте №1.

>Покажи конкретную строчку дельфийского кода, при исполнении которой происходит >эта самая "аномалия" ..
Не покажу. Аномалия происходит при переходе от ret 4 к вызывающему коду (call edx). Сама функция нормально выполняется и на С и на Delphi. Но из-за неправильного ESP на Delphi, креш происходит впоследствии. Результат в обоих случаях возвращается через стек – могу дампы выложить. Причем, возвращается не адрес структуры,а сами ее значения (a,b).


 
ПЗ   (2008-09-15 21:47) [37]

>Автор что-то напутал, наверно ..
А какой смысл автору искажать код? Автору нужно решение, а не спор. Вот снимок окна отладчика на С:
До вызова хттп://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:48) [38]

Млин, оно ссылки не принимает :-(

Вчера штудировал MSDN и наткнулсяна такую штуку как __inline. Не тут ли собака порылась? Ведь в С++ ClassID() именно inline, а в делфях инлайна нет. Там что-то писалось про ESP, но как-то туманно. По-дефолту в настройках компилера VC у меня стоит _cdecl.При явном указании _stdcall метод не выполняется, вызывая ошибку при попытке записи по адресу [eax].

PS. Еще раз повторю – здесь не интерфейсы, а КЛАССЫ (без AddRef, GetInterface…). И макс получает их экземпляры по адресу, возвращаемому специальной функцией из DLL. Это не я придумал, а Autodesk. Кто не верит – могу выложить справку к SDK. Метод ув. Slym интересен, но с наскока у меня он не заработал. Если с классами не выйдет, буду пробовать этот вариант.


 
Сергей М. ©   (2008-09-15 22:09) [39]


> А какой смысл автору искажать код?


А ты собери С-проект с выключенными опциями отладки, тогда будет понятно где "шняга" - то ли у тебя, то ли у 0xfffd )


> Аномалия происходит при переходе от ret 4 к вызывающему
> коду


Ты русский понимаешь ?

тебе ж говорят - проблема в передаче self)

Ты хоть из штанов выпрыгни, но Делфи будет его передавать в данном контексте. При Pascal fastcall - через eax, при cdecl, stdcall, safecall - через стек первым параметром (с соответствующей стороны).


 
oxffff ©   (2008-09-15 23:39) [40]

Так, не поленился открыть 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 __cdecl ClassID()

lea         eax,[ebp+FFFFFF28h]
push        eax  
lea         ecx,[ebp-8]
push        ecx  
call        00411203
add         esp,8



Страницы: 1 2 3 вся ветка

Форум: "Основная";
Текущий архив: 2009.11.08;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.57 MB
Время: 0.01 c
15-1252854430
turbouser
2009-09-13 19:07
2009.11.08
С праздником всех!


1-1222903199
samz
2008-10-02 03:19
2009.11.08
Наследование форм и Parent контрола


13-1124174625
inspirion
2005-08-16 10:43
2009.11.08
IIS 5.1 и ASP.NET


2-1253990678
faiwer
2009-09-26 22:44
2009.11.08
Вопросы по поводу Scroll-а


15-1250661192
leonidus
2009-08-19 09:53
2009.11.08
Компонент для отображения HTML





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский