![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
В продолжение темы про DLL-классы на Delphi и MSVC. Нужен совет!
ПЗ © (2008-08-06 21:23:00)Имею плагин для 3d Studio MAX, написанный на С. Хочу написать его на Delphi. Плагин сидит в DLL, экспортирующей функцию, которая возвращает указатель на следующий класс (MessageBox вставлены мною для отладки):
// класс-описатель для вашего plug-in'а
class MyUtilityClassDesc
{
private:
Tab interfaces; // the FnPub interfaces published by this plugin
// maxutil.lib needed!
public:
virtual void AAA() {}
virtual void BBB() {}
virtual ~MyUtilityClassDesc() {MessageBox(0,"ClassDesc.~ClassDesc","",MB_OK);}
virtual int IsPublic()
{
MessageBox(0,"ClassDesc.IsPublic","",MB_OK);
return 1;
}
virtual void * Create(BOOL loading = FALSE)
{
MessageBox(0,"ClassDesc.Create","",MB_OK);
return NULL;//&theMyUtility;
}
int BeginCreate(Interface *i) {MessageBox(0,"ClassDesc.BeginCreate","",MB_OK); return 0;}
int EndCreate(Interface *i) {MessageBox(0,"ClassDesc.EndCreate","",MB_OK); return 0;};
virtual const TCHAR * ClassName()
{
MessageBox(0,"ClassDesc.ClassName","",MB_OK);
return "MyUtility";
}
virtual SClass_ID SuperClassID()
{
MessageBox(0,"ClassDesc.SuperClassID","",MB_OK);
return UTILITY_CLASS_ID;
}
virtual CClassID ClassID()
{
MessageBox(0,"ClassDesc.ClassID","",MB_OK);
return cid;//MYUTILITY_CLASS_ID;
}
virtual const TCHAR * Category()
{
MessageBox(0,"ClassDesc.Category","",MB_OK);
return "Utility";
}
virtual BOOL OkToCreate(Interface *i) {MessageBox(0,"ClassDesc.OkToCreate","",MB_OK); return TRUE; }
virtual BOOL HasClassParams() {MessageBox(0,"ClassDesc.HasClassParams","",MB_OK); return FALSE;}
virtual void EditClassParams(HWND hParent) {MessageBox(0,"ClassDesc.EditClassParams","",MB_OK);}
virtual void ResetClassParams(BOOL fileReset=FALSE) {MessageBox(0,"ClassDesc.ResetClassParams","",MB_OK);}
virtual int NumActionTables() {MessageBox(0,"ClassDesc.NumActionTables","",MB_OK); return 0; }
virtual ActionTable* GetActionTable(int i) {MessageBox(0,"ClassDesc.GetActionTable","",MB_OK); return NULL; }
virtual BOOL IsManipulator() {MessageBox(0,"ClassDesc.IsManipulator","",MB_OK); return FALSE; }
virtual BOOL CanManipulate(ReferenceTarget* hTarget) {MessageBox(0,"ClassDesc.CanManipulate","",MB_OK); return FALSE; }
virtual BOOL CanManipulateNode(INode* pNode) {MessageBox(0,"ClassDesc.CanManipulateNode","",MB_OK); return FALSE; }
virtual Manipulator* CreateManipulator(
ReferenceTarget* hTarget,
INode* pNode) {MessageBox(0,"ClassDesc.CreateManipulator","",MB_OK); return NULL; }
virtual Manipulator* CreateManipulator(INode* pNode) {MessageBox(0,"ClassDesc.CreateManipulator2","",MB_OK); return NULL;}
virtual BOOL NeedsToSave() {MessageBox(0,"ClassDesc.NeedsToSave","",MB_OK); return FALSE; }
virtual IOResult Save(ISave *isave) {MessageBox(0,"ClassDesc.Save","",MB_OK); return IO_OK; }
virtual IOResult Load(ILoad *iload) {MessageBox(0,"ClassDesc.Load","",MB_OK); return IO_OK; }
virtual DWORD InitialRollupPageState() {MessageBox(0,"ClassDesc.InitialRollupPageState","",MB_OK); return 0x7fffffff;}
// возвращает имя для MaxScript
virtual const TCHAR * InternalName()
{
MessageBox(0,"ClassDesc.InternalName","",MB_OK);
return _T("MyUtility");
}
// возвращает hInstance модуля
virtual HINSTANCE HInstance()
{
MessageBox(0,"ClassDesc.HInstance","",MB_OK);
return hInstance;
}
virtual int NumParamBlockDescs() {MessageBox(0,"ClassDesc.NumParamBlockDescs","",MB_OK); return 0; }
virtual ParamBlockDesc2* GetParamBlockDesc(int i) {MessageBox(0,"ClassDesc.GetParamBlockDesc","",MB_OK); return NULL; }
virtual ParamBlockDesc2* GetParamBlockDescByID(BlockID id) {MessageBox(0,"ClassDesc.GetParamBlockDescByID","",MB_OK); return NULL; }
virtual void AddParamBlockDesc(ParamBlockDesc2* pbd) {MessageBox(0,"ClassDesc.AddParamBlockDesc","",MB_OK); }
virtual void BeginEditParams(IObjParam *ip, ReferenceMaker* obj, ULONG flags, Animatable *prev) {MessageBox(0,"ClassDesc.BeginEditParams","",MB_OK); }
virtual void EndEditParams(IObjParam *ip, ReferenceMaker* obj, ULONG flags, Animatable *prev) {MessageBox(0,"ClassDesc.EndEditParams","",MB_OK); }
virtual void InvalidateUI(ParamBlockDesc2* pbd) {MessageBox(0,"ClassDesc.InvalidateUI","",MB_OK); }
virtual MCHAR* GetRsrcString(INT_PTR id){MessageBox(0,"ClassDesc.GetRsrcString","",MB_OK); return NULL;};
virtual void MakeAutoParamBlocks(ReferenceMaker* owner) {MessageBox(0,"ClassDesc.MakeAutoParamBlocks","",MB_OK); }
virtual int NumParamMaps() {MessageBox(0,"ClassDesc.NumParamMaps","",MB_OK); return 0; }
virtual IParamMap2* GetParamMap(int i) {MessageBox(0,"ClassDesc.GetParamMap","",MB_OK); return NULL; }
virtual IParamMap2* GetParamMap(ParamBlockDesc2* pbd) {MessageBox(0,"ClassDesc.GetParamMap2","",MB_OK); return NULL; }
virtual void SetUserDlgProc(ParamBlockDesc2* pbd, ParamMap2UserDlgProc* proc=NULL) {MessageBox(0,"ClassDesc.SetUserDlgProc","",MB_OK); }
virtual ParamMap2UserDlgProc* GetUserDlgProc(ParamBlockDesc2* pbd) {MessageBox(0,"ClassDesc.GetUserDlgProc","",MB_OK); return NULL; }
virtual bool DrawRepresentation(COLORREF bkColor, HDC hDC, Rect &rect) {MessageBox(0,"ClassDesc.DrawRepresentation","",MB_OK); return FALSE; }
virtual int NumInterfaces() {MessageBox(0,"ClassDesc.NumInterfaces","",MB_OK); return 0; }
virtual FPInterface* GetInterfaceAt(int i) {MessageBox(0,"ClassDesc.GetInterfaceAt","",MB_OK); return NULL; }
virtual FPInterface* GetInterface(Interface_ID id){MessageBox(0,"ClassDesc.GetInterface","",MB_OK); return NULL; }
virtual FPInterface* GetInterface(MCHAR* name){MessageBox(0,"ClassDesc.GetInterface2","",MB_OK); return NULL; }
virtual void AddInterface(FPInterface* fpi){MessageBox(0,"ClassDesc.AddInterface","",MB_OK); };
virtual void ClearInterfaces() { MessageBox(0,"ClassDesc.ClearInterfaces","",MB_OK); }
virtual Class_ID SubClassID() {MessageBox(0,"ClassDesc.SubClassID","",MB_OK); return Class_ID(); }
virtual INT_PTR Execute(int cmd, ULONG_PTR arg1=0, ULONG_PTR arg2=0, ULONG_PTR arg3=0) {MessageBox(0,"ClassDesc.Execute","",MB_OK); return 0; }
};
Два виртуальных метода вначале - пустышки. Зачем нужны - ХЗ, но без них последовательность вызовов методов нарушается. Последовательность следующая:
1. MyUtilityClassDesc.SuperClassID()
2. MyUtilityClassDesc.ClassID()
3. MyUtilityClassDesc.SuperClassID()
4. MyUtilityClassDesc.IsPublic()
5. MyUtilityClassDesc.ClassID()
6. MyUtilityClassDesc.ClassName()
7. MyUtilityClassDesc.Category()
8. MyUtilityClassDesc.InitialPageRollupState()
9. MyUtilityClassDesc.NemActionTables()
....
Написал аналог на Delphi (cм. далее). Есть проблема, тебуется опытный глаз!
ПЗ (2008-08-25 21:27:00) [1]Жесть... Похоже один из методов врет с передачей параметров (которых нет) или с соглашением о вызове. Где-то неверно очищается стек. Идет CALL, но перед RET корректный адрес возврата в стее оказываетеся на одну позицию выше, чем надо. В результате - возврат в пустоту...
oxffff (2008-08-19 21:33:00) [2]
> ПЗ (19.08.08 21:01) [22]
Выходишь(из процедуры) по ret и мотаешь вверх(назад) до вызова процедуры. Ставишь бряк там. Смотришь передачу параметров и сам вызов.
Соблюдая терпение(если их будет несколько), попадаешь в свою процедуру.
ПЗ (2008-08-19 21:01:00) [3]Эээ... Так при выходе или на вызове? Если второе, то как это сделать? Вызов из другой программы идет. У меня исходников 3ds нет.
На выходе я ставил, но после ret содержимое регистров меняется. Чистоты эксперимента нет.
oxffff (2008-08-18 21:09:00) [4]Поставить бряк при выходе из процедуры на вызове.
ПЗ (2008-08-18 20:48:00) [5]Вопрос по отладчику: как посмотреть вызов метода, если его вызывает чужая программа?
Я поставил брейкпоинт на нужный метод, и ессно, вижу внутренности этого метода, когда прога останавливается на брейкпоинте. А как мне посмотреть сам момент его вызова?
ПЗ (2008-08-16 20:22:00) [6]ОК, буду пробовать. В ассемблере я совсем не варю. В классе TObject я насчитал как минимум 8 виртуальных методов. Все они, как я понимаю, автоматом переползают в мой ClassDesc из-за наследования.
oxffff (2008-08-15 21:32:00) [7]
> ПЗ (15.08.08 21:02) [17]
Посмотри внимательно, как происходит вызов метода и как и куда кладутся параметры на ассемблере в 1,2,3 из [15].
1. Посмотри как происходит сам вызов через таблицу VMT(какие смещения используются), сравни и найди разницу.
2. Корректно ли передаются параметры. И то ли ты передаешь в случае с Delphi.
Соответственно так ты сможешь найти причину.
Поскольку меня смущает твоя фраза
....
> Все работает, при условии добавления ААА,ВВВ. Последовательность
> вызовов как в п.1.
....
>... Delphi
> Никаких доп. методов не требуется.
По твоему получается, что VMT смещения в Delphi начинаются с нуля.
А на примере 2 для С++ начинаюся с "+8". Так быть не может.
ПЗ (2008-08-15 21:02:00) [8]А что понимается под "посмотреть под отладчиком сами вызовы"? Исходников SDK у меня нет, только заголовочники. Брейкпоинты в методы я вставлял, но это то же, что и MessageBox. Что еще можно посмотреть?
oxffff (2008-08-14 22:44:00) [9]
> М.б. конструктор копий там должен был быть?
Обычный конструктор и конструктор копий в С++ не может быть виртуальным. Рекомендую посмотреть под отладчиком сами вызовы в честных и не честных случаях и тогда все станет кристально понятно.
ПЗ (2008-08-14 21:19:00) [10]На всякий случай, попробую еще внести ясность. Итак имею:
1. "Совсем честную" DLL на С++ с использованием *.LIB и *.Н из SDK:
class MyUtilityClassDesc:public ClassDesc
где
class ClassDesc: public MaxHeapOperators - оба сидят в SDK.
Это честный павильный плагин, который работает. Методы вызывыются в последовательности, описанной выше.
2. "Не совсем честную" DLL на С++ без использования *.LIB и *.Н из SDK:
class MyUtilityClassDesc - сидит в самой DLL, ни от чего не наследуется.
Все работает, при условии добавления ААА,ВВВ. Последовательность вызовов как в п.1.
3."Совсем не честную" DLL на Delphi без использования *.LIB и *.Н из SDK:
ClassDesc=class ... end; - сидит в самой DLL, ни от чего не наследуется.
Никаких доп. методов не требуется. Последовательность вызовов совпадает с п.1, но после InitialPageRollupState крэш :-(
Уф....
ПЗ (2008-08-14 21:11:00) [11]Я выше написал - методы AAA, BBB пришлось добавлять только на C-реализации. На Delphi они не нужны (последовательность вызовов тогда нарушается). На delphi первым (как и должно быть) вызывается ClassDesc.SuperClassID, потом ClassDesc.ClassID, ClassDesc.SuperClassID, ClassDesc.IsPublic, ClassDesc.ClassID, ClassDesc.Category, ClassDesc.InitialRollupState. После этого происходит крэш.
Аналогичная последовательность (я проверил) у настоящих фирменных плагинов, написанных на C++ с наследованием от заголовочников и LIB из SDK.
Когда я попробовал написать MyUtilityClassDesc (выше) на С++ БЕЗ LIB из SDK (скопировал класс из заголовочника), у меня первым вызвался не SuperClassID, а Category(), который на две позиции ниже в VMT (что вызывало крэш, ессно). Экспериментальным путем я добавил два виртуальных метода и все заработало.
Сейчас проверил еще раз сишный вариант. BBB() вызывается перед IsPublic. ААА() не вызывается. ХЗ что это такое? М.б. конструктор копий там должен был быть? В заголовочнике SDK его нет.
oxffff (2008-08-14 08:51:00) [12]Почему у тебя не совпадают позиции соответствующих методов в виртуальных таблицах?
Итак, имеем: class MyUtilityClassDesc
{
public:
virtual void AAA() {}// – ХЗ №1
virtual void BBB() {}// – ХЗ №2
virtual ~MyUtilityClassDesc() { }//Деструктор. Конструктора нет!
virtual int IsPublic() { return 1; }
ClassDesc=class {MaxHeapOperators}
public
destructor ClassDesc();virtual;
function IsPublic():integer;virtual;cdecl;
…
function ClassName():PAnsiChar;virtual;cdecl;
function SuperClassID():SClass_ID;virtual;cdecl;
function ClassID():TClass_ID;virtual;cdecl;
function Category():PAnsiChar;virtual;cdecl;
То есть при вызове ClassDesc.IsPublic(Delphi) у тебя будет вызываться
MyUtilityClassDesc.BBB(C++)
ПЗ (2008-08-11 21:00:00) [13]Сегодня попробовал отладчиком смотреть, но адреса AV все время разные :-(
ПЗ (2008-08-09 20:18:00) [14]Нет, точно не здесь. Деструктор вызывается (я контролировал) после закрытия 3ds, когда все библиотеки выгружаются. А у меня они и загрузиться не успевают :-)))
На всякий случай, вот код этого самого MaxHeapOperators:
#ifdef BLD_UTIL
#define UtilExport __declspec( dllexport )
#else
#define UtilExport __declspec( dllimport )
#endif
class MaxHeapOperators
{
public:
UtilExport static void* operator new( size_t size );
UtilExport static void* operator new( size_t size, const std::nothrow_t & e );
UtilExport static void* operator new( size_t size, const char* filename, int line );
UtilExport static void* operator new( size_t size, const std::nothrow_t & e, const char * filename, int line );
UtilExport static void* operator new( size_t size, unsigned long flags );
UtilExport static void* operator new( size_t size, const std::nothrow_t & e, unsigned long flags );
UtilExport static void* operator new[]( size_t size );
UtilExport static void* operator new[]( size_t size, const std::nothrow_t & e );
UtilExport static void* operator new[]( size_t size, const char* filename, int line );
UtilExport static void* operator new[]( size_t size, const std::nothrow_t & e, const char * filename, int line );
UtilExport static void* operator new[]( size_t size, unsigned long flags );
UtilExport static void* operator new[]( size_t size, const std::nothrow_t & e, unsigned long flags );
UtilExport static void operator delete( void * ptr );
UtilExport static void operator delete( void * ptr, const std::nothrow_t& e );
UtilExport static void operator delete( void * ptr, const char * filename, int line );
UtilExport static void operator delete( void * ptr, const std::nothrow_t & e, const char * filename, int line );
UtilExport static void operator delete( void * ptr, unsigned long flags );
UtilExport static void operator delete( void * ptr, const std::nothrow_t & e, unsigned long flags );
UtilExport static void operator delete[]( void * ptr );
UtilExport static void operator delete[]( void * ptr, const std::nothrow_t& e );
UtilExport static void operator delete[]( void * ptr, const char * filename, int line );
UtilExport static void operator delete[]( void * ptr, const std::nothrow_t& e, const char * filename, int line );
UtilExport static void operator delete[]( void * ptr, unsigned long flags );
UtilExport static void operator delete[]( void * ptr, const std::nothrow_t&e, unsigned long flags );
UtilExport static void* operator new( size_t size, void * placement_ptr );
UtilExport static void operator delete( void * ptr, void * placement_ptr );
};
Реализацию опускаю за тривиальностью. Есть подозрение, что неизвестные методы AAA() и BBB(), которые мне пришлось добавить в сишной реализации - это конструктор по-умолчанию и конструктор копий. Где-то в MSDN попадалось, что они должны автоматически добавляться к любому классу. Тем более, что BBB() на этапе загрузки ВЫЗЫВАЕТСЯ. Я только сегодня это обнаружил, вставив MessageBox.
oxffff (2008-08-09 14:57:00) [15]
> oxffff © (09.08.08 14:54) [9]
Может приводить поскольку я не знаю по твоему коду описание декструктора MaxHeapOperators.
Если он перекрывает стандартный декструктор, то проблемы гарантированы на 100%. Если вызов стандартного перекрытого не используется, тогда проблем быть не должно.
oxffff (2008-08-09 14:54:00) [16]
> ПЗ (08.08.08 20:13) [8]
При очень беглом обзоре на [8]
(в понедельник на работе я постараюсь посмотреть более внимательно).
Я нашел следующее несоответствие.
ClassDesc=class {MaxHeapOperators}
public
destructor ClassDesc();virtual;
...
destructor ClassDesc.ClassDesc();
begin
inherited; <-этот вызов может приводить к AV.
end;
Поскольку в Delphi указатель на VMT по отрицательным смещениям имеет виртуальные методы. В том числе и на начальный деструктор.
В С++ этот виртуальный деструктор будет расположен по положительным смешениям. В этом будет не соответствие.
ПЗ (2008-08-08 20:13:00) [17]ОК, попробую. Что в прошлый раз обсудили, работает. В чем сейчас проблема – ХЗ:
Итак, имеем:class MyUtilityClassDesc
{
public:
virtual void AAA() {}// – ХЗ №1
virtual void BBB() {}// – ХЗ №2
virtual ~MyUtilityClassDesc() { }//Деструктор. Конструктора нет!
virtual int IsPublic() { return 1; }
…
virtual const TCHAR * ClassName() { return _T("MyUtility"); }
virtual SClass_ID SuperClassID() { return 0x1020; }
virtual CClassID ClassID() { return CClass_ID(); }
virtual const TCHAR * Category() { return _T("Utility"); }
…
virtual int NumActionTables() { return 0; }
…
virtual DWORD InitialRollupPageState() { return 0x7fffffff; }
…
};
MyUtilityClassDesc.ClassID() возвращает экземпляр следующего класса:
class CClassID
{
private:
ULONG a,b;
public:
CClassID() { a = b = 0xffffffff; }
CClassID(const CClassID& cid) { a = cid.a; b = cid.b; }
CClassID(ulong aa, ulong bb) { a = aa; b = bb; }
ULONG PartA() const { return a; }
ULONG PartB() const { return b; }
void SetPartA( ulong aa ) { a = aa; }
void SetPartB( ulong bb ) { b = bb; }
int operator==(const CClassID& cid) { return (a==cid.a&&b==cid.b);}
int operator!=(const CClassID& cid) const { return (a!=cid.a||b!=cid.b); }
CClassID& operator=(const CClassID& cid) { a=cid.a; b = cid.b; return (*this); }
bool operator
oxffff (2008-08-08 12:12:00) [18]
>
> Все бы хорошо, но после вызова InitialPageRollupState происходит
> крэш! Как мне найти ошибку? Не наврал ли я с возвращением
> Pchar`ов и ClaccID()? Сам класс TClassID - простейший -
> два поля и несколько методов для доступа к ним. Если надо
> - приведу отдельно. Help!
О, я как раз из отпуска приехал.
Вроде же разобрали как делать месяц назад.
Не мог бы ты сократить код до минимума с сохранением ошибки?
Сергей М. (2008-08-08 08:13:00) [19]
> на делфи тот же метод будет у меня возвращать объект моего
> аналога данного класса TClass_ID по значению?
Как бы ни осуществлялся возврат (а осуществляется он в делфи всегда по ссылке), дельфийские классы никаким боком не пересекаются с сишными.
ПЗ (2008-08-07 21:05:00) [20]Нет, там не COM-сервер, а к сожалению, именно классы в DLL :-( Документация есть, но не на API, а на эти самые классы. И заголовочники есть на С++. Ровно по ним все и делалось. Отсюда и cdecl везде – все методы должны иметь сишные правила вызова. Я извиняюсь за большой объем кода выше, там 90% методов для мебели сделаны. Значение имеют только то, что не в строчку записаны :-) ClassName, Category, ClassID, SuperClassID, InitialPageRollupState.
Имею следующее подозрение: Метод ClassDesc::ClassID() согласно документации и сишному заголовочнику, возвращает непосредственно экземпляр класса Class_ID. Не указатель на него, а по значению. Вопрос: а на делфи тот же метод будет у меня возвращать объект моего аналога данного класса TClass_ID по значению?
Сюда же – согласно документации, сам класс ClassDesc не имеет конструктора! Деструктор (виртуальный) есть, а конструктора нет. На делфи он есть по-умолчанию (от TObject). Это может сыграть?