Link to home
Start Free TrialLog in
Avatar of DynamicMonsieur
DynamicMonsieur

asked on

Problems in assigning code to TNotifyEvent

Hi,

I have a problem with TNotifyEvents:

I have program with external procedure (which is implemented in dll library), which has one of its parameters of type TNotifyEvent. The implementation of passed TNotifyEvent I would like to be in the same library, too.

{Main program - external procedure "CallLibrary"}

Procedure CallLibrary(AApplication: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); StdCall; External 'MyLib.dll';
//--
Procedure TForm1.FormCreate(Sender: TObject);
Begin
  CallLibrary(Application, Screen, MenuItem1Click);
End;


{Implementation of procedure "CallLibrary" in external dll MyLib.dll}

Procedure CallLibrary(AApplication: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); StdCall;
Begin
   Application := AApplication;
   Screen := AScreen;
   OnMenuClick := ??????? I dont know ????????;
End;

I want somehow to define and implement the method for OnMenuClick and assign it to the OnMenuClick.
I tried to write procedure and assign it, but it is not accepted because it is regular procedure
and not the TNotifyEvent; I also tried to declare a new type TMyOnClick = TNotifyEvent, but
how then implementation shoul be written for such a method?

I will be very thankful for your answers and examples.

Andrius

Avatar of geobul
geobul

Hi,

Try this way (all is in the DLL):

  TDummy = Class // Dummy Class
    class procedure MyMenuClick(Sender: TObject; var Key: Word; Shift: TShiftState);
  end;
