Solved

Accessing objects in a DLL, To COM or not to COM

Posted on 1998-08-18
6
361 Views
Last Modified: 2010-04-04
I have defined a base class called TBase with several virtual methods. Based on this TBase class I have inherited several child classes implementing the virtual methods differently. Every child is contained in a DLL with a procedural interface to the constructor and destructor.
In the Main app there is a reference, Base: TBase declared and assigned by the currently loaded DLL constructor. So far, so good. The object behaves real nice until I destroy it. Access violation in the DLL and runtime error 216.

Is this a possible way to do it? Or am I just lucky getting it to work at all? I'm in a hurry and would like not having to dig in to COM right now. Or should I?
0
Comment
Question by:jorgen.hojdmo
  • 3
  • 3
6 Comments
 
LVL 4

Accepted Solution

by:
d003303 earned 400 total points
ID: 1337163
OK,
here's some sample code how to manage objects contained in a DLL.
First is the CBase unit containing the TBase base class. Next are two DLL projects implementing a new object class with TBase as an anchestor and providing a constructor interface. Last one is a sample app to load and instanciate these objects at runtime.
Take a look at the sources and hopefuly you will find the (small) bug in your project.

// BOC
// Base Class
unit CBase;

interface

uses Classes;

type

  TBase = class(TObject)
  public
    function Add(ANumber: Integer): Integer; virtual;
    function Sub(ANumber: Integer): Integer; virtual;
    function Multi(ANumber: Integer): Integer; virtual;
    function Divid(ANumber: Integer): Integer; virtual;
    function Modulo(ANumber: Integer): Integer; virtual;
  end;

implementation

function TBase.Add(ANumber: Integer): Integer;
begin
  Result := 0;
end;

function TBase.Sub(ANumber: Integer): Integer;
begin
  Result := 0;
end;

function TBase.Multi(ANumber: Integer): Integer;
begin
  Result := 0;
end;

function TBase.Divid(ANumber: Integer): Integer;
begin
  Result := 0;
end;

function TBase.Modulo(ANumber: Integer): Integer;
begin
  Result := 0;
end;

end.

// first DLL
unit c2;

interface

uses CBase;

type
  TBase2 = class(TBase)
  private
    FMagicNumber : Integer;
  public
    constructor Create;
    function Add(ANumber: Integer): Integer; override;
    function Sub(ANumber: Integer): Integer; override;
    function Multi(ANumber: Integer): Integer; override;
    function Divid(ANumber: Integer): Integer; override;
    function Modulo(ANumber: Integer): Integer; override;
  end;

implementation

constructor TBase2.Create;
begin
  inherited Create;
  FMagicNumber := 2;
end;

function TBase2.Add(ANumber: Integer): Integer;
begin
  Result := ANumber + FMagicNumber;
end;

function TBase2.Sub(ANumber: Integer): Integer;
begin
  Result := ANumber - FMagicNumber;
end;

function TBase2.Multi(ANumber: Integer): Integer;
begin
  Result := ANumber * FMagicNumber;
end;

function TBase2.Divid(ANumber: Integer): Integer;
begin
  Result := ANumber div FMagicNumber;
end;

function TBase2.Modulo(ANumber: Integer): Integer;
begin
  Result := ANumber mod FMagicNumber;
end;

end.

//

library class2;

uses
  SysUtils,
  Classes,
  CBase,
  c2 in 'c2.pas';
 
function CreateInstance: TBase;
begin
  Result := TBase2.Create;
end;

exports
  CreateInstance index 1;

begin
end.

// second DLL

unit c3;

interface

uses CBase;

type
  TBase3 = class(TBase)
  private
    FMagicNumber : Integer;
  public
    constructor Create;
    function Add(ANumber: Integer): Integer; override;
    function Sub(ANumber: Integer): Integer; override;
    function Multi(ANumber: Integer): Integer; override;
    function Divid(ANumber: Integer): Integer; override;
    function Modulo(ANumber: Integer): Integer; override;
  end;

