Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 886
  • Last Modified:

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.
0
EricTaylor
Asked:
EricTaylor
  • 8
  • 5
1 Solution
 
ITugayCommented:
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.
0
 
EricTaylorAuthor Commented:
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?
0
 
Sinisa VukCommented:
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

0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
ITugayCommented:
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:)
0
 
EricTaylorAuthor Commented:
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.)
0
 
ITugayCommented:
1. P... = ^T...
We just declare pointer to record. Receiver window will receive pointer to record, instead of record itself.

2. packed record.
You can use it without packing. Yes, no any difference in this case.

3. Let say it receives  M: TWMCopyData
TWMCopyData is record declared like this:

 
TWMCopyData = packed record
    Msg: Cardinal;
    From: HWND;
    CopyDataStruct: PCopyDataStruct;
    Result: Longint;
  end;

Open in new window


where PCopyDataStruct points to TCopyDataStruct

TCopyDataStruct declared like this:

  tagCOPYDATASTRUCT = packed record
    dwData: DWORD;
    cbData: DWORD;
    lpData: Pointer;
  end;

Open in new window

Actually, from dll or com, you will send record of TCopyDataStruct. Where you can assign three parameters:
  dwData - any dword you wish
  cbData - length of lpData
  lpData - here we will place pointer to TSendRecord (it is PSendRecord).

So, in form handle of WM_COPYDATA message, we have to write something like this:
procedure TFMainForm.WMCOPYDATA(var M: TWMCopyData);
var
  P: PSendRecord;
  I1: Integer;
  S1: String;
  ID: Integer;
begin
  I1 := M.CopyDataStruct.dwData;
  P := PSendRecord(M.CopyDataStruct.lpData);
  S1 := P^.FileName;
  ID := P^.JobID;

  ... process received data ...

  M.Result := 1;
end;

Open in new window


Code in dll or com will look like this:
procedure SendData(AFormHandle: Integer; SomeInt1, JobID: Integer; AFileName: String);
var
  CDS: TCopyDataStruct;
  SR: TSendRecord;
begin
  SR.FileName := AFileName;
  SR.JobID := JobID;    

  CDS.dwData := SomeInt1;
  CDS.cbData := SizeOf(SR);
  CDS.lpData := @SR;

  SendMessage(AFormHandle, WM_COPYDATA, 0, Integer(@CDS));
end;

Open in new window


4. As you see in [3.] we receive special record and needs to return some result to notify windows messaging about processing event (TWMCopyData.Result). And it is predefined for all message handlers to send Message as var parameter.
0
 
EricTaylorAuthor Commented:
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
0
 
EricTaylorAuthor Commented:
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.
0
 
EricTaylorAuthor Commented:
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.
0
 
EricTaylorAuthor Commented:
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.
0
 
ITugayCommented:
Right way.
It doesn't work? Then show me TMainProgramCall structure definition.
0
 
EricTaylorAuthor Commented:
type
  pMainProgramCall = ^TMainProgramCall;
  TMainProgramCall = packed record
    CallDesc:string;
    ID:integer;
    ID2:integer;
    ID3:integer;
  end;

Open in new window

0
 
ITugayCommented:
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.
0
 
EricTaylorAuthor Commented:
Thanks much. Needed to do some minor adjustments (cf. other messages in this thread), but this got things working for me.
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 8
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now