Форум: "Основная";
Текущий архив: 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
← →
oxffff © (2008-09-15 23:45) [41]У автора темы вызов __thiscall
The __thiscall calling convention is used on member functions and 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. Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.
← →
oxffff © (2008-09-16 00:06) [42]Для автора.
Если тебе все же необходимо делать этот вызов __thiscall для delphi нужно немного сообразительности. Идея очень простая. Тебе нужно в delphi сделать фиктивный объект транслятор __thiscall в __stdcall в delphi.
То есть создаешь некий объект вида
thisCallTostdcallWrapper=record
VMT:PVMT;
instance:DelphiInstance;
end;
VMT=array[1..MethodsCount] of pointer;
pVMT=^VMT;
где каждый указатель это указатель на код.
asm
push [esp];
mov ecx,[ecx+4];
mov [esp+4],ecx;
jmp stdcallMethodImpl;
end;
Процесс можно автоматизировать.
Это оставляем тебе в качестве домашнего задания. :)
← →
oxffff © (2008-09-16 00:14) [43]
> Процесс можно автоматизировать.
> Это оставляем тебе в качестве домашнего задания. :)
Подсказка тебе просто необходимо генерировать код на лету.
Заменяя только смещение в jmp stdcallMethodImpl на лету.
То есть берешь Delphi объект и проходишь всю его VMT с генерацией кода.
← →
Германн © (2008-09-16 00:28) [44]<offtop>
Термин "плагин" в моих глазах упал ещё ниже. Плинтус уже где-то рядом. :)
</offtop>
← →
Slym © (2008-09-16 10:13) [45]oxffff © (16.09.08 0:14) [43]
Подсказка тебе просто необходимо генерировать код на лету.
проще сделать такtype
TClassID=packed record
a,b:LongWord;
end;
PClassDesc=^TClassDesc;
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;
FClassID:TClassID;
OtherData:Pointer;
end;
function GetClassID:TClassID;stdcall;
var this:PClassDesc;
begin
asm
mov this, ecx
end;
result:=this^.FClassID;
end;
function LibClassDesc(const i:integer):TClassDesc;
begin
ZeroMemory(@result,SizeOf(result));
result.FClassID.a:=$FF00FF;
result.FClassID.b:=$AABBEE;
result.ClassID:=@GetClassID;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ClassDesc:TClassDesc;
ClassID:TClassID;
begin
ClassDesc:=LibClassDesc(1);
asm
lea ecx, ClassDesc
end;
ClassID:=ClassDesc.ClassID;
if (ClassID.a<>$FF00FF) or (ClassID.b<>$AABBEE) then raise exception.Create("Bad ClassID");
end;
← →
oxffff © (2008-09-16 10:32) [46]
> Slym © (16.09.08 10:13) [45]
Ты кажется не в теме. Точнее не понял тему.
Код вызывается из С++ с __thiscall calling convention. А не из delphi
Во вторых твой код не корректный.
При stdcall функция очистит на стек на 4 больше, чем при __thiscall.
Ну и главное обращение по VMT косвенное, то если ты передаш в С++ код PClassDesc. То код свалиться, поскольку ждет вместо
IsPublic: function:LongBool;stdcall;
указатель на VMT.
P.S. Зачем писать код для каждой функции вручную, если можно его генерировать на ходу автоматически. Представь если у автора таких функций >10.
Далее
← →
Slym © (2008-09-16 14:04) [47]oxffff © (16.09.08 10:32) [46]
Код вызывается из С++ с __thiscall calling convention. А не из delphi
этим я эмулирую thiscall -asm
lea ecx, ClassDesc
end;
ClassID:=ClassDesc.ClassID;
oxffff © (16.09.08 10:32) [46]
При stdcall функция очистит на стек на 4 больше, чем при __thiscall
чистит ровно настолькоже т.к. это не метод класса и нет скрытого параметра self - чистит только 4 (указатель на результат)
oxffff © (16.09.08 10:32) [46]
Ну и главное обращение по VMT косвенное
не факт что VMT ждет, а скорее всего на структуру с фиксированными точками входа в процедуры, т.к. с++ ничего низнает о дельфевой VMT
oxffff © (16.09.08 10:32) [46]
Зачем писать код для каждой функции вручную, если можно его генерировать на ходу автоматически
Си позволяет инлайнить код объявленных методов в месте обявления, а дельфи при определении записи (record) нет... тат нужно типа хелперов как в старших дельфях, но в 7 их нету
← →
oxffff © (2008-09-16 15:09) [48]
> Slym © (16.09.08 14:04) [47]
Ты должен передать С++ коду указатель на экземпляр класса, с коим код С++ будет взаимодейстовать вызывая его виртуальные методы через VMT с __thiscall.
Механизм вызова у них идентичен, за исключение того что в С++ VMT плавающий.
Здесь нет речи про Inline. Здесь косвенный вызов, нет прямой линковки.
function GetClassID:TClassID;stdcall;
var this:PClassDesc;
begin
asm
mov this, ecx
end;
result:=this^.FClassID;
end;
Теперь я понял, что это у тебя обычный метод. Однако в твоем случае нет гарантии того, что в ecx то, что должно быть.
+ нужно писать код вручную.
Я предлагаю этот процесс автоматизировать. То есть автор пишет методы в delphi c stdcall, а обертка транслирует вызовы __thiscall в stdcall.
← →
oxffff © (2008-09-16 15:55) [49]Например вот функция ты передаешь ей sdtcall функцию, оно возвращает обертку для преобразования из __thiscall вызова.
Пользуясь ею генерируешь свою таблицу VMT у объекта обертки реального объекта. Особо не тестил, так что прошу сильно не бить.
function Generate__ThisCallStub(StdCallee:pointer):pointer;
Label StubStartLabel,StubEndLabel;
var StubStart:pointer;
StubSize:DWORD;
fixOffset:DWORD;
OffsetValue:DWORD;
begin
try
result:=nil;
asm
mov StubStart,offset StubStartLabel;
mov StubSize,offset StubEndLabel;
sub StubSize,offset StubStartLabel;
mov fixOffset,Offset StubEndLabel;
sub fixOffset,offset StubStartLabel;
end;
GetMem(result,StubSize);
try
CopyMemory(result,StubStart,StubSize);
OffsetValue:=DWORD(StdCallee)-DWORD(result)-fixOffset;
asm
mov eax,fixOffset
lea eax,[eax-4];
add eax,result;
mov edx,OffsetValue;
mov [eax],edx;
end;
except
FreeMem(result);
raise;
end;
except
end;
exit;
///////////////////////////////////////////////////////////////////////////////
//Generic code
///////////////////////////////////////////////////////////////////////////////
asm
StubStartLabel:
push [esp];
mov ecx,[ecx+4];
mov [esp+4],ecx;
DB $E9;
DD $0;
StubEndLabel:
end;
end
← →
ПЗ (2008-09-16 20:53) [50]У меня три вопроса:
1. Вчера выкладывал шот той же функции ClassID() на VС без дебаг-опций:
mov ecx, dword ptr [sid]
mov [eax], dword ptr [esp+4]
mov dword ptr [eax], ecx
mov edx, dword ptr [sid+4]
mov dword ptr [eax+4],edx
ret 4
Здесь нет этих наворотов с
push eax
lea ecx,[ebp-8]
Это нормально?
2. А нельзя внутри этого же метода на делфи тупо увеличить add esp,4 ? this все равно внутри не используется.
3. У меня ведь куча методов в этом классе. Как определить, какие из них thiscall, а какие – cdecl ? Вот, например, перед ClassID() вызывается аналогичный SuperClassID(), который возвращает число. Он работает одинаково хорошо что на С что на Делфи и стек никуда не смещается. Есть некоторые другие методы, к которым у меня нет вопросов. Неужели методом тыка трассировать все?
← →
ПЗ (2008-09-16 21:29) [51]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 загрузилась и увидела делфёвый плагин. Бой выигран, но впереди еще тяжелое сражение за функциональность данного плагина. Надеюсь на дальнейшее плодотворное сотрудничество. Всех мастеров благодарю за консультации!
← →
oxffff © (2008-09-16 23:24) [52]
> Дамы и господа, одержана первая победа над гегемонией С++!
К сожалению нет. Так ты себе еще больше проблем создашь!!!
Рекомендую досконально разобраться в форме вызова на стороне С++, и сделать соответствие на delphi.
← →
Германн © (2008-09-17 02:05) [53]Долго пытался удержаться, но не могу более.
Увидел asm инструкцию RET внутри Паскалевской процедуры...
Нет слов.
← →
Slym © (2008-09-17 05:03) [54]ПЗ (16.09.08 21:29) [51]
pop ebp
огребеш...
вольному/бездумному изменению регистров используй eax,edx,ecx; остальное для отпиливание ножек у табуретки с петлей на шее
← →
ПЗ (2008-09-17 21:04) [55]Если уж разбираться досконально, то надо иметь ответы на вышепоставленные вопросы. Почему именно этот злополучный метод стал 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). Зато теперь стек у меня пришел в то состояние, в котором должен быть (я сличал) . Работы еще много, пойду пока дальше, там посмотрим как будет работать. В случае сбоев буду кумекать с переадресацией вызова. Из-за одного метода переделывать все мне что-то не хочется.
← →
oxffff © (2008-09-17 23:23) [56]
> Если уж разбираться досконально, то надо иметь ответы на
> вышепоставленные вопросы. Почему именно этот злополучный
> метод стал thiscall? В реальности, кстати, он не структуру,
> а класс должен возвращать (по-значению, т.е., через стек).
>
Покажи передачу параметров в С++ примере на ASM.
> if ... then Exit(code).
Есть моменты которые делает Delphi за тебя, а именно корректно разматывает SEH фреймы(конструкции try\except\finally),а также финализирует переменные.
← →
Slym © (2008-09-18 08:37) [57]oxffff © (17.09.08 23:23) [56]
а также финализирует переменные
+1
с ручным ret динамические структуры типа строк и дин массивов не сфинализируются, что приведет к утечке памяти
← →
ПЗ (2008-09-18 20:52) [58]>Покажи передачу параметров в С++ примере на ASM.
----------------------------------------
Каких параметров? Возвращаемый результат идет через стек:
После возврата из ClassID()
0x0012DD38 ff 00 ff 00 ÿ.ÿ. <= ESP
0x0012DD3C ee bb aa 00 .
0x0012DD40 00 00 00 00 ....
0x0012DD44 48 90 2e 0d H..
0x0012DD48 80 12 0c 66 €..f
0x0012DD4C c0 80 cb 08 À€Ë.
0x0012DD50 d0 e2 12 00 Ðâ..
0x0012DD54 17 92 7b 65 .’{e
Остальное видно на снимках с отладчика, которые я выше приводил.
>Есть моменты которые делает Delphi за тебя, а именно корректно разматывает SEH фреймы(конструкции try\except\finally),а также финализирует переменные.
---------------------------------------
Я исключения в DLL не использую, если только внутренняя реализация Делфи. Но тут вопрос: а что я нарушил вместо RET сделав RET 4 ? Состояние стека у меня как раз пришло в норму. Это я не оправдываюсь, а сам пытаюсь понять, чего накуролесил :-)
> с ручным ret динамические структуры типа строк и дин массивов не сфинализируются, что приведет к утечке памяти
--------------------------------------
Динамические массивы я не использую, классы убивает сам MAX, а ANSI-строки… Хм. Память выделяется, память очищается. Надо понаблюдать, но думаю, в таких монстрах как МАХ утечек вряд ли удается избежать. Как RET 4 мог повилять?
← →
Сергей М. © (2008-09-18 21:16) [59]
> он не структуру, а класс должен возвращать
Дельфийский что ли класс ?
Ну ты и кулибин, однако)
← →
ПЗ (2008-09-19 22:33) [60]>Дельфийский что ли класс ?
>Ну ты и кулибин, однако)
------------------------------------------------
Что значит дейльфийский и почему кулибин - я? Autodesk хочет, чтобы плагины были на С, а я хочу на Делфи :-) Соответственно, Autodesk дает заголовочники С, в которых определен класс Class_ID – та же структура с полями a,b плюс несколько методов для доступа к ним. Методы не виртуальные (!) и в реальности она их не вызывает (проверил). Передача через стек по-значению, на выходе в стеке лежит тот же результат, что и со структурой (тоже проверил). Record нормально заглатывается что на С, что на Делфи.
← →
GrayFace © (2008-09-21 02:05) [61]ПЗ (17.09.08 21:04) [55]
Почему именно этот злополучный метод стал thiscall?
Потому что все методы классов - thiscall по умолчанию.
Надо просто делать функции stdcall и создавать из них массив. Примерно так:
MClass_ID = packed record
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 в качестве объекта.
← →
GrayFace © (2008-09-21 02:06) [62]*И подавай ему @MyObject в качестве объекта.
← →
Slym © (2008-09-21 17:15) [63]GrayFace © (21.09.08 2:05) [61]
и получится менее наглядная реализация Slym[26]
← →
ПЗ (2008-09-22 20:52) [64]>Потому что все методы классов - 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, внутреннего объекта. То есть, обратная задача. Сча над ней работаю.
← →
GrayFace © (2008-09-24 02:56) [65]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-24 22:12) [66]
> Дак она же без параметров.
Дак и 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-25 00:57) [67]Блин, до меня только сейчас дошло про ret 4. Пиши везде stdcall и не мучайся. :) Никаких наворотов не надо.
ПЗ (24.09.08 22:12) [66]
Это будет одна запись или две подряд?
Не знаю, позволит ли он конкретно это, но overload-методы - это, конечно, разные записи. Наверняка подряд, но я не проверял.
← →
ПЗ (2008-09-25 20:45) [68]
> >Пиши везде stdcall и не мучайся. :) Никаких наворотов не
> надо.
Уже писал. Не работает. Стек сдвигается.
Нормально работает только cdecl. Кроме ClassID, в который кроме cdecl пришлось воткнуть ret 4. Вот такая загогулина.
← →
GrayFace © (2008-09-27 20:01) [69]А, нет, туплю - там же взвращается структура, соответственно, передается на нее указатель, так что это __thiscall.
← →
ПЗ (2008-09-27 21:07) [70]Но ответа на вопрос №55 я так и не нашел :-(
> Почему именно этот злополучный метод стал thiscall?
← →
GrayFace © (2008-09-28 10:50) [71]Они все должны быть thiscall. Среди других есть методы с параметрами или возвращающие структуру? Видимо нет, или в их случае сдвиг esp не приводит к проблемам. Можешь проверить - если self они определяют неправильно, то thiscall.
← →
ПЗ (2008-09-28 20:02) [72]Из тех методов, которые 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 ?
← →
Slym © (2008-09-29 04:18) [73]ПЗ (28.09.08 20:02) [72]
А как проверить, правильно ли они определяют self?
например так :)function Creat(loading:Boolean=FALSE):Pointer;virtual;cdecl;
begin
result:=self.Creat(loading);
end;
если СтекОверфлоу значед self рабочий если АцесВиолейшн значед нерабочий :)
← →
GrayFace © (2008-09-29 20:37) [74]ПЗ (28.09.08 20:02) [72]
То есть, метод либо экспортируется, либо импортируется из DLL. ВОПРОС: CoreExport в этом случае помещает метод в VMT ?
нет, он же не virtual.
← →
ПЗ (2008-09-29 21:36) [75]А как он тогда экспортируется?
Возвращаясь к 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 лежать никак не может. Что не так?
← →
Slym © (2008-09-30 04:10) [76]ПЗ (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;
← →
Slym © (2008-09-30 04:12) [77]
procedure TUtility.BeginEditParams(param2:pionter);cdecl;
var ip:TInterface; iu:IUtil;
begin
ip:=TInterface({param1}self);
iu:=IUtil(param2);
end;
← →
GrayFace © (2008-10-01 10:08) [78]ПЗ (29.09.08 21:36) [75]
Вопрос: КТО должен класть THIS в регистр ECX? Если верить отладчику, вызывающая программа у меня этого не делает.
должна она.
ПЗ (29.09.08 21:36) [75]
Как выкрутиться?
Как в [61] или [26]
← →
Dimka Maslov © (2008-10-01 13:38) [79]В данном случае проще написать библиотеку-оболочку на С++, которая в свою очередь будет уже вызывать дельфийскую
← →
ПЗ (2008-10-01 22:28) [80]Проще. Но я преследую чисто исследовательские цели. Буду продираться.
Вот например, если вышеупомянутый ClassID сделать stdcall то при возврате структуры m_id по-значению (a,b:Integer) компилятор вставляет в конце ret 8. То есть, он хочет освободить 8 байт стека, якобы выделенных под результат? Однако на сях thiscall в этом же случае ставит ret 4, хотя в стек кладет тот же 8-байтовый результат. Нельзя ли их как-нибудь помирить малой кровью?
← →
Сергей М. © (2008-10-01 22:44) [81]
> То есть, он хочет освободить 8 байт стека, якобы выделенных
> под результат?
С какого перепугу ?
Тебе ж устали уже долдонить - в стек при этом кладется 4 байта со значением self (это отнюдь не результат) и еще 4 байта со значением адреса, по которому следует записать 8-байтовый результат !
Вот эти 4 байта + 4 байта = 8 байт и составляют ту самую 8-ку, которая фигурирует в ret 8 при том самом stdcall...
← →
Сергей М. © (2008-10-01 23:14) [82]
> Однако на сях thiscall в этом же случае ставит ret 4, хотя
> в стек кладет тот же 8-байтовый результат.
Да не в стек результат кладется !
Кладется он по адресу, переданному 4-мя байтами, положенными в стек непосредственно перед вызовом. Эти 4 байта и удаляются с вершины стека при ret 4.
Т.е. _thiscall - это, получается, жуткая смесь под названием "Кошмар дельфиста", состоящая из
- C-fastcall - параметр this передается через РОН ecx, подобно тому как Делфи при pascal-fastacall передает self через РОН ebx
- stdcall - прочие параметры передаются через стек справа налево по ссылке или по значению, в зависимости от контекста, балансировкой стека заведует вызываемый код
← →
oxffff © (2008-10-02 19:57) [83]Бой окончен.
Еще с того момента как
Slym © (15.09.08 12:41) [25].
oxffff © (16.09.08 15:55) [49]
← →
ПЗ (2008-10-02 21:12) [84]Да не в стек результат кладется !
Кладется он по адресу, переданному 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 21:32) [85]
> ПЗ (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]
Используй ее. И не нужно будет задумываться.
← →
Slym © (2008-10-03 04:52) [86]oxffff © (02.10.08 21:32) [85]
И не нужно будет задумываться.
У него научный интерес... а доводы окружающих это простой и ненаучный рефератизм
← →
ПЗ (2008-10-04 20:24) [87]Все понял,в сем спасибо.
← →
GrayFace © (2008-10-04 22:18) [88]oxffff © (16.09.08 15:55) [49]
Data Execution Protection будет ругаться. Для таких вещей приходится использовать VirtualAlloc с указанием флага EXECUTE.
← →
oxffff © (2008-10-04 23:04) [89]
> GrayFace © (04.10.08 22:18) [88]
Если процессор не поддерживает NX бит страницы, то увсе будет ок.
← →
oxffff © (2008-10-04 23:07) [90]
> GrayFace © (04.10.08 22:18) [88]
Есть еще VirtualProtect.
Страницы: 1 2 3 вся ветка
Форум: "Основная";
Текущий архив: 2009.11.08;
Скачать: [xml.tar.bz2];
Память: 0.75 MB
Время: 0.008 c