implementation

constructor TBase3.Create;
begin
  inherited Create;
  FMagicNumber := 3;
end;

function TBase3.Add(ANumber: Integer): Integer;
begin
  Result := ANumber + FMagicNumber;
end;

function TBase3.Sub(ANumber: Integer): Integer;
begin
  Result := ANumber - FMagicNumber;
end;

function TBase3.Multi(ANumber: Integer): Integer;
begin
  Result := ANumber * FMagicNumber;
end;

function TBase3.Divid(ANumber: Integer): Integer;
begin
  Result := ANumber div FMagicNumber;
end;

function TBase3.Modulo(ANumber: Integer): Integer;
begin
  Result := ANumber mod FMagicNumber;
end;

end.

//

library class3;

uses
  SysUtils,
  Classes,
  CBase,
  c3 in 'c3.pas';

function CreateInstance: TBase;
begin
  Result := TBase3.Create;
end;

exports
  CreateInstance index 1;

begin
end.

// sample app
// DFM file
object Form1: TForm1
  Left = 200
  Top = 108
  Width = 329
  Height = 362
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 12
    Top = 16
    Width = 109
    Height = 25
    Caption = '&Load Object...'
    TabOrder = 0
    OnClick = Button1Click
  end
  object ListBox1: TListBox
    Left = 12
    Top = 52
    Width = 165
    Height = 265
    ItemHeight = 13
    TabOrder = 1
    OnClick = ListBox1Click
  end
  object Edit1: TEdit
    Left = 188
    Top = 52
    Width = 121
    Height = 21
    ReadOnly = True
    TabOrder = 2
  end
  object Edit2: TEdit
    Left = 188
    Top = 76
    Width = 121
    Height = 21
    ReadOnly = True
    TabOrder = 3
  end
  object Edit3: TEdit
    Left = 188
    Top = 100
    Width = 121
    Height = 21
    ReadOnly = True
    TabOrder = 4
  end
  object Edit4: TEdit
    Left = 188
    Top = 124
    Width = 121
    Height = 21
    ReadOnly = True
    TabOrder = 5
  end
  object Edit5: TEdit
    Left = 188
    Top = 148
    Width = 121
    Height = 21
    ReadOnly = True
    TabOrder = 6
  end
  object Edit6: TEdit
    Left = 188
    Top = 24
    Width = 121
    Height = 21
    TabOrder = 7
    Text = '1'
  end
  object OpenDialog1: TOpenDialog
    Filter = 'Dynamic Link Libraries|*.dll'
    Left = 128
    Top = 16
  end
end

// PAS file

unit _mother_app;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    ListBox1: TListBox;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses CBase;

type
  TDLLObject = class(TObject)
  private
    FDLLObject: TBase;
    FDLLHandle: THandle;
  public
    constructor Create(ADLLName: string);
    destructor Destroy; override;
    property DLLObject: TBase read FDLLObject;
  end;

////////////////////////////////////////////////////////////////////////////////

constructor TDLLObject.Create(ADLLName: string);
var CreateInstanceFunction: function: TBase;
begin
  inherited Create;
  try
    if not FileExists(ADLLName)
     then Exception.Create('DLL not found');
    FDLLHandle := LoadLibrary(PChar(ADLLName));
    if FDLLHandle = 0
     then Exception.Create('Failed to load ' + ADLLName);
    @CreateInstanceFunction := GetProcAddress(FDLLHandle, 'CreateInstance');
    if CreateInstanceFunction = nil
     then Exception.Create('No suitable object DLL');
    FDLLObject := CreateInstanceFunction;
  except
    Free;
    raise;
  end;
end;

destructor TDLLObject.Destroy;
begin
  if FDLLObject <> nil
   then FDLLObject.Free;
  if FDLLHandle <> 0
   then FreeLibrary(FDLLHandle);
  inherited Destroy;
end;

////////////////////////////////////////////////////////////////////////////////

