Конференция "Основная" » Delphi, Dll, классы, C++ [D7, WinXP]
 
  • ПЗ (08.07.08 20:22) [0]
    Возник вопрос:
    Как вызывать в программе С++ методы класса, написанного на delphi в Dll?
    Всё пляшет от SDK для 3dsMAX, который, как известно, хочет, чтобы все плагины писались на MSVC. Провел эксперимент. Написал dll на delphi:

    library MyDLL;
    uses
     SysUtils,
     Classes;

    {$R *.res}
    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
    {
    public:
    virtual char AFoo()=0;
    }
    ;

    typedef void* (__cdecl* PFUNC)();

    int main(int argc, char* argv[])
    {
    printf("Hello World!\n");

    HMODULE hLibrary=LoadLibrary("MyDLL.Dll");
    PFUNC Func1= (PFUNC) GetProcAddress(hLibrary,"Func1");

    CFoo* Foo=(CFoo*)Func1();
    char ch=Foo->AFoo();
    printf(&ch);

    FreeLibrary(hLibrary);


    return 0;
    }



    Вылетает на вызове метода. Неужели настолько отличаются бинарники, что нельзя обмануть CPP и вызвать метод дельфевого класса?
  • Eraser © (08.07.08 20:27) [1]
    > [0] ПЗ   (08.07.08 20:22)

    обектные модели в Delph и C++ несколько отличаются друг от друго, поэтому нельзя вот так просто взять и приравнять. Но вызвать думаю можно, но как просто статичную функцию, естесственно доступа к полям не будет.
  • palva © (08.07.08 20:47) [2]
    Доступ к полям будет, если научиться при обращении к функции передавать в качестве дополнительного параметра адрес объекта. Но это надо посмотреть по окну CPU как там все организовано. По моему адрес объекта - первый параметр.
  • Eraser © (09.07.08 00:27) [3]
    > [2] palva ©   (08.07.08 20:47)

    это да, но прийдется вручную организовывать объектную модель Делфи на C++ ) но в качестве частного решения можно конечно упростить и просто жестко прошить смещения.
  • icWasya © (09.07.08 09:14) [4]
    Можно попытаться использовать интерфейсы - вот у них всё одинаково
  • oxffff © (09.07.08 11:18) [5]

    > ПЗ   (08.07.08 20:22)  


    У тебя ошибка в коде.


    > function Func1:Pointer;
    > begin
    >     PFoo:=@Foo;
    >     Result:=PFoo;
    > end;


    Ты передаешь двойной указатель на объект.

    Нужно просто

    function Func1:Pointer;
    begin
    Result:=Foo;
    end;
  • oxffff © (09.07.08 11:26) [6]
    Также естественным условием interoperability является гарантия обеспечения смещение VMT равным 0.
    Поэтому объявление хотя бы одного pure virtual метода должно быть первым.
    И только после этого объявлять поля.
    Правда возможно придется поплясать с выравниванием. :)
  • Dimka Maslov © (09.07.08 11:37) [7]
    Только использование COM-интерфейсов гарантирует совместимость.
  • han_malign © (09.07.08 12:20) [8]

    > Также естественным условием 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;
  • oxffff © (09.07.08 14:22) [9]

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

    И что же здесь правильного?
  • ПЗ (09.07.08 20:37) [10]
    Да действительно, с Result:=Foo MSVC DLLку заглотил! Простейший случай заработал, спасибо!
    Насчет полей, вроде, можно не заморачиваться. В классах 3dsMSAX поля не значатся, весь обмен информацией через методы. Но есть засада: не все методы у них виртуальные. Как быть? Обычные методы MSVC заглотит?

    А как быть с наследованием? SDK для C++ содержит целую иерархию классов в своих сишных LIBах. Переписав все методы в один класс на делфи (с учетом соглашения о вызовах), будет ли это аналогично наследованию с точки зрения клиентской стороны (3dsMAX\MSVC)?

    ЗЫ. COM оно к сожалению тоже не поддерживает. Autodesk все еще в каменном веке живет :-) Если б поддерживало, проблем бы не было.
  • oxffff © (09.07.08 21:34) [11]

    > Как быть? Обычные методы MSVC заглотит?


    Cвязывание обычных методов придется делать через прямой их экспорт в виде сигнатура метода + первый параметр указатель на объект + calling convention. Либо через виртуальные обертки.


    > А как быть с наследованием? SDK для C++ содержит целую иерархию
    > классов в своих сишных LIBах. Переписав все методы в один
    > класс на делфи (с учетом соглашения о вызовах), будет ли
    > это аналогично наследованию с точки зрения клиентской стороны
    > (3dsMAX\MSVC)?


    Наследование - это прямая агрегация с совпадающим временем жизни.
    Главное чтобы VMT у С++ классов был на месте.
    В противном случае все равно все решаемо.
  • oxffff © (09.07.08 21:47) [12]

    > А как быть с наследованием? SDK для C++ содержит целую иерархию
    > классов в своих сишных LIBах. Переписав все методы в один
    > класс на делфи (с учетом соглашения о вызовах), будет ли
    > это аналогично наследованию с точки зрения клиентской стороны
    > (3dsMAX\MSVC)?


    Ничего тебе не мешает сделать туже иерархию в Delphi, несмотря даже на множественное наследование (сделаешь как одиночное в порядке следования виртуальных методов в VMT).
    Далее виртуальное наследование С++ тоже не проблема, смысл в том агрегат разделяется. И обращение к нему косвенное через другую таблицу vbptr.


    > ЗЫ. COM оно к сожалению тоже не поддерживает. Autodesk все
    > еще в каменном веке живет :-) Если б поддерживало, проблем
    > бы не было.


    COM - это обыкновенный указатель на абстрактный класс. С семантикой подсчета ссылок. То есть все тоже самое.
  • ПЗ (10.07.08 20:18) [13]
    Так, я понял, что я не первый, кто этим занимается, и это радует. Попробую задавать вопросы более последовательно. Итак, имею класс на пасе:
     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
    {
    public:
    virtual char AFoo()=0;
    virtual char BFoo()=0;
    char DFoo() {return 0;}
    ;
    };

    - DFoo не работает :-( Где наврал?
  • oxffff © (10.07.08 22:07) [14]

    > Так, я понял, что я не первый, кто этим занимается, и это
    > радует.


    Я этим не занимался.  :)

    Однако внимательно ли ты читал мой пост?
    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 .........  };
    };
  • ПЗ (11.07.08 20:26) [15]
    Oxffff:Не получается так. Заголовочники классов предопределены SDK и менять я их не смогу. В реальности мне надо будет реализовать на делфи нечто в таком роде:

    #ifdef BLD_PARAMBLK2
    # define PB2Export __declspec( dllexport )
    #else
    # define PB2Export __declspec( dllimport )
    #endif
    class ClassDesc2 : public ClassDesc
    {
    private:
     Tab<ParamBlockDesc2*> pbDescs;  // parameter block descriptors
     Tab<IParamMap2*>  paramMaps;  // any current param maps
     IAutoMParamDlg*   masterMDlg;  // master material/mapParamDlg if any
     IAutoEParamDlg*   masterEDlg;  // master EffectParamDlg if any

    protected:
     void SetMParamDlg(IAutoMParamDlg* dlg) { masterMDlg = dlg; }

     void SetEParamDlg(IAutoEParamDlg* dlg) { masterEDlg = dlg; }
     Tab<IParamMap2*>& GetParamMaps() { return paramMaps; }

    public:
     PB2Export    ClassDesc2();
     PB2Export      ~ClassDesc2();
     PB2Export void   ResetClassParams(BOOL fileReset);
     PB2Export int   NumParamBlockDescs() { return pbDescs.Count(); }
     PB2Export ParamBlockDesc2* GetParamBlockDesc(int i) { return pbDescs[i]; }
     PB2Export ParamBlockDesc2* GetParamBlockDescByID(BlockID id);
     PB2Export ParamBlockDesc2* GetParamBlockDescByName(MCHAR* name);
     PB2Export void   AddParamBlockDesc(ParamBlockDesc2* pbd);
     PB2Export void   ClearParamBlockDescs() { pbDescs.ZeroCount(); }
     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); // nominated param
     PB2Export void   MakeAutoParamBlocks(ReferenceMaker* owner);
     PB2Export int   NumParamMaps() { return paramMaps.Count(); }
     PB2Export IParamMap2* GetParamMap(int i) { return paramMaps[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) { SetUserDlgProc(pbd, 0, proc); }
     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); // mjm - 07.06.00
     PB2Export IAutoEParamDlg* CreateParamDialog(BlockID id, IRendParams *ip, SpecialFX* obj, MapID mapID=0); // mjm - 07.06.00
     PB2Export void   MasterDlgDeleted(IAutoMParamDlg* dlg);
     PB2Export void   MasterDlgDeleted(IAutoEParamDlg* dlg);
     PB2Export IAutoMParamDlg* GetMParamDlg() { return masterMDlg; }
     PB2Export IAutoEParamDlg* GetEParamDlg() { return masterEDlg; }
     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)? Универсальность мне не нужна и многие методы, думаю, можно заглушками сделать, т.к. они вряд ли реально  будут все вызываться.
  • oxffff © (11.07.08 21:31) [16]

    > ПЗ   (11.07.08 20:26) [15]


    Я честно говоря не могу понять что тебе необходимо?
    То ли вызов метода Pascal объекта из С++, то ли наоборот. :)
    Давай "чистый" пример.
  • oxffff © (11.07.08 21:43) [17]

    > Вопрос: как реализовать этот класс не на сях, а на делфи,
    >  и скормить его сишному клиенту (MAX)?


    Тебе нужно сделать привязку внешней процедуры (сигнатура метода + параметер ссылка на объект) Delphi к методу С++(просто сигнатура).
  • Сергей М. © (12.07.08 18:55) [18]

    > Всё пляшет от SDK


    Не-а..

    Все "пляшет" от головы того , кто трындит тут про тот или иной SDK.

    Ты, чудо, отладчик-то пошльзовал ?

    Ну и что он, отладчик говорит про соглашение о вызове ?

    Только не говори, мол. я твоя не понимайт - сразу Орешник подрастет)..
  • ПЗ (12.07.08 20:05) [19]
    oxffff: Не совсем так. SDK, как известно, представляет собой набор заголовочников *.h и LIBов, где предопределена некая структура классов (пример я выше привел). Предполагается, что программист, подключая данные заголовочники, напишет на MSVC DLL, в которой, собственно, реализует экземпляры данных классов. 3ds подключает DLL, получает ссылку на экземпляр нужного ей класса и работает с ним.
    В задаче спрашивается - как написать эту DLL не на C++, а на Delphi?

    Именно для этого я выше написал эксперименты, с которыми вы мне помогли. Теоретически должно работать. Сейчас заткнулся на невиртуальных методах. Проблему выше описал.
  • ПЗ (18.07.08 21:00) [20]
    Собрался с мыслями. Попробую уточнить задачу:
    -Есть сторонняя программа (3dsMAX), написанная на С++
    -Есть структура класса (ClassDesc2, см выше), реализация которого надо зашить в DLL

    Вопрос: Как сделать эту DLL на Delphi?
    Уточнение: Как реализовать на Delphi невиртуальные методы, чтобы 3ds их смогла вызвать обычным для себя образом?
 
Конференция "Основная" » Delphi, Dll, классы, C++ [D7, WinXP]
Есть новые Нет новых   [134491   +13][b:0][p:0.004]