...
class procedure TDummy .MyMenuClick(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  // do something
end;

Procedure CallLibrary(AApplication: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); StdCall;
Begin
   Application := AApplication;
   Screen := AScreen;
   OnMenuClick := TDummy .MyMenuClick;
End;

I haven't tried that from a DLL though.

Regards, Geo
My previous comment is wrong I think. Your app won't know what TDummy.MyMenuClick is. Sorry.
You passed your Form1.MenuItem1Click procedure into your library probably for the purpose of executing the click from the library.

I'd actually avoid using the main application names, Application and Screen, and call them, say, mainApplication and main Screen, so that one knows exactly what they are. consequently there should be a declaration mainFormClick of type TNotifyEvent and the assignments would then be :-

Begin
  mainApplication := AApplication;
   mainScreen := AScreen;
   mainFormClick := OnMenuClick;
End;
hello  DynamicMonsieur,  in your  CallLibrary procedure the  OnMenuClick is a variable for a procedure of object (TNotifyEvent) , so you can  do it's assignment in more than one way. . . You do not say anything about why you want to use a TNotifyEvent, or how you will use a TNotifyEvent in your DLL code. I do not beleive it is such a good Idea to pass a TObject (in the TNotifyEvent Sender) between the application and the Library, since the "TObject" may be defined in one and not the other?. .

here is some code that will allow you to use a TNotifyEvent in your library, but first I must say that when I tried to use your code -

Procedure CallLibrary(AApplication: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); StdCall;
Begin
   Application := AApplication;
   Screen := AScreen;
end;

I got Access Violations at different places, so your CallLibrary may should use different methods for that, I would suggest that you use variables in your DLL as a Reference to the programs TApplication and TScreen



library demoDLL;

uses
  Windows, Forms, Classes;


var
refApp: TApplication;
refScreen: TScreen;
MyEvent: TNotifyEvent = nil;


Procedure SetEvent(App: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); export;
Begin
  refApp := App;
  refScreen := AScreen;
  MyEvent := OnMenuClick;
End;

procedure ClickIt; export;
begin
if @MyEvent <> nil then MyEvent(nil);
// I pass NIL as the TObject in MyEvent because I am not sure that
// the pointer value for a TObject created here in this DLL will mean
// anything to the program where the OnMenuClick procedure is
end;

exports
  SetEvent,
  ClickIt;

begin

end.


 = = = = = = = = = = = = = = = = = = = = = =

here is the code used in the program that loads the DLL, a button click -

procedure TForm1.sbut_DoDllEventClick(Sender: TObject);
var
hDemo: THandle;
SetEvent: Procedure(App: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent);
ClickIt: procedure;
begin
hDemo:=LoadLibrary('DemoDll.dll');
if hDemo = 0 then
  begin
  ShowMessage('hDemo library was not Loaded');
  Exit;
  end;

@SetEvent := GetProcAddress(hDemo, 'SetEvent');
if @SetEvent = nil then
  begin
  ShowMessage('SetEvent was not located');
  FreeLibrary(hDemo);
  Exit;
  end;

SetEvent(Application, Screen, Open1Click);
// Open1Click is a Menu Item Click Event

@ClickIt := GetProcAddress(hDemo, 'ClickIt');
if @ClickIt = nil then
  begin
  ShowMessage('ClickIt was not located');
  FreeLibrary(hDemo);
  Exit;
  end;

ClickIt;

FreeLibrary(hDemo);
end;


procedure TForm1.Open1Click(Sender: TObject);
begin
// I do Not  use the Sender as it may be NIL
ShowMessage('Memu Open Click');
end;


 = = = = = = = = = = = =  = = = =  = = = = = = =  = = = =

here is some code for another method to use it, this time with a TObject that has an TNotifyEvent in it


library demoDLL;

uses
  Windows, Forms, Classes;

type
  T2IntFunc = function(aInt, bInt: Integer): Integer;

  TSomeObj = class
    AnEvent: TNotifyEvent;
    end;

var
refApp: TApplication;
refScreen: TScreen;
SomeObj1: TSomeObj = nil;


Procedure SetEvent(App: TApplication; AScreen: TScreen; OnMenuClick: TNotifyEvent); export;
Begin
  refApp := App;
  refScreen := AScreen;
  SomeObj1 := TSomeObj.Create;
  SomeObj1.AnEvent := OnMenuClick;
End;

procedure ClickIt; export;
begin
if (SomeObj1 <> nil) and Assigned(SomeObj1.AnEvent) then
  SomeObj1.AnEvent(nil);
end;

// you should add some code to Free the SomeObj1
exports
  SetEvent,
  ClickIt;

begin

end.


 = = = = = = = = = = = = = = = = = = = = = = = = = = =

this will use the same code in the program button click above for this new DLL code
I also do not use the  
StdCall;
because since you use some types like TApplication you can only call this from a Delphi program, , so I see no reason to use the StdCall

ask questions if you need more info
Avatar of DynamicMonsieur

ASKER

Hi,
 
 To Slick812: Thank you very much for your comments.
 To avoid Access Violations use StdCall
 in both Dll and main application. Also in Finalization
 section I reccommend to assign both Application and Screen
 objects to nil.
 
 Probably my question was not very clear. I will try to describe
 the entire situation in more detail.
   1. In main application I have MDI form.
   2. In Dll library I have MDI child form, which should be
      constructed by main application menu event OnMenuClick: TNotifyEvent.
   3. The menu item, that clicked should construct the MDI child form,
      is also described in DLL library. The type of menu descritption
      variable is not TMenuItem, but it is the record:
        TMenuInfo = Record
          Placement: Word;
          MenuName: PChar;
          MenuCaption: PChar;
        End;
      In which is simple information about how should be named and
      captioned and where should be placed the menu item.
   4. In Dll are implemented procedures:
      a) Procedure LoadLib(AApplication: TApplication; AScreen: TScreen; var MenuInfo: TMenuInfo);
         Begin
           Application := AApplication;
           Screen := AScreen;
           MenuInfo.Placement := 0;
           MenuInfo.MenuName := 'MenuItem_Test';
           MenuInfo.MenuCaption := 'Testing';
         End;
         
         It passes the global variables references to Dll's global variables Application And Screen
         and returns to main application menu item (that shoul be constructed on main form to call
         MDI form from dll) information.
         
      b) Procedure PutMenuEvent(OnMenuClick: TNotifyEvent); StdCall;
         Begin
           //OnMenuClick := OnClick;
         End;
         
         This procedure is called from main application when menu item is constructed. It's OnClick
         event reference is passed to the Dll to execute some code.
     
      c) Procedure OnClick(Sender: TObject);
         var
           Form_UserForm: TForm;
         Begin
           Form_UserForm := TForm.Create(Application.MainForm);
         End;
     
      !!! This is my question an main problem. I want this (c) procedure, implemented in dll,
          to be of type TNotifyEvent.
         