procedure TForm1.FormDestroy(Sender: TObject);
begin
  while ListBox1.Items.Count <> 0 do
   begin
     ListBox1.Items.Objects[0].Free;
     ListBox1.Items.Delete(0);
   end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute
   then ListBox1.Items.AddObject(OpenDialog1.FileName, TDLLObject.Create(OpenDialog1.FileName));
end;

procedure TForm1.ListBox1Click(Sender: TObject);
var Index,
    ANumber: Integer;
begin
  for Index := 0 to ListBox1.Items.Count - 1 do
   if ListBox1.Selected[Index] then
    begin
      with (ListBox1.Items.Objects[Index] as TDLLObject).DLLObject do
       begin
         ANumber := StrToInt(Edit6.Text);
         Edit1.Text := IntToStr(Add(ANumber));
         Edit2.Text := IntToStr(Sub(ANumber));
         Edit3.Text := IntToStr(Multi(ANumber));
         Edit4.Text := IntToStr(Divid(ANumber));
         Edit5.Text := IntToStr(Modulo(ANumber));
       end;
      Break;
    end;
end;

end.

// DPR file

program mother_app;

uses
  Forms,
  _mother_app in '_mother_app.pas' {Form1},
  CBase in 'CBase.pas';

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

// EOC

If you have questions -> ask.

Good luck,
Slash/d003303
0
 

Author Comment

by:jorgen.hojdmo
ID: 1337164
This is exactly how I implemented it. The object is rather complex and what I realy would like to find out is the limitations of this type of solution. Except for the normal DLL limitations such as global vatriables that is. The reason for this is the fact that when I compile the inherited classes in the main app, everything works just fine.

0
 
LVL 4

Expert Comment

by:d003303
ID: 1337165
Mainly there are just a few limitations. DLLs are reference counted and can be instanciated more than once by the same application. For each instance, the DLLs receives a new heap and stack. Thus, global variables are local for each instance.
A very important thing is that some objects rely on global instances of an object, like forms. They need the Application instance of TApplication that is auto-created in the normal EXE startup code. DLLs have a different Application variable if they contain forms.
Check the dependencies on these global variables of your classes, maybe this is the point.
Another thing is the exported instancing function. If you e.g. declare a global variable to receive the instance and pass it over (like Form1 in a form unit), you may run into problems.
It will be helpful if you could post the declaration of your classes here that I can take a look at it.

Slash/d003303
0
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 

Author Comment

by:jorgen.hojdmo
ID: 1337166
I have debugged the DLL and the problem seems to be some finalize procedures in TurboPowers APDCom (Serial Com) object that I create in the DLL nonvisual. Another APDCom object resides in the main app. Might be some conflict there?? An ugly solution is to skip the deallocation of the object and DLL. It works because the DLL is only unloaded when the app terminates. Not nice. By the way here is the class declaration for the base class and one child.

TSimulatorInterface = class( TObject )
private
    procedure SetEnabled( NewEnabled: Boolean );
    procedure SetReadMask( NewReadMask: TSimulatorParamsMask );
    procedure SetWriteMask( NewWriteMask: TSimulatorParamsMask );
    procedure SetTreatmentUnit( NewTreatmentUnit: Integer );

protected
    FOnError: TSimulatorEvent;
    FOnReceive: TSimulatorEvent;
    FSimulatorSettings: TSimulatorSettings;
    FSimTRUsFile: String;
    Timer: TTimer;
    FEnabled: Boolean;
    TimeOuts: Integer;
    MaxTimeOuts: Integer;
    PrioCounter: TSimulatorParamPriorities;
    SimulatorTRUs: TSimulatorTRUs;
    ActiveSimulatorTRU: TSimulatorTRU;

    FAvailableRead: TSimulatorParamsMask;
    FAvailableWrite: TSimulatorParamsMask;
    FReadMask: TSimulatorParamsMask;
    FWriteMask: TSimulatorParamsMask;
    FPriorities: TSimulatorParamPriorities;

    FPatientName: string;
    FPatientID: string;
    FFieldName: string;
    FFieldID: string;
    FTreatmentUnit: Integer;
    FParams: TSimulatorParams;
    FPresets: TSimulatorParams;
    procedure SetSimulatorSettings( SS: TSimulatorSettings ); virtual;
