Solved

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

Posted on 1998-08-18
6
360 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
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 

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

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

760 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

21 Experts available now in Live!

Get 1:1 Help Now