Получение класса из dll, написанной на C++

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Получение класса из dll, написанной на C++

Сообщение mildok » 08.05.2010 15:03:09

Версия Lazarus'a: lazarus-0.9.28.2-fpc-2.2.4-win32.exe.
OS: Windows XP SP3.
Delphi: 2010
C++:
CodeBlock 10.5
VisualStudio: 2008
.NeT Framework установленный в системе: 3.5 sp1.
Я начинающий программист. Прошу прощения если такие посты уже были - я не нашел на форуме. Лично я более разбираюсь в Pascal, чем в C++. Но, к сожалению очень большое количество текстов написано на C++. (По большей части меня интересует Windows Development Kit) :(
В интернете, как я почитал, существует множество способов импорта класса в Delphi из dll, написанной в VisualC++. Из тех способов на мой взгляд самым быстрым является использование параметра __declspec(dllexport) перед именем класса и создание функции, с помощью которой создаётся экземпляр класса и в дальнейшем обращение к этому экземпляру. После компиляции на Visual C++, получаем dll которую в дальнейшем пытаюсь использовать.
Проблема: Использование dll в Delphi 2010 происходит нормально, однако при использовании данной библиотеки в Lazarus'е, компиляция и компоновка проходят нормально, но при запуске полученной программы происходит вот что:

ok
was import
exception at 006E0049:
Access violation.

Вот код на C++:
Код: Выделить всё
//other_npob.h
class __declspec(dllexport) CIncomeImp
{
public:
   virtual double __stdcall GetIncome( double aNetto );
   virtual void   __stdcall FreeObject();
   int a;
};

extern "C" __declspec(dllexport) CIncomeImp *CreateIncome();

// other_npob.cpp: определяет процедуры инициализации для DLL.
#include "stdafx.h"
#include "other_npob.h"

double __stdcall CIncomeImp::GetIncome( double aNetto )
{
   printf("yryn");
   a=55;
   return aNetto;
}

void __stdcall CIncomeImp::FreeObject()
  {
    delete this ;
  }

CIncomeImp *CreateIncome()
{
    return new CIncomeImp ;
}


Код на Delphi:
Код: Выделить всё
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  unit1 in 'unit1.pas';
var
  incomeRef: IIncome; //member of the reference
  cIncome: Double;
  function CreateIncome: IIncome; cdecl; external('tmp.dll');

begin
  { TODO -oUser -cConsole Main : Insert code here }
  incomeRef:=createIncome;
  writeln('was import');
  writeln(incomeRef.a);
  cIncome:=incomeRef.GetIncome(22);
  writeln(cIncome);
  writeln(incomeRef.a);
end.

//unit1.pas
unit Unit1;

interface
type
IIncome = class
public
   a:integer;
   function GetIncome(const aNetto: double): double; virtual; stdcall; abstract;
   procedure FreeObject; virtual; stdcall; abstract;
end;
     
implementation

end.

Код на Lazarus'е:
Код: Выделить всё
program project1;

{$mode delphi}

uses
  ShareMem,
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, SysUtils, CustApp, unit1
  { you can add units after this };

type

  { TMyApplication }

  TMyApplication = class(TCustomApplication)
  protected
    procedure DoRun; override;
  public
    constructor Create(TheOwner: TComponent); override;
  end;

{ TMyApplication }
  function CreateIncome: IIncome; cdecl; external('other_npob.dll');

procedure TMyApplication.DoRun;
var
  ErrorMsg: String;

  incomeRef: IIncome; //member of the reference
  cIncome: Double;
begin

  { add your program here }
  writeln('ok');
  incomeRef:=createIncome;
  writeln('was import');
  cIncome:=incomeRef.GetIncome(22);
  // stop program loop
  Terminate;
end;

constructor TMyApplication.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  StopOnException:=True;
end;

var
  Application: TMyApplication;
{$IFDEF WINDOWS}{$R project1.rc}{$ENDIF}

begin
  Application:=TMyApplication.Create(nil);
  Application.Title:='My Application';
  Application.Run;
  Application.Free;
end.

//unit1.pas
unit unit1;

{$mode delphi}

interface

uses
  ShareMem,Classes, SysUtils;

type
IIncome = class
public
   a:integer;
   function GetIncome(const aNetto: double): double; virtual; stdcall; abstract;
   procedure FreeObject; virtual; stdcall; abstract;
end;

implementation

end.


Заранее благодарю за любую помощь. :D

Добавлено спустя 8 часов 19 минут 46 секунд:
Эх...Никто меня не любит, отвечать мне не хочет, все меня игнорируют... :cry: ...Ну тогда сам с собой буду разговаривать.. :!: .. :)
Насколько я разобрался во усём виновата Virtual Method Table..Редиска...
Насколько можно судить из http://en.wikipedia.org/wiki/Virtual_method_table указатель на первый виртуальный метод в VMT си плюса начинается с +0, в то время как в Lazarus'е, судя по статье http://www.freepascal.org/docs-html/pro ... 02-2080027 указатель на первый метод начинается толи с +16, толи с +20 (подскажите кстати откуда точно)..А предыдущие будут 20(или 16) байт будут заполнены служебной инфой...
Отсюда следует несколько простых, аки наковальня, решений проблем возникших у меня с Lazarus'ом:

1) Перейти на Delphi. Не подходит - я жадный и столько денег, сколько просят за эту чудесную программу не дам.. :x
2) Использовать "обёртки" и через них вызывать методы класса. В моём прожекте (слово-то какое :? ) можно не торопиться, поэтому пока что, не хочу заниматься этим..Хм..Интересным занятием.. :) ..
3) Найти то маленькое различие ( :roll: ) между работой Delphi и Lazarus'а и устранить его. Честно говоря я знаю, что я - негодяй, подлец и вообще не понимаю сколько тут работы и ничего не смогу сделать, потому что мало что смыслю в профессиональном программировании на ООП.. :D ..Полностью согласен и принимаю данное замечание, надеюсь что к нему мы больше не вернёмся.. :wink: ..
Итак, как мне кажется я нашёл одно различие в работе этих великолепных сред программирования - это VMT таблицы. У меня возник такой вопрос а можно ли "подогнать" таблицу VMT C++ и Lazarus'а. К примеру добавить в код C++ несколько "методов-болванок", так чтобы полезные нам методы класса начинались как раз-таки с +16(или 20) байта? :shock:
И второй вопрос можно ли как-нибудь посмотреть VMT, которая формируется в исполняемой программе?
Кто-нибудь помогите.. :evil: .. :)

