-
Возник вопрос: Как вызывать в программе С++ методы класса, написанного на delphi в Dll? Всё пляшет от SDK для 3dsMAX, который, как известно, хочет, чтобы все плагины писались на MSVC. Провел эксперимент. Написал dll на delphi:
library MyDLL;
uses
SysUtils,
Classes;
type
TFoo= class
public
function AFoo:Char;virtual;cdecl;
end;
var Foo:TFoo;
PFoo:^TFoo;
function TFoo.AFoo:Char;
begin
Result:='A';
end;
function Func1:Pointer;
begin
PFoo:=@Foo;
Result:=PFoo;
end;
exports
Func1;
begin
Foo:=TFoo.Create;
end.
Написал клиента на Delphi, проверил, работает: экспортируемая функция возвращает указатель на переменную класса TFoo, чей метод успешно вызывается. Попробовал написать клиента на CPP:
#include "stdafx.h"
class CFoo
;
typedef void* (__cdecl* PFUNC)();
int main(int argc, char* argv[])
Вылетает на вызове метода. Неужели настолько отличаются бинарники, что нельзя обмануть CPP и вызвать метод дельфевого класса?
-
> [0] ПЗ (08.07.08 20:22)
обектные модели в Delph и C++ несколько отличаются друг от друго, поэтому нельзя вот так просто взять и приравнять. Но вызвать думаю можно, но как просто статичную функцию, естесственно доступа к полям не будет.
-
Доступ к полям будет, если научиться при обращении к функции передавать в качестве дополнительного параметра адрес объекта. Но это надо посмотреть по окну CPU как там все организовано. По моему адрес объекта - первый параметр.
-
> [2] palva © (08.07.08 20:47)
это да, но прийдется вручную организовывать объектную модель Делфи на C++ ) но в качестве частного решения можно конечно упростить и просто жестко прошить смещения.
-
Можно попытаться использовать интерфейсы - вот у них всё одинаково
-
> ПЗ (08.07.08 20:22)
У тебя ошибка в коде.
> function Func1:Pointer; > begin > PFoo:=@Foo; > Result:=PFoo; > end;
Ты передаешь двойной указатель на объект.
Нужно просто
function Func1:Pointer; begin Result:=Foo; end;
-
Также естественным условием interoperability является гарантия обеспечения смещение VMT равным 0. Поэтому объявление хотя бы одного pure virtual метода должно быть первым. И только после этого объявлять поля. Правда возможно придется поплясать с выравниванием. :)
-
Только использование COM-интерфейсов гарантирует совместимость.
-
> Также естественным условием interoperability является гарантия > обеспечения смещение VMT равным 0.
- для класса это гарантированно, но лучше делать "по правильному": TXFoo= class
public
function AFoo:Char;virtual;abstract;cdecl;
end;
TFoo= class(TXFoo)
private
..........
public
function AFoo:Char;override;cdecl;
end; virtual char AFoo()=0; - соглашение о вызове зависит от ключей компиляции обычно это __thiscall или __fastcall, поэтому: virtual char __cdecl AFoo()=0;
-
> han_malign © (09.07.08 12:20) [8] > > > Также естественным условием interoperability является > гарантия > > обеспечения смещение VMT равным 0. > > - для класса это гарантированно, У классов С++ смещение плавающее поэтому, чтобы оно было такое как нужно для interoperability с Delphi необходимо объявить С++ виртуальный метод первым до объявление полей класса.
>но лучше делать "по правильному":
> > TXFoo= class > public > function AFoo:Char;virtual;abstract;cdecl; > end;
> TFoo= class(TXFoo) > private > .......... > public > function AFoo:Char;override;cdecl; > end; >
И что же здесь правильного?
-
Да действительно, с Result:=Foo MSVC DLLку заглотил! Простейший случай заработал, спасибо! Насчет полей, вроде, можно не заморачиваться. В классах 3dsMSAX поля не значатся, весь обмен информацией через методы. Но есть засада: не все методы у них виртуальные. Как быть? Обычные методы MSVC заглотит?
А как быть с наследованием? SDK для C++ содержит целую иерархию классов в своих сишных LIBах. Переписав все методы в один класс на делфи (с учетом соглашения о вызовах), будет ли это аналогично наследованию с точки зрения клиентской стороны (3dsMAX\MSVC)?
ЗЫ. COM оно к сожалению тоже не поддерживает. Autodesk все еще в каменном веке живет :-) Если б поддерживало, проблем бы не было.
-
> Как быть? Обычные методы MSVC заглотит?
Cвязывание обычных методов придется делать через прямой их экспорт в виде сигнатура метода + первый параметр указатель на объект + calling convention. Либо через виртуальные обертки.
> А как быть с наследованием? SDK для C++ содержит целую иерархию > классов в своих сишных LIBах. Переписав все методы в один > класс на делфи (с учетом соглашения о вызовах), будет ли > это аналогично наследованию с точки зрения клиентской стороны > (3dsMAX\MSVC)?
Наследование - это прямая агрегация с совпадающим временем жизни. Главное чтобы VMT у С++ классов был на месте. В противном случае все равно все решаемо.
-
> А как быть с наследованием? SDK для C++ содержит целую иерархию > классов в своих сишных LIBах. Переписав все методы в один > класс на делфи (с учетом соглашения о вызовах), будет ли > это аналогично наследованию с точки зрения клиентской стороны > (3dsMAX\MSVC)?
Ничего тебе не мешает сделать туже иерархию в Delphi, несмотря даже на множественное наследование (сделаешь как одиночное в порядке следования виртуальных методов в VMT). Далее виртуальное наследование С++ тоже не проблема, смысл в том агрегат разделяется. И обращение к нему косвенное через другую таблицу vbptr.
> ЗЫ. COM оно к сожалению тоже не поддерживает. Autodesk все > еще в каменном веке живет :-) Если б поддерживало, проблем > бы не было.
COM - это обыкновенный указатель на абстрактный класс. С семантикой подсчета ссылок. То есть все тоже самое.
-
Так, я понял, что я не первый, кто этим занимается, и это радует. Попробую задавать вопросы более последовательно. Итак, имею класс на пасе: TFoo= class public function AFoo:Char;virtual;cdecl; function BFoo:Char;virtual;cdecl; function DFoo:Char;cdecl; end; Хочу загнать его в DLL и вызвать из клиента, написанного на MSVC (в идеале вызывать будет 3dsMAX). Вопросы: а) Какие особенности надо соблюдать при написании пас-dll б) Как правильно определить заголовок класса на CPP ? Попробовал class CFoo
;
}; - DFoo не работает :-( Где наврал?
-
> Так, я понял, что я не первый, кто этим занимается, и это > радует.
Я этим не занимался. :)
Однако внимательно ли ты читал мой пост? oxffff © (09.07.08 21:34) [11]
1.
TFoo= class public function AFoo:Char;virtual;cdecl; function BFoo:Char;virtual;cdecl; function DFooImpl:Char;cdecl; function DFoo:Char;cdecl;virtual;cdecl; begin result:=DFooImpl; end; end;
2.TFoo= class public function AFoo:Char;virtual;cdecl; function BFoo:Char;virtual;cdecl; function DFoo:Char;cdecl; end;
function DFooExport(obj:TFoo;ParamList):Res;cdecl; begin result:=obj.DFoo(ParamList); end;
exports DFooExport name 'DFoo';
C++
__declspec ....DFooExternal { public: virtual char AFoo()=0; virtual char BFoo()=0; char DFoo() { DFooExternal(this ......... }; };
-
Oxffff:Не получается так. Заголовочники классов предопределены SDK и менять я их не смогу. В реальности мне надо будет реализовать на делфи нечто в таком роде:
#ifdef BLD_PARAMBLK2
# define PB2Export __declspec( dllexport )
#else
# define PB2Export __declspec( dllimport )
#endif
class ClassDesc2 : public ClassDesc
void SetEParamDlg(IAutoEParamDlg* dlg)
Tab<IParamMap2*>& GetParamMaps()
public:
PB2Export ClassDesc2();
PB2Export ~ClassDesc2();
PB2Export void ResetClassParams(BOOL fileReset);
PB2Export int NumParamBlockDescs()
PB2Export ParamBlockDesc2* GetParamBlockDesc(int i)
PB2Export ParamBlockDesc2* GetParamBlockDescByID(BlockID id);
PB2Export ParamBlockDesc2* GetParamBlockDescByName(MCHAR* name);
PB2Export void AddParamBlockDesc(ParamBlockDesc2* pbd);
PB2Export void ClearParamBlockDescs()
PB2Export void BeginEditParams(IObjParam *ip, ReferenceMaker* obj, ULONG flags, Animatable *prev);
PB2Export void EndEditParams(IObjParam *ip, ReferenceMaker* obj, ULONG flags, Animatable *prev);
PB2Export void InvalidateUI();
PB2Export void InvalidateUI(ParamBlockDesc2* pbd);
PB2Export void InvalidateUI(ParamBlockDesc2* pbd, ParamID id, int tabIndex=-1); PB2Export void MakeAutoParamBlocks(ReferenceMaker* owner);
PB2Export int NumParamMaps()
PB2Export IParamMap2* GetParamMap(int i)
PB2Export IParamMap2* GetParamMap(ParamBlockDesc2* pbd, MapID map_id = 0);
PB2Export void SetUserDlgProc(ParamBlockDesc2* pbd, MapID map_id, ParamMap2UserDlgProc* proc=NULL);
inline void SetUserDlgProc(ParamBlockDesc2* pbd, ParamMap2UserDlgProc* proc=NULL)
PB2Export ParamMap2UserDlgProc* GetUserDlgProc(ParamBlockDesc2* pbd, MapID map_id = 0);
PB2Export IAutoMParamDlg* CreateParamDlgs(HWND hwMtlEdit, IMtlParams *imp, ReferenceTarget* obj);
PB2Export IAutoMParamDlg* CreateParamDlg(BlockID id, HWND hwMtlEdit, IMtlParams *imp, ReferenceTarget* obj, MapID mapID=0);
PB2Export IAutoEParamDlg* CreateParamDialogs(IRendParams *ip, SpecialFX* obj); PB2Export IAutoEParamDlg* CreateParamDialog(BlockID id, IRendParams *ip, SpecialFX* obj, MapID mapID=0); PB2Export void MasterDlgDeleted(IAutoMParamDlg* dlg);
PB2Export void MasterDlgDeleted(IAutoEParamDlg* dlg);
PB2Export IAutoMParamDlg* GetMParamDlg()
PB2Export IAutoEParamDlg* GetEParamDlg()
PB2Export void RestoreRolloutState();
PB2Export ParamID LastNotifyParamID(ReferenceMaker* owner, IParamBlock2*& pb);
PB2Export void Reset(ReferenceMaker* owner, BOOL updateUI = TRUE, BOOL callSetHandlers = TRUE);
PB2Export void GetValidity(ReferenceMaker* owner, TimeValue t, Interval &valid);
};
Он наследуется от другого класса(у которого все методы виртуалтьные), от него еще кто-то наследуется. Клиент (MAX) получает ссылку на экземпляр класса и сам вызывает методы как хочет. Вопрос: как реализовать этот класс не на сях, а на делфи, и скормить его сишному клиенту (MAX)? Универсальность мне не нужна и многие методы, думаю, можно заглушками сделать, т.к. они вряд ли реально будут все вызываться.
-
> ПЗ (11.07.08 20:26) [15]
Я честно говоря не могу понять что тебе необходимо? То ли вызов метода Pascal объекта из С++, то ли наоборот. :) Давай "чистый" пример.
-
> Вопрос: как реализовать этот класс не на сях, а на делфи, > и скормить его сишному клиенту (MAX)?
Тебе нужно сделать привязку внешней процедуры (сигнатура метода + параметер ссылка на объект) Delphi к методу С++(просто сигнатура).
-
> Всё пляшет от SDK
Не-а..
Все "пляшет" от головы того , кто трындит тут про тот или иной SDK.
Ты, чудо, отладчик-то пошльзовал ?
Ну и что он, отладчик говорит про соглашение о вызове ?
Только не говори, мол. я твоя не понимайт - сразу Орешник подрастет)..
-
oxffff: Не совсем так. SDK, как известно, представляет собой набор заголовочников *.h и LIBов, где предопределена некая структура классов (пример я выше привел). Предполагается, что программист, подключая данные заголовочники, напишет на MSVC DLL, в которой, собственно, реализует экземпляры данных классов. 3ds подключает DLL, получает ссылку на экземпляр нужного ей класса и работает с ним. В задаче спрашивается - как написать эту DLL не на C++, а на Delphi?
Именно для этого я выше написал эксперименты, с которыми вы мне помогли. Теоретически должно работать. Сейчас заткнулся на невиртуальных методах. Проблему выше описал.
-
Собрался с мыслями. Попробую уточнить задачу: -Есть сторонняя программа (3dsMAX), написанная на С++ -Есть структура класса (ClassDesc2, см выше), реализация которого надо зашить в DLL
Вопрос: Как сделать эту DLL на Delphi? Уточнение: Как реализовать на Delphi невиртуальные методы, чтобы 3ds их смогла вызвать обычным для себя образом?
|