Форум: "Основная";
Текущий архив: 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