Итак, несмотря ни на что (на нескольких форумах меня прямо послали в...интернет изучать обёртки, кое-где вообще заявили - проблема неразрешима, а кое-где вообще ничего не отвечают :wink: )..Я всё-таки разрешил проблему в одну строчку... :D
Ещё раз скажу, что программист я начинающий, поэтому ногами меня не бить и всё описанное далее ИМХО..!..Проблему я написал выше. Решение в общем-то, как оказалось такое же простое как и математика, к сожалению не такое постоянное. И - я угадал, действительно, подогнать таблицу VMT C++ к таблице Lazarusa можно. После создания Dll'кой экземпляра класса, где-то в куче по формату vmt си плюса класс начинается с +0, однако по формату vmt Lazarus'а (кто-нибудь объясните мне, кстати - зачем Lazarus так с ней поступил (с таблицей) ) pointer на класс будет начинаться с +12...Отсюда получаем, что после возврата Dll'кой указателя (на область памяти с которой начинается экзэмпляр класса) он указывает на +0. Lazarus же топает по этому адресу...думает...прибавляет свои +12 байт и думает, что класс тута с +12, что конечно неправда.. Естественно, что решением проблемы является простая математика:
dec(PInteger(incomeRef)^, $12);
И теперь мы должны иметь "правильный" указатель на класс..Но не тут-то было - вот нету тута класса и усё :( ...Поэтому делаем Цикл и просматриваем всю кучу до -88 (-88 потому что http://www.rsdn.ru/article/Delphi/delphiabs.xml последний указанный -76, и вероятно не просто так ну и + наши 12байт = 88 байт проверки) А просматриваем - банально проверкой:
is Нужный_Нам_Класс
Ну это общее решение возникшей проблемы, т.к. как мне кажется в будущем опять обновят компилятор C++, опять сменят компилятор Lazarus, че-нить вгрызут в сам Windows и опять будет другое смещение. В моём случае верное смещение оказалось на 50 байтов ниже, чем мне подсовывал (как я понимаю) компилятор Lazarus'а. Т.е. :
dec(PInteger(incomeRef)^, $50);
Ну да, так ничего не понять, наверно будет начинающему программисту, так что вот полный код:
Код на VisualC++:
Код: Выделить всё
//other_npob.h
//
class __declspec(dllexport) CIncomeImp
{
public:
   virtual int __stdcall GetIncome( int aNetto );
   virtual void __stdcall FreeObject();
};

extern "C" __declspec(dllexport) CIncomeImp *CreateIncome();

// other_npob.cpp: определяет процедуры инициализации для DLL.
//
#include "stdafx.h"
#include "other_npob.h"

int __stdcall CIncomeImp::GetIncome( int aNetto )
{
   printf("yry\n");
   return aNetto;
}

void __stdcall CIncomeImp::FreeObject()
  {
    delete this ;
  }

CIncomeImp *CreateIncome()
{   
   return new CIncomeImp;
}

//other_npob.def
; other_npob.def: объявляет параметры модуля для DLL.

LIBRARY      "other_npob"

EXPORTS
    ; Сюда можно направлять явные операции экспорта
GetIncome

Код на Lazarus'е:
Код: Выделить всё
//unit1.pas
unit unit1;
interface

type
IIncome = class
public
   function GetIncome(aNetto: integer): integer; virtual; stdcall; abstract;
   procedure FreeObject; virtual; stdcall; abstract;
end;
  function CreateIncome():IIncome; stdcall; external('other_npob.dll');
implementation

end.

//project1.lpr
program project1;
uses
  unit1;

var
  incomeRef: IIncome;
  cIncome: integer;

begin
  writeln('ok');
  incomeRef:=createIncome();      //создаём экзэмпляр класса
  writeln('was import');           //пишем на экран, что усё ОК
  dec(PInteger(incomeRef)^, $50);  //из-за разных форматов таблиц VMT (ИМХО) приходится извращаться в куче
  cIncome:=incomeRef.GetIncome(6); //собственно проверяем работу класса
  writeln(cIncome);                //проверяем что занесено в проверочную переменную
end.

У меня работает... :D ..

Итак повторюсь решение в $50 байтиков конкретное, для данных компиляторов(Лазаруса и C++). Для решения общего смотри пост выше.. :) ..Хотя я уверен - что это плохой и ужасный стиль программирования..Но всё-таки..
Вот и усё.. :D ..Большая просьба модераторам: то что я написал я не нашёл в интернете на русском языке "всё сразу и вместе", однако видел очень много вопросов подобного рода в интернете как на русском языке, так и на английском:

http://community.freepascal.org:10000/b ... m_id=24090
http://www.mail-archive.com/fpc-devel@l ... 01598.html
http://community.freepascal.org:10000/b ... m_id=24097
viewtopic.php?f=14&t=3628
Да и молчание по этому вопросу здесь, где я задал этот животрепещущий вопрос о многом говорит.. :?
Кстати, кто мне со знанием дела объяснит почему именно +$50 байтов, тому...морс (что? почему не пиво? А пиво - вредно.. :D )

:mrgreen: Большая просьба модераторам: как мне кажется вы очень обяжете Многих и Многих новичков как я, если внесёте хотя бы часть из того бреда, что я написал выше (я понимаю, что скорей всего написал половину - неправильно :( ) в FAQ. :( Это, действительно печально, когда любимая бесплатная программа отказывается делать то же, что её платный аналог..
Но главное - мы победили и всё работает.. :!: :D Всем удачи!..
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 20.01.2011 20:03:31

Предыдущее сообщение - уже большое поэтому напишу пониже :D . Опять отвечаю сам себе...гыгыгы :D

Вобщем-то нашёл быстрый способ получения правильного смещения, т.к. в lazarus'е 0.9.29 естесственно смещение в $50 байтов не сработало, как и ожидалось...

Имхо решение на любую версию lazarus и fpc:
пишем где-нибудь в тексте программы vmtParent и удерживая Ctrl нажимаем на vmtParent. :wink: Мы перешли в objpash.inc, который находится по адресу: lazarus\fpc\2.5.1\source\rtl\inc\objpash.inc. Теперь ищем последнее упоминание vmt тута. В версии lazarus'а 0.9.29 с fpc версии 2.5.1 - это метод vmtToString
Вот про что я говорю :D :
Код: Выделить всё
       vmtInstanceSize         = 0;
       vmtParent               = sizeof(ptruint)*2;
       { These were negative value's, but are now positive, else classes
         couldn't be used with shared linking which copies only all data from
         the .global directive and not the data before the directive (PFV) }
       vmtClassName            = vmtParent+sizeof(pointer);
       vmtDynamicTable         = vmtParent+sizeof(pointer)*2;
       vmtMethodTable          = vmtParent+sizeof(pointer)*3;
       vmtFieldTable           = vmtParent+sizeof(pointer)*4;
       vmtTypeInfo             = vmtParent+sizeof(pointer)*5;
       vmtInitTable            = vmtParent+sizeof(pointer)*6;
       vmtAutoTable            = vmtParent+sizeof(pointer)*7;
       vmtIntfTable            = vmtParent+sizeof(pointer)*8;
       vmtMsgStrPtr            = vmtParent+sizeof(pointer)*9;
       { methods }
       vmtMethodStart          = vmtParent+sizeof(pointer)*10;
       vmtDestroy              = vmtMethodStart;
       vmtNewInstance          = vmtMethodStart+sizeof(pointer);
       vmtFreeInstance         = vmtMethodStart+sizeof(pointer)*2;
       vmtSafeCallException    = vmtMethodStart+sizeof(pointer)*3;
       vmtDefaultHandler       = vmtMethodStart+sizeof(pointer)*4;
       vmtAfterConstruction    = vmtMethodStart+sizeof(pointer)*5;
       vmtBeforeDestruction    = vmtMethodStart+sizeof(pointer)*6;
       vmtDefaultHandlerStr    = vmtMethodStart+sizeof(pointer)*7;
       vmtDispatch             = vmtMethodStart+sizeof(pointer)*8;
       vmtDispatchStr          = vmtMethodStart+sizeof(pointer)*9;
       vmtEquals               = vmtMethodStart+sizeof(pointer)*10;
       vmtGetHashCode          = vmtMethodStart+sizeof(pointer)*11;
       vmtToString             = vmtMethodStart+sizeof(pointer)*12; 

       { IInterface }
       S_OK          = 0;
       S_FALSE       = 1;
       E_NOINTERFACE = hresult($80004002);
.....

Как видите последний метод - vmtToString (дальше уже идут интерфейсы). Теперь высчитываем его vmtMethodStart+sizeof(pointer)*12 = 96 и прибавляем 4(4 - по-моему это сам размер указателя vmtToString), итого получаем 100 - это и есть нужное нам смещение
Т.е. код будет выглядеть теперь вот так:
Код: Выделить всё
dec(PInteger(mydriver)^, 100);

Где mydriver - это класс импортируемый из dll библиотеки. :D Вобщем у меня так получается импортировать класс для работы с зеркальным драйвером UVNC :D :D. За сим прощаюсь. Буду рад любым исправелниям бреда, написанного мной выше :wink:..Гыгыгы... :mrgreen:
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение Vadim » 21.01.2011 05:23:25

Есть одна проблема - и Вы её правильно заметили. Если микрософт опять что-нибудь изменит в своём компиляторе, то Ваш код уже работать не будет.
mildok писал(а):dec(PInteger(incomeRef)^, $50);

Константа ведь? А, по идее, эту величину надо находить поиском. И ещё одно глобальное осложнение - скомпилируйте dll'ку с помощью C++Builder или с помощью GCC и опять работать не будет.
Получается, что этой проблемой никто не занимается, потому что нет универсального решения для того пути, по которому Вы пошли.
И что же делать? Есть простой, но требующий доп. кода, путь решения - создавать болванку класса в Паскале. Так сделали, к примеру, для интерфейсных элементов Qt.
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Получение класса из dll, написанной на C++

Сообщение Odyssey » 21.01.2011 12:42:15

Имхо, топикстартеру, с его необычными интересами, прямая дорога пилить Linking with C++ code в FPC, которое сейчас Planned for later versions.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Получение класса из dll, написанной на C++

Сообщение fat365 » 22.04.2011 10:26:38

Огромное спасибо автору за

mildok писал(а):
Код: Выделить всё
dec(PInteger(mydriver)^, 100);



Больше 2 недель мучался, пока случайно не нашел этот пост.
fat365
незнакомец
 
Сообщения: 1
Зарегистрирован: 22.04.2011 10:23:01

Re: Получение класса из dll, написанной на C++

Сообщение stikriz » 22.04.2011 12:52:36

А что, если объявить интерфейс такой же как в сях плас плас в лазарусе, то присвоение указателя из ДЛЛ в переменную этого интерфейса не канает?
В ДЛЛ это же интерфейс, а не просто некий класс?
Аватара пользователя
stikriz
энтузиаст
 
Сообщения: 612
Зарегистрирован: 15.03.2006 09:37:47

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 07.05.2011 18:33:52

fat365, пожалуйста... я сам мучался, пока чё-нить понял.. :D Если смените компилятор Lazarus или fpc и у вас опять не будет работать, то решение описано мной выше =)...ну если это можно, конечно, назвать решением...

stikriz, если я вас правильно понял, то ничего не получится :( . Поэтому я и открыл этот топик.
Вы имеете ввиду что-то вроде этого?:
Делаем dll в VisualC++
Код: Выделить всё
//other_npob.h
//
class __declspec(dllexport) CIncomeImp
{
public:
   virtual int __stdcall GetIncome( int aNetto );
   virtual void __stdcall FreeObject();
};

extern "C" __declspec(dllexport) CIncomeImp *CreateIncome();

// other_npob.cpp: определяет процедуры инициализации для DLL.
//
#include "stdafx.h"
#include "other_npob.h"

int __stdcall CIncomeImp::GetIncome( int aNetto )
{
   printf("yry\n");
   return aNetto;
}

void __stdcall CIncomeImp::FreeObject()
  {
    delete this ;
  }

CIncomeImp *CreateIncome()
{   
   return new CIncomeImp;
}

//other_npob.def
; other_npob.def: объявляет параметры модуля для DLL.

LIBRARY      "other_npob"

EXPORTS
    ; Сюда можно направлять явные операции экспорта
GetIncome

Пытаемся импортировать класс из dll в Lazarus'е:
Код: Выделить всё
//unit1.pas
unit unit1;
interface

type
IIncome = class
public
   function GetIncome(aNetto: integer): integer; virtual; stdcall; abstract;
   procedure FreeObject; virtual; stdcall; abstract;
end;
  function CreateIncome():IIncome; stdcall; external('other_npob.dll');
implementation

end.

//project1.lpr
program project1;
uses
  unit1;

var
  incomeRef: IIncome;
  cIncome: integer;

begin
  writeln('ok');
  incomeRef:=createIncome();      //создаём экзэмпляр класса
  writeln('was import');           //пишем на экран, что усё ОК
  cIncome:=incomeRef.GetIncome(6); //собственно проверяем работу класса
  writeln(cIncome);                //проверяем что занесено в проверочную переменную
end.

Боюсь, что ничего не получится как я писал из-за разных vmt.
Поэтому нужно извращаться в памяти. Изменять указатель на класс в Lazarus'е вот таким образом:
Код: Выделить всё
......
  incomeRef:=createIncome();      //создаём экзэмпляр класса
  dec(PInteger(incomeRef)^, $50);  //изменяем указатель на класс в версии fpc 2.2.4 на $50 байтиков, а в версии 2.5.1 на 100 байтиков почему так - описал выше  :D
......

stikriz, в верху исходники попробуйте сами :D
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение stikriz » 08.05.2011 09:36:41

[/quote]
type
IIncome = class

А если так:
Код: Выделить всё
IIncome = interface
Аватара пользователя
stikriz
энтузиаст
 
Сообщения: 612
Зарегистрирован: 15.03.2006 09:37:47

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 08.05.2011 10:42:34

Продолжаем эксперименты :D
stikriz, Не получается :( .
Код dll'ки на VisualC++ тот же самый

Код имортирования класса в Lazarus'е:
unit1.pas:
Код: Выделить всё
//unit1.pas
unit unit1;
{$mode delhi}
interface

type
IIncome = interface
   function GetIncome(aNetto: integer): integer;stdcall;
   procedure FreeObject; stdcall;
end;
  function CreateIncome():IIncome; stdcall; external('try.dll');
implementation

end.

project1.lpr:
Код: Выделить всё
//project1.lpr
program project1;
uses
  unit1;

var
  incomeRef: IIncome;
  cIncome: integer;

begin
  writeln('ok');
  incomeRef:=createIncome();      //создаём экзэмпляр класса
  writeln('was import');           //пишем на экран, что усё ОК
  cIncome:=incomeRef.GetIncome(6); //собственно проверяем работу класса
  writeln(cIncome);                //проверяем что занесено в проверочную переменную
  readln;
end.


При запуске External:SIGSEGV :( .
При чём dec(PInteger(incomeRef)^,100); уже не помогает...
Длл-ка try.dll естесственно лежит в каталоге... :D
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение stikriz » 08.05.2011 12:00:07

mildok писал(а):При запуске External:SIGSEGV :( .

Я же не зря спросил. Класс наследник IUnknown? Это интерфейс?
Там же еще три метода, а потом уже остальные.
Дело в том, что у интерфейсов принято что виртуальная таблица методов начинается с -0.
Чтобы на FreePascal тоже начиналось с нуля, должен быть интерфейс.
В Дельфи с третьей версии тупо сделали у классов так же, как у интерфейсов.
У FreePascal, видимо, нет.
Поэтому у FreePascal проблемы с жестким преобразованием типов из класса в интерфейс и обратно - только через as...
Аватара пользователя
stikriz
энтузиаст
 
Сообщения: 612
Зарегистрирован: 15.03.2006 09:37:47

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 08.05.2011 14:51:08

stikriz, нет класс не является наследником IUnkown :D
Хотя может я чего-то незнаю...Вот код .h на c++ дллки, которую я набросал для тестов:
Код: Выделить всё
class __declspec(dllexport) CIncomeImp
{
public:
   virtual int __stdcall GetIncome( int aNetto );
   virtual void __stdcall FreeObject();
};

extern "C" __declspec(dllexport) CIncomeImp *CreateIncome();

Как видите - здесь нет интерфейсов..обыкновенный такой класс :D ...
...Надеюсь я вас правильно понял. :)
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение stikriz » 08.05.2011 20:51:55

mildok писал(а):Надеюсь я вас правильно понял. :)

Правильно.

Вот и получается, что, чтобы был переносимый код, подходящий для любых компиляторов, которые будут использовать эту длл, нужно использовать интерфейсы, а не просто классы. С таким же успехом, Вы получите ошибку, если код в длл будет на паскале. Интерфейсы - это не изобретение микрософт, не надо стеснятся их использовать. Зато какая гибкость и не надо думать об освобождении указателя.

Конечно, можно в этот раз прибавить 100, потом, в следующий раз еще что-то поменяется, а там, глядишь, и FreePascal сделают класс со смещением 0... А надо это Вам? Все время отслеживать что там поменяется...
Аватара пользователя
stikriz
энтузиаст
 
Сообщения: 612
Зарегистрирован: 15.03.2006 09:37:47

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 08.05.2011 23:00:02

stikriz, большое спасибо за помощь :D
Почитал немного про интерфейсы - действительно хорошая штука :D. И лучше, конечно пользоваться ими...

Но отмечу 2 случая:
1) Если dll-ка так сказать "не наша" и её исходных кодов нет, а в Lazarus'е выдаёт External:SIGSEGV - тогда, я думаю можно попробовать и решение выше - если заработает - значит написана dll без интерфейсов.. :D
2) Случай, который возник у меня :( ...
Я привык к Delphi и Lazarus'у и решил написать программу-аналог Radmin'a, который ещё и обгоняет его по некоторым характеристикам :D (в плане просмотра удал. рабочего стола я кстати преуспел - некоторые возможности, которых нет в Радмине.. Ура мне! :mrgreen: )
Так уж получилось, что надо использовать зеркальный драйвер - а самому написать - так я чуть не вымер(даже перечитав статьи Лаврова - за что ему спасибо большое). Так вот, естесственно взгляд мой упёрся в UVNC-готовый_к_использования_видеодрайвер_с_примерами и там есть исходники класса, чтобы использовать драйвер...Короче - всё готово можно юзать...Но(!)...исходники videodriver.cpp и videodriver.h на C++ :(.
Тута я опечалился сильно - я-то Lazarus плохо знаю, а разобраться в хитросплетении чужого класса на C++, чтобы переписать его на Pascal'ь...
Вобщем, если бы я что-то переделал там с класса на интерфейсы - подозреваю, что не заработало бы - тем более я хотел сначала просто проверить - заработает или нет... :D
Поэтому я думаю, что способ "непереписывания на интерфейсы", пригодится хотя бы в рабочие моменты - чтобы запустить проверить заработает или нет :D . А-то ведь не запустится - и не понятно - то ли твои баги от переписывания на интерфейсы - то ли сам код нерабочий :D..
Вобщем вот...Попробую переписать на интерфейсы - надеюсь всё станет лучше... :D
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36

Re: Получение класса из dll, написанной на C++

Сообщение mildok » 03.07.2011 19:49:18

Вот - появилось свободное время - думаю надо закончить эту ветку форума :D.

Как оказалось - можно сделать так:
Код: Выделить всё
dec(PInteger(myclass)^, sizeof(TVmt));

Это для всех версий freepascal насколько я понял.

Что касаемо интерфейсов. Моё рассуждение:
Если срабатывает
Код: Выделить всё
dec(PInteger(myclass)^, sizeof(TVmt));

значит Правильный указатель на класс находится на смещении 0.(Потому что вычитаем из полученного sizeof(TVmt) и получается правильный). Как до этого писал, stikriz - таблица vmt у интерфейсов имеет нулевое смещение - поэтому я пошёл писать интерфейсы. :D
Попробовал в Lazarus'е замострячить что-то вроде:
Код: Выделить всё
IIncome = interface
   function Get4uc(n:integer):double; stdcall;
   function GetIncome(aNetto: integer): integer; stdcall;
   procedure FreeObject; stdcall;
End;           

Ничего не получилось. :(
Решил, что интерфейс должен передаваться при написании на C++ и при импорте в Lazarus, с обоих сторон вобщем. Нашёл ссылку как сделать интерфейс на C++:
http://www.sdteam.com/t206
И ещё почитал начиная с этой статьи: http://www.firststeps.ru/mfc/activex/r.php?18 и до http://www.firststeps.ru/mfc/activex/r.php?20 включительно.
Но волшебная птица Обломинго всё-равно поворачивалась ко мне....хмм.. :( :(
Вобщем долго я мучился - получил конечный вариант, который вроде работает. А вообще получается целый COM-объект блин.... :(
Правда интерфейсы надо описывать и при упаковывании на C++ в dll, и при использовании кода dll на Lazarus'е. :(
Пишем dll с нужным классом на C++(при этом придётся ещё приписать интерфейс :( от чего я и убегал):
Я писал на CodeBlock:
other_npob.h
Код: Выделить всё
//other_npob.h
//
#include <windows.h>

#define STDMETHODIMP HRESULT _stdcall
#define STDMETHODIMP_(type) type _stdcall

struct AbstractIncomeImp  //Эт интерфейс, к которому мы и будем обращаться
{
public:
    virtual STDMETHODIMP_(ULONG) AddRef();                 //Не метод класса, нужен.
    virtual STDMETHODIMP_(ULONG) Release();           //Не метод класса, нужен.               
    virtual STDMETHODIMP_ (HRESULT) QueryInterface(THIS_ REFIID riid, LPVOID FAR* ppObj); //Не метод класса, нужен.
   virtual double __stdcall Get4uc(int n)=0;                    //Метод класса.
   virtual int __stdcall GetIncome( int aNetto )=0;            //Метод класса.
   virtual void __stdcall FreeObject()=0;                         //Метод класса.
   virtual void __stdcall fulli()=0;                                   //Метод  класса.
        virtual int __stdcall CaM_gypak()=0;                         //Метод класса.
};

class CIncomeImp: public AbstractIncomeImp{                                  //Нужный нам класс
private:
    ULONG mRef;

public:
    STDMETHODIMP_(ULONG) AddRef();                                           //Пришлось добавить.
    STDMETHODIMP_(ULONG) Release();                                    //Пришлось добавить.
    STDMETHODIMP_ (HRESULT) QueryInterface(THIS_ REFIID riid, LPVOID FAR* ppObj); //Пришлось добавить.
   double __stdcall Get4uc(int n);
   int __stdcall GetIncome( int aNetto );
   void __stdcall FreeObject();
    void __stdcall fulli();
    int __stdcall CaM_gypak();
};

extern "C" __declspec(dllexport) __cdecl bool CreateIncome(AbstractIncomeImp ** pInterface);

other_npob.cpp:
Код: Выделить всё
// other_npob.cpp
//
//#include "stdafx.h"
#include "other_npob.h"
#include <stdio.h>

double CIncomeImp::Get4uc(int n)        //Метод класса
{
   printf("yry2\n");
   return n * n;
}

int CIncomeImp::GetIncome( int aNetto ) //Метод класса
{
   printf("yry\n");
   return aNetto;
}

void CIncomeImp::FreeObject() //Метод класса
{
    delete this ;
}

void CIncomeImp::fulli() //Метод класса
{
    int a=1;
}

int CIncomeImp::CaM_gypak() //Метод класса
{
    return 543;
}

STDMETHODIMP_(ULONG) CIncomeImp::AddRef() //Пришлось добавить
{
   return mRef++;
}

STDMETHODIMP_(ULONG) CIncomeImp::Release() //Пришлось добавить
{
   ULONG uRet=--mRef;
   if (!uRet) delete this;
   return uRet;
}

STDMETHODIMP_ (HRESULT) CIncomeImp::QueryInterface(THIS_ REFIID riid, LPVOID FAR* ppObj) //Пришлось добавить
{
   HRESULT retCode=NOERROR;
   ppObj=0;
   if (IsEqualIID(riid,IID_IUnknown))
   {
      *ppObj=this;
      AddRef();
   }
   else retCode = E_NOINTERFACE;
   return retCode;
}

bool CreateIncome(AbstractIncomeImp ** pInterface) //Функция для создания Интерфейса класса (!!)
{
   if(!*pInterface)
   {
      *pInterface= new CIncomeImp;
      return true;
   }
   return false;
}

Не забудьте прилинковать к проекту на C++ библиотеку libuuid.a.
Компилируем на C++ получаем dll'ку с доступной нам всего одной функцией CreateIncome, с помощью которой мы и создадим экземпляр класса. А с помощью интерфейса будем этим экземпляром пользоваться. Такие дела.
Код использования dll на Lazarus'е:
unit1.pas:
Код: Выделить всё
//unit1.pas
unit unit1;
interface

type

IIncome =interface(IUnknown) //IInterface)                         //Интерфейс
   function Get4uc(n:integer):double; stdcall;
   function GetIncome(aNetto: integer): integer; stdcall;
   procedure FreeObject; stdcall;
   procedure fulli;stdcall;
   function CaM_gypak():integer;stdcall;
End;

TIncome = class(TInterfacedObject, IIncome)               //Класс - но он как выяснилось - не нужен
public
   function Get4uc(n:integer):double;virtual; stdcall; abstract;
   function GetIncome(aNetto: integer): integer; virtual; stdcall; abstract;
   procedure FreeObject; virtual; stdcall; abstract;
   procedure fulli; virtual; stdcall; abstract;
   function CaM_gypak(): integer; virtual; stdcall; abstract;
end;

  function CreateIncome(k:Pointer):boolean; cdecl; external('other_npob.dll'); //Та самая единственно доступная в dll функция =)
implementation

end.

project1.lpr:
Код: Выделить всё
//project1.lpr
program project1;
uses
  unit1;

var
  incomeRef: IIncome;
  cIncome: longint;
  Tinc:TIncome;

begin
  writeln('ok');
  createIncome(@incomeRef);      //создаём экзэмпляр класса
//  dec(PInteger(incomeRef)^, sizeof(TVmt));
  writeln('was import');           //пишем на экран, что усё ОК
  writeln(incomeRef.Get4uc(3));                //проверяем что занесено в проверочную переменную
  readln;
end.   

И всё работает - как видите даже без извращений в куче как у меня выше...Но у меня остались 2 маленьких вопроса:
1) Насколько я понял в Lazarus'е по-умолчанию interface имеет 3 обязательных метода:
QueryInterface
_AddRef
_Release
А в интерфейсе C++ таких трёх стандартных методов нет - поэтому мне пришлось их создавать самостоятельно в C++.

Внимание вопрос 1: методы, которые я создал в C++ являются настоящими методами COM архитектуры...но они мне не нужны(мне вообще объект COM не нужен), так вот если я вместо них - сделаю просто болванки например:
Код: Выделить всё
STDMETHODIMP_ (HRESULT) CIncomeImp::QueryInterface(THIS_ REFIID riid, LPVOID FAR* ppObj)//Пришлось добавить
{
   return NOERROR;
}

То даст ли мне Windows потом по почкам за такое отношение к этим методом? В принципе я уже компилировал, всё работает и с "оболваненными" этими 3 методами..Но хотелось бы услышать мнение знающих людей. :)

2) Насколько я понял - если вдруг у класса будут поля, то придётся в интерфейсе прописывать процедурки, которые будут эти поля доставать и менять....Но хорошо если у класса будет например 4 поля...Но если полей будет 100, это ж совсем не весело будет писать процедуру с доставанием этих полей - я тогда уж лучше буду в куче извращаться с
dec(PInteger(myclass)^,sizeof(TVmt)); <- 1 строчка кода супротив 100 по-моему лучше. :D (хотяб по размеру dll'ки)

А посему Внимание вопрос №2: какие ещё существуют способы, для доступа к полям класса, кроме выше упомянутого - т.е. написание, для каждого поля процедуры чтения и записи?

Вопрос № 3(которого на самом деле не существует): может я схожу с ума и существуют способы быстрее и надёжнее? :o
Всё написанное мной выше является производством ума студента после сессии, и предназначается для незаконного и законного копирования Всем-Кому-Не-Лень. Вслучае не копирования, данного материала - значит вам лень.. :D :D
Ногами не бить - я кусаюсь и вообще я студент-плохой-пОгроммист :D.
mildok
новенький
 
Сообщения: 22
Зарегистрирован: 08.05.2010 14:09:36


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: Google Adsense [Bot] и гости: 92

Рейтинг@Mail.ru