Link to home
Start Free TrialLog in
Avatar of EricTaylor
EricTaylor

asked on

Communicating from Delphi bpl -> main program via event

Our solution has a main program with a series of com objects that the main program invokes as needed. I've moved many common functions to libraries (bpl) so that both the main program and the com objects can call the same functions without duplicating code. However, I now have a case where a dialog in the library needs to send a request back to the main program. The library function could have been called by the main program itself, but it could also have been called by one of the com objects that's also running. How do I create an event that the library triggers that the main program can react to?  Thanks for any clarification.
Avatar of Igor UL7AAjr
Igor UL7AAjr
Flag of Kazakhstan image

To communicate with main application from .dll loaded by another application, I'm using WM_COPYDATA way. I suppose , that there is no problems to use that method from bpl or com objects too. If you provide more details, then a will try to write specific example how to use it.
Avatar of EricTaylor
EricTaylor

ASKER

From the library or com object, I really need to pass a variant array that the main program will interpret and take some action based on. For example, if I needed from a report or dialog to have the main program open the file for a particular job, I might need to pass something like:

var v:Olevariant;

v[0] := 'OpenJob';
v[1] := iJobID;

with this approach, if I can create an event that calls the main program, and a corresponding handler in the main program, then I can pass any number of different actions with varying degrees of detail depending on what is needed. Does this answer your question?
Avatar of Sinisa Vuk
Let say you defined some calll back procedure:

...
type
  TMainCallbackProc = procedure (Sender: TObject; v: Olevariant; var bResult: Boolean) of object;
...

// somewhere in main unit

procedure TForm1.DoInMain(Sender: TObject; v: Olevariant; var bResult: Boolean);
begin
  ...
  //do some job here
end;

procedure TForm1.CallJob(Sender: TObject);
begin
  ...
  //do some job here
  if DoSomeJob(123, @DoInMain) then ...

end;
//********************************
//somewere in blp

function DoSomeJob(param1: Integer; pCallBack: TMainCallbackProc): Boolean;
begin
...
  if Assigned(pCallBack) then pCallBack(some_obj, v, res); // do call back
...
end;

Open in new window

Using OleVariants with WM_COPYDATA is not good idea. You have to prepare some kind of record e.g.

PSendRecord = ^TSendRecord;
TSendRecord = packed array
  FileName: String[40]; 
  JobInfo: String[100];
  JobID: Integer;
end;

Open in new window


Put data at that record in library or com.
Find handle of window of main application (receiver form handle)
Execute SendMessage with WM_COPYDATA

In form of main application you have to write handler.

  TMyForm = class(TForm)
  ...
     procedure WMCopyData(var MessageData: TWMCopyData); message WM_COPYDATA;
  ...
  end;

Open in new window


Very easy and fast. Only some limitation

The data being passed must not contain pointers or other references to objects not accessible to the application receiving the data.
While this message is being sent, the referenced data must not be changed by another thread of the sending process.
The receiving application should consider the data read-only. The lParam parameter is valid only during the processing of the message. The receiving application should not free the memory referenced by lParam. If the receiving application must access the data after SendMessage returns, it must copy the data into a local buffer.

Steel need more details? No problem - ask:)
ITugay: this sounds pretty straightforward, but I want to get a bit of clarification.

1. What is the "PSendRecord = ^TSendRecord;" line for?