public
    constructor Create;
    destructor Destroy; override;

    procedure SettingsUpdated; virtual;
    procedure Reset; virtual;
    procedure SoftReset; virtual;
    procedure Clear; virtual;

    procedure TimeOut( Sender: TObject );
    procedure RestartTimeOut;
    procedure QueryParams; virtual;
    procedure DataAvailable( Sender: TObject ); virtual;
    procedure AutoSetup; virtual;

    procedure Test; virtual;

    property Enabled: Boolean read FEnabled write SetEnabled;
    property AvailableRead: TSimulatorParamsMask read FAvailableRead;
    property AvailableWrite: TSimulatorParamsMask read FAvailableWrite;
    property ReadMask: TSimulatorParamsMask read FReadMask write SetReadMask;
    property WriteMask: TSimulatorParamsMask read FWriteMask write SetWriteMask;
    property Priorities: TSimulatorParamPriorities read FPriorities write FPriorities;

    property PatientName: string read FPatientName write FPatientName;
    property PatientID: string read FPatientID write FPatientID;
    property FieldName: string read FFieldName write FFieldName;
    property FieldID: string read FFieldID write FFieldID;
    property TreatmentUnit: Integer read FTreatmentUnit write SetTreatmentUnit;
    property Params: TSimulatorParams read FParams;
    property Presets: TSimulatorParams read FPresets write FPresets;

    property OnReceive: TSimulatorEvent read FOnReceive write FOnReceive;
    property OnError: TSimulatorEvent read FOnError write FOnError;
    property SimulatorSettings: TSimulatorSettings read FSimulatorSettings write SetSimulatorSettings;
    property SimulatorTRUsFile : String read FSimTRUsFile write FSimTRUsFile;
end;


TSimulixInterface = class( TSimulatorInterface )
protected
    MsgCom: TSimulixMsgCom;

    ReadyToSend: Boolean;
    QueriesToSend: TStringList;
   
    procedure Send( Query: string );
    procedure DoSend;

    function NextToken( S: string; var Index: Integer ): string;
    procedure SetSimulatorSettings(SS: TSimulatorSettings); override;
public
    constructor Create;
    destructor Destroy; override;

    procedure SettingsUpdated; override;
    procedure Clear; override;
    procedure Reset; override;
    procedure SoftReset; override;

    procedure QueryParams; override;
    procedure DataAvailable( Sender: TObject ); override;
    procedure AutoSetup; override;

    procedure Test; override;

end;
0
 
LVL 4

Expert Comment

by:d003303
ID: 1337167
Yo,
I think the APDCom components could be the point. Maybe you should keep the component only in the main app and pass an instance of the object to the DLL when calling the object creation function. To avoid that some internal global variables in the ADPCom unit mess all up (like the Application variable in the forms unit), you should not include the ADPCom unit in your DLL project. Copy and paste the object declarations into a seperate file and use it as an interface reference.

Slash/d003303
0
 

Author Comment

by:jorgen.hojdmo
ID: 1337168
Well, I stil haven't found the problem with the destructor but I suppose you answered the question I asked. Thanks.
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Virtuailstring tree compare node issue 14 105
control image tags in a string ? 12 110
Adoquery sql  left join does not work 25 80
How to load 2 images in same column in Delphi 2 31
A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Windows 10 is mostly good. However the one thing that annoys me is how many clicks you have to do to dial a VPN connection. You have to go to settings from the start menu, (2 clicks), Network and Internet (1 click), Click VPN (another click) then fi…
In this video I am going to show you how to back up and restore Office 365 mailboxes using CodeTwo Backup for Office 365. Learn more about the tool used in this video here: http://www.codetwo.com/backup-for-office-365/ (http://www.codetwo.com/ba…

912 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now