--------------------------------------------------------------------------------
 Dll library loading in Main application:
     Procedure TForm_FrameLite.LoadUserModule(LibraryName: string);
     var
       LibHandle: THandle;
       LoadLib: TLoadLib;
       PutMenuEvent: TPutMenuEvent;
       MenuInfo: TMenuInfo;
       MenuItem: TMenuItem;
     Begin
       LibHandle := LoadLibrary(PCHAR(LibraryName));
       if LibHandle <> 0 then begin
         @LoadLib := GetProcAddress(LibHandle, 'LoadLib');
         @PutMenuEvent := GetProcAddress(LibHandle, 'PutMenuEvent');
         if (@LoadLib <> nil) and (@PutMenuEvent <> nil) then
         begin
           LoadLib(Application, Screen, MenuInfo);
           MenuItem := CreateMenuItem(MenuInfo);
           PutMenuEvent(MenuItem.OnClick);
         end
         else begin
           ShowMessage('Wrong library version.');
           FreeLibrary(LibHandle);
         end;
       end
       else
         MessageBox(Application.MainForm.Handle, 'Wrong library version', 'Error', MB_ICONWARNING + MB_OK);
    End;
 
 SUMMARY:
 I want to make that passed to Dll OnMenuClick event
 would construct  a MDI child form (which is declared in DLL) in a
 MDI form (which is declared in main application).
 My problem is that I don't know how to IMPLEMENT, that is,
 how to ASSIGN CODE to TNotifyEvent type variable, passed
 to dll. This code SHOULD BE ALSO IN DLL.
 
 
 Thank you very much for your comments and examples,
 
 Andrius
Your basic problem is that when the user clicks on a dynamically made menu item some code needs to be executed in a dll. The OnClick variable in a TMenuItem object is of type procedure of object, which means that it expects an object method to be made available. The dll however has no objects at its disposal for this purpose.

I do this in my IDE by assiging the OnClick event in the TMenuItem (I actually use Actions) to a method on the main form (MDI Parent) which looks at the caption property of the Sender parameter (the sender is in this case TMenuItem). This then decides which procedure in which dll is to be called (which in your case is going to return a subclass of TForm).

What I think your design lacks, in order to make it tidy, is a sort of Data manager which manages a list of the loaded Dlls. This will enable on FormClose to close the dlls properly. It also keeps the Proc Instances for the procedures to be called.
ok here is some code for a DLL that will transfer a procedure to the Form to call in a click event ther, I really do not understand your problem so much?  I showed you how to get a procedure (or function) address from a DLL with GetProcAddress( ), so if you want to get a procedure that you can call in a menu click you just define a procedure in your DLL to export, and then call it in a menu Click event. .  However you seem to be stuck on the TNotifyEvent, and this will not be a good way to have a DLL procedure,
First, I beleive the parameter - Sender: TObject - will be useless, since the Object will be in the Form and not defined in the DLL, second this TNotifyEvent is a "procedure of object", which is a usefull thing in a Form (or other Object) but not so useful as a call to a DLL export or passed pointer reference. . .  So I do NOT use the TNotifyEvent in my code for a DLL procedure to pass to the Form. I use the -

procedure MakeChild(Event: Integer)

with one parameter, which is just for example, but I doubt you will need to use the  parameter.

Anyway, I was unable to get a MDI child form to be created in the Main MDI Form with creation code in my DLL, could not tell you why?

Here is the code for the library -


library ChildMDI;

uses
  Windows, SysUtils, Forms, DllForm, Dialogs, Classes;
                          // DllForm has the MDI Child Form in it
{$R *.RES}

type

  TMenuInfo = Record
    Placement: Word;
    MenuName: PChar;
    MenuCaption: PChar;
    CreateChild: procedure(Event: Integer);
   end;


var
refApp: TApplication = nil;
refScreen: TScreen;

procedure MakeChild(Event: Integer);
var
S1: String;
//Child: TDllChildForm;
begin
{I was not able to get a MDI child form to to be created here
I would get access violations in the Create event below}

//Child := TDllChildForm.Create(refApp);
//Child.Memo1.Text := 'NEW CHILD';

case Event of
  1: S1 := 'File  Open was clicked';
  2: S1 := 'File Save was clicked';
  3: S1 := 'Make a DLL Child was clicked';
  end;

ShowMessage(S1);
end;

function LoadLib(App: TApplication; AScreen: TScreen; var MenuInfo: TMenuInfo): Boolean;
Begin
Result := False;
if (App = nil) or (refApp <> nil) then Exit;
refApp := App;
refScreen := AScreen;
MenuInfo.Placement := 0;
MenuInfo.MenuName := 'Okhuytfd'; // be sure this is NOT a name all ready used
MenuInfo.MenuCaption := 'Make a DLL Child';
MenuInfo.CreateChild := MakeChild;
Result := True;
End;

