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.
Заранее благодарю за любую помощь.

Добавлено спустя 8 часов 19 минут 46 секунд:
Эх...Никто меня не любит, отвечать мне не хочет, все меня игнорируют...



Насколько я разобрался во усём виновата 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. Не подходит - я жадный и столько денег, сколько просят за эту чудесную программу не дам..

2) Использовать "обёртки" и через них вызывать методы класса. В моём прожекте (слово-то какое


3) Найти то маленькое различие (



Итак, как мне кажется я нашёл одно различие в работе этих великолепных сред программирования - это VMT таблицы. У меня возник такой вопрос а можно ли "подогнать" таблицу VMT C++ и Lazarus'а. К примеру добавить в код C++ несколько "методов-болванок", так чтобы полезные нам методы класса начинались как раз-таки с +16(или 20) байта?

И второй вопрос можно ли как-нибудь посмотреть VMT, которая формируется в исполняемой программе?
Кто-нибудь помогите..


Итак, несмотря ни на что (на нескольких форумах меня прямо послали в...интернет изучать обёртки, кое-где вообще заявили - проблема неразрешима, а кое-где вообще ничего не отвечают


Ещё раз скажу, что программист я начинающий, поэтому ногами меня не бить и всё описанное далее ИМХО..!..Проблему я написал выше. Решение в общем-то, как оказалось такое же простое как и математика, к сожалению не такое постоянное. И - я угадал, действительно, подогнать таблицу VMT C++ к таблице Lazarusa можно. После создания Dll'кой экземпляра класса, где-то в куче по формату vmt си плюса класс начинается с +0, однако по формату vmt Lazarus'а (кто-нибудь объясните мне, кстати - зачем Lazarus так с ней поступил (с таблицей) ) pointer на класс будет начинаться с +12...Отсюда получаем, что после возврата Dll'кой указателя (на область памяти с которой начинается экзэмпляр класса) он указывает на +0. Lazarus же топает по этому адресу...думает...прибавляет свои +12 байт и думает, что класс тута с +12, что конечно неправда.. Естественно, что решением проблемы является простая математика:
dec(PInteger(incomeRef)^, $12);
И теперь мы должны иметь "правильный" указатель на класс..Но не тут-то было - вот нету тута класса и усё

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.
У меня работает...

Итак повторюсь решение в $50 байтиков конкретное, для данных компиляторов(Лазаруса и C++). Для решения общего смотри пост выше..

Вот и усё..

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 байтов, тому...морс (что? почему не пиво? А пиво - вредно..




Но главное - мы победили и всё работает..