2. Given a relatively small amount of data in the record (can't imagine more than a few hundred bytes in the worst case, and in most cases it's going to be one string(40) and a few integers, does it matter if the record is packed? The packing might save 30 bytes, but doesn't it also result in a slight inefficiency when the data is accessed? (Not trying to argue with your solution, just to understand it. I've never worked with packed records before so I'm just trying to understand what the importance is.)

3. How does the procedure in the receiving application know that the received data (in your example var MessageData) is a TSendRecord, or do you just have to do something like "if MessageData is TSendRecord then with TSendRecord(MessageData) do begin..."

4. Why is MessageData preceded by var? Since we're never going to write back to this, couldn't we just pass MessageData without the var? (Again, I'm not arguing with you here. This type of call is new to me, so I'm just trying to understand.)
ASKER CERTIFIED SOLUTION
Avatar of Igor UL7AAjr
Igor UL7AAjr
Flag of Kazakhstan 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
Sorry for the delay in responding to your responses; had to go away for a few days. Will be trying to process these responses today and figuring out if I can make this work or if I have further questions.  Eric
Sinisav: didn't really understand your solution. Might be equally good, but this type of call is really new to me.

ITugay: Your explanation seemed to make sense, but I'm apparently missing something critical. Here's what I put in the library (slightly simplified):
procedure CallMainProgram(sCallDesc:string; iID:integer; iID2:integer = 0; iID3:integer = 0);
  var
      copyDataStruct : TCopyDataStruct;
      aCall: TMainProgramCall;
      aHandle: HWND;
      pCall: ^TMainProgramCall;
begin
  aHandle := FindWindow('TFrmTest1', nil);
  if 0 = aHandle then begin
    showmessage('The main program doesn''t seem to be open right now, so we can''t complete your request.');
    exit;
  end;
  with aCall do begin
    CallDesc := sCallDesc;
    ID := iID;
    ID2 := iID2;
    ID3 := iID3;
  end;
  pCall := @aCall;
  with copyDataStruct do begin
    dwData := 0;
    cbData := SizeOf(aCall);
    lpData := pCall;
  end;
  SendMessage(aHandle, WM_COPYDATA, 0, Integer(@copyDataStruct));
end;

Open in new window

In the program that I was trying to issue a call to (in the case above, a test app that I wrote with a main form class of TfrmTest1), I put in this:
type
  TfrmTest1 = class(TForm)
    procedure WMCopyData(var MessageData: TWMCopyData); message WM_COPYDATA;

...

implementation

procedure TfrmTest1.WMCopyData(var MessageData: TWMCopyData);
begin
  showmessage('here');

end;

Open in new window

While both programs seemed to compile correctly, I couldn't get the latter program to respond. I may be missing something obvious... but being a beginner with this at least, what I'm missing isn't clear to me. Any further guidance would be great.
ITugay: Disregard the last message; I thought i was building with packages but wasn't, so the change I made to the library wasn't reflected until I rebuild my test program. Above call appears to be triggering the response I wanted. I'm still having a bit of trouble getting the values of the response in the receiving program, but let me work with this for a bit: I'll let you know if I can't figure it out.
okay. The message is correctly triggering, but I can't figure out how to get the data that's being pointed to. I tried this:
procedure TfrmTest1.WMCopyData(var MessageData: TWMCopyData);
  var pCall: ^TMainProgramCall;
      sCall:string;
      ID, ID2, ID3:integer;
begin
  pCall := MessageData.CopyDataStruct.lpData;
  sCall := pCall^.CallDesc;
  ID := pCall^.ID;
  ID2 := pCall^.ID2;
  ID3 := pCall^.ID3;
  showmessage(sCall);
end;

Open in new window

Pointers are another area I've managed to do almost nothing with before, so pardon my ignorance.
Right way.
It doesn't work? Then show me TMainProgramCall structure definition.
type
  pMainProgramCall = ^TMainProgramCall;
  TMainProgramCall = packed record
    CallDesc:string;
    ID:integer;
    ID2:integer;
    ID3:integer;
  end;

Open in new window

CallDesc: string; - here is a problem. String without a length definition is a pointer.   You have to declare string[50] or another length to keep you data. Declaring string by this way you force compiller to use ShortString and allocate static memory to keep string data.

See "String Types" topic at Delphi help for more details.

Best regards,
Igor.
Thanks much. Needed to do some minor adjustments (cf. other messages in this thread), but this got things working for me.