exports
  LoadLib;

begin

end.


 = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

Here is some code I used in the Main MDI form -



type

  TMenuInfo = Record
    Placement: Word;
    MenuName: PChar;
    MenuCaption: PChar;
    CreateChild: procedure(Event: Integer);
   end;


  TMainForm = class(TForm)



  private
    { Private declarations }
    hLib: THandle;
    MakeChild: procedure(Event: Integer);
    procedure DoDllChild(Sender: TObject);




procedure TMainForm.FormCreate(Sender: TObject);
var
MenuInfo1: TMenuInfo;
LoadLib: function(App: TApplication; AScreen: TScreen; var MenuInfo: TMenuInfo): Boolean;
MItem: TMenuItem;
begin
hLib := LoadLibrary('ChildMdi.dll');
if hLib <> 0 then
  begin
  @LoadLib := GetProcAddress(hLib, 'LoadLib');
  if @LoadLib <> nil then
    if LoadLib(Application, Screen, MenuInfo1) then
      begin
      MItem := TMenuItem.Create(Edit1);
      MItem.Name := MenuInfo1.MenuName; // the name may not be useable
      // you could save this as a Variable (TMenuItem) and use it
      MItem.Caption := MenuInfo1.MenuCaption;
      MItem.Tag := 12345; // I use the Tag to Identify this Menu Item
      MakeChild := MenuInfo1.CreateChild;
      MItem.OnClick := DoDllChild; // define an OnClick procedure IN YOUR FORM CODE
      Edit1.Add(MItem);
      end;
  end;
end;


procedure TMainForm.DoDllChild(Sender: TObject);
var
send: Integer;
begin
if Sender = FileOpen1 then
  send := 1 else
  if Sender = FileSave1 then
  send := 2 else
  if TComponent(Sender).Tag = 12345 then
  send := 3 else
  send := 0;

// use the DLL procedure to call in this Form Procedure
if Assigned(MakeChild) then
  MakeChild(send);
end;


= = = = = = = = = = = = = = = = = = = = = = = = = =  ==

This is how I would do the DLL transfer and call for your procedure
You can call  FreeLibrary(hLib); in the Form Close or OnDestroy, but the DLL will be unloaded by windows system anyway when the  process is finished.
There may be a way to get a MDI child created in a DLL to go into the Main MDI Form, but I could not get it to work
I still could not get the MDI child to be called from the DLL. . .
however I fould an article at About.com that gives some info about how to do it

Storing and Calling an MDI Child Form from a DLL -
http://delphi.about.com/library/weekly/aa020805a.htm

it says that you will need to use run-time packages, I do not much like using run-time packages, but it might work for you
Thank very much, Slick812, I'll try some methods to do this. I will keep commenting. Your method is not very applicable for me, because I want to have many dll's with many menu items descriptions in differrent dll's. Every of that menu item should return it's event to the right dll and would be able to do some actions in it (for example, create a MDI form). I have completed all the steps except this - how to make a TNotifyEvent to do some action in Dll.

I can create this form in static way now, but I want to do it dynamically. To successfully create MDI Child form from DLL, you must pass references to main projects's global variables Application and Screen, because the MDI child form in dll must know, where is the MDI parent. You must assign these references to Dll's Applicaiton and Screen. Only this way it worked for me. Although, the other question would be about the strange behaviour of that child form: it does not receive tab key to move through controls and some other control keys input. This is very strange.
Again you are going for the TNotifyEvent in the DLL, I do not get it? You can do a single TNotifyEvent  in your Form code  (a menu OnClick Event) and then call many many different DLL procedures from many many diferent DLLs. .

if you are able to assign the Application and Screen in your DLL to the projects TApplication and TScreen with out access violations it may work, I got access violation when I did that. . .
ASKER CERTIFIED SOLUTION
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
It worked! I do not get Access Violations. To avoid access violations, you may try to assign TApplication and TScreen to nil at finalization section. It helped when I made it by some other method. Your suggestion to use one procedure may be useful, too, but it requires some mechanism to differ from which dll the call came (I want to make dll's without recompiling and making any changes to main frame (MDI form), main form should know about any new dll, existing in right place, which has the loading procedure. Thank you very much, Slick812. I can move forward from this point.