Link to home
Start Free TrialLog in
Avatar of philly_tee
philly_teeFlag for New Zealand

asked on

Comport DLL-type interface with streaming data

Hi all,

I have been tasked with creating a DLL-like system that interacts with a device connected to a Com Port, which is constantly streaming data.  The DLL will be used by other developers using all different languages. Thinking about it, 'Device Driver' springs to mind.

Anyway, my problem is that by nature, DLLs are a request -> response type setup, whereby as soon as a DLL function returns, the state is lost.   I have a TComPort component on a form in the DLL, which probably isn't the best idea, and If a modally shoow the form everything works fine.  Ideally i'd like to not use the form at all, but so far haven't been able to get it to work.

What I need, is a way of constantly collecting the streaming data from the com port, and then passing that back to the parent application without the parent application having to constantly call a DLL function.  I have callbacks working fine, which allows me to trigger events for the parent application, I just can't think of a way to keep the innards of the DLL active so as to be always listening to the com port.

I know nothing about making device drivers, had a quick look at how to build activex controls (gave up), and thought I could write a full app with COM automation for the parent app but trying to stay away from that.

Your suggestions please?


Thanks

Philip
ASKER CERTIFIED SOLUTION
Avatar of Lukasz Zielinski
Lukasz Zielinski
Flag of Poland 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
Avatar of philly_tee

ASKER

Thanks for the reply ziolko,

I'm a bit (ok very) rusty on threads... spent the last hour reading up on them but I think I'll have to read a bit more yet.  You wouldn't happen to have a small example or a decent tutorial lying around somewhere would you?

Thanks again,

Philip
firat of all reading is not everything:) test and debug test and debug.... :)

sample... look into Borland\Delphi\Demos\Threads
I will post something here with comport but I cant promise to do it today... you know saturday, carnival:)

ziolko.
Ok I've had a play and managed to get it to work in a stardard app.  As soon as I convert the thread stuff to dll I don't get callbacks from the ComPort....  Not sure if the thread is closing down as soon as initport is finishing??

I'm using a component I found somewhere in my travels...  TComPort by Dirk Claessens.

Heres my code:


DLL:
library Project1;
 
uses
  SysUtils,
  Classes,
  Forms,
  uThread in 'uThread.pas';
 
var
    ComThread : TComThread;
 
{$R *.res}
 
procedure InitPort(clientFunc : TCallBackFunction); stdcall;
begin
    ComThread := TComThread.Create(True, clientFunc);
end;
 
procedure OpenPort; stdcall;
begin
    ComThread.Resume;
end;
 
exports InitPort, OpenPort;
 
end.
 
 
 
unit uThread;
 
interface
 
uses
  Classes, ComPort, Dialogs;
 
type
  TCallBackFunction = procedure(data : string);
 
  TComThread = class(TThread)
  ComPort1 : TComPort;
  private
    { Private declarations }
    CallBackProc : TCallBackFunction;
    Temp : String;
    Procedure SendBackData;
    procedure ComPort1ReceiveCallBack(Data: String);
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended : Boolean; ReturnProc : TCallBackFunction);
  end;
 
implementation
 
procedure TComThread.ComPort1ReceiveCallBack(Data: String);
begin
    // Put the data into a variable as I don't know how to get it to the callback caller any other way
    Temp := Data;
    Synchronize(SendBackData);
end;
 
constructor TComThread.Create(CreateSuspended: Boolean; ReturnProc : TCallBackFunction);
begin
    Inherited Create(CreateSuspended);
 
    CallBackProc := ReturnProc;
    ComPort1 := TComPort.Create(ComPort1);
    ComPort1.Port := 'COM4';
    ComPort1.Baud := 57600;
    ComPort1.ReceiveCallBack := ComPort1ReceiveCallBack;
end;
 
procedure TComThread.Execute;
begin
    ComPort1.Open;
end;
 
procedure TComThread.SendBackData;
begin
    // Call the parent app's callback function and give it some data
    CallBackProc(Temp);
end;
 
end.
 
 
 
 
 
Calling Application:
 
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, unit2;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    ComThread : TComThread;
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure reply(Data : String);
begin
    // This is the callback function which is called by the dll.
    Form1.Memo1.Lines.Append(Data);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
    // Get the dll to create it's thread, pass in the callback function
    ComThread := TComThread.Create(True, reply);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
    // Open the com port and start receiving data
    ComThread.Resume;
end;
 
end.

Open in new window

Hi,
I don't have TComPort component so I used timer instead:
DLL:

library COMDLL;

uses
  SysUtils,
  Classes,
  Forms,
  uComThread in 'uComThread.pas';

var
    ComThread : TComThread;

{$R *.res}

procedure OpenPort(CallBack : TCallBackFunction); stdcall;
begin
  if not Assigned(ComThread) then
    ComThread := TComThread.Create(False, CallBack);
end;

procedure ClosePort;stdcall;
begin
  FreeAndNil(ComThread);
end;
 
exports
  OpenPort name 'OpenPort',
  ClosePort name 'ClosePort';
 
end.
 
 
uComThread.pas:

unit uComThread;

interface

uses
  Classes, {ComPort, }Dialogs, Windows, ExtCtrls;

type
  TCallBackFunction = procedure(Data: PChar);stdcall;

  TComThread = class(TThread)
  private
//    FComPort: TComPort;
    FTimer: TTImer;
    CallBackProc : TCallBackFunction;
    FBuffer: PChar;
    procedure SendBackData;
    procedure ComPort1ReceiveCallBack(Data: string);
    procedure Timer1Timer(Sender: TObject);
    function ProcessMessage(var Msg: TMsg):Boolean;
    procedure ProcessMessages;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended : Boolean; ReturnProc : TCallBackFunction);reintroduce;
    destructor Destroy;override;
  end;

implementation

uses SysUtils;

procedure TComThread.ComPort1ReceiveCallBack(Data: String);
begin
  ReallocMem(FBuffer, Length(Data) + 1);
  StrPCopy(FBuffer, Data);
  Synchronize(SendBackData);  
end;

constructor TComThread.Create(CreateSuspended: Boolean; ReturnProc : TCallBackFunction);
begin
  inherited Create(CreateSuspended);
  FTimer := TTimer.Create(nil);
  FTimer.OnTimer := Timer1Timer;
  FTimer.Interval := 1000;
  FTimer.Enabled := True;    
  CallBackProc := ReturnProc;
{  FComPort := TComPort.Create(ComPort1);
  FComPort.Port := 'COM4';
  FComPort.Baud := 57600;
  FComPort.ReceiveCallBack := ComPort1ReceiveCallBack;}
end;

destructor TComThread.Destroy;
begin
  CallBackProc := nil;
//  FreeAndNil(FComPort);
  inherited Destroy;
end;

procedure TComThread.Execute;
begin
//  FComPort.Open;
  while not Terminated do begin
    ProcessMessages;
    Sleep(1);
  end;
end;

function TComThread.ProcessMessage(var Msg: TMsg): Boolean;
begin
  Result := False;
  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
  begin
    Result := True;
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end;

procedure TComThread.ProcessMessages;
var Msg: TMsg;
begin
  while ProcessMessage(Msg) do ;
end;

procedure TComThread.SendBackData;
begin
  if Assigned(CallBackProc) then
    CallBackProc(FBuffer);
end;

procedure TComThread.Timer1Timer(Sender: TObject);
begin
  ComPort1ReceiveCallBack('it is now ' + FormatDateTime('hh:mm:ss', Now));
end;

end.


and EXE:

unit fMainForm;

interface

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

type
  TForm1 = class(TForm)
 
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject); private
    { Private declarations }
  public
    { Public declarations }
  end;

  TCallBackFunction = procedure(Data: PChar);stdcall;

var
  Form1: TForm1;

procedure OpenPort(CallBack : TCallBackFunction); stdcall;
procedure ClosePort;stdcall;


implementation

{$R *.dfm}

procedure OpenPort;external 'COMDLL.DLL' name 'OpenPort';
procedure ClosePort;external 'COMDLL.DLL' name 'ClosePort';

procedure reply(Data : PChar);stdcall;
begin
  Form1.Memo1.Lines.Add(Data);    
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  OpenPort(@reply);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ClosePort;
end;

end.



ziolko.
but if you get rid of timer and un-comment comport stuff it should work.

note about ProcessMessages in uComThread, test it with connection to port normally it is not needed but if I remember correctly it might be needed with COM, just not sure so test it yourself

ziolko
Thanks for the code.  Timer works fine (not that we ever doubted that), however I stall can't get the callbacks from the com port.

constructor TComThread.Create(CreateSuspended: Boolean; ReturnProc : TCallBackFunction);
begin
  inherited Create(CreateSuspended);
  FComPort := TComPort.Create(nil);
  FComPort.Port := 'COM4';
  FComPort.Baud := 57600;
  CallBackProc := ReturnProc;
  FComPort.ReceiveCallBack := ComPort1ReceiveCallBack;
  FComPort.OnPortOpen := Timer1Timer;
  FComPort.Open;
end;

OnPortOpen is firing, but nothing from the callback function.  

I can see through a port monitor (portmon) that initialisation data transfer occurs, but no actual data from the connected device.
Works fine if I do a test app with the same comport config.

Any ideas?

Philip
will take a look at this one more.
do you use ComPort from Sourceforge.net?

ziolko.
Thanks. Appreciate all the time you're spending on this!

I use TComPort by Dirk Claessens... available at http://users.pandora.be/dirk.claessens2/downloads/tcomport.zip

But I'll use anything if it works, so if you get the one mentioned above to work with all this then I'll switch.  I have actually used the one you mentioned before but lost it along the way and found this other one instead.

Points upped to 500 as this is obviously a fair bit more involved than I thought it would be.

Regards

Philip
ok, let's stay with component you use, i'l ldownload it and try to get it working

ziolko.
hmm I'm a bit confused should work, make sure that you have same settings on ComPort in DLL as in your test EXE.
did you try to debug DLL?

ziolko.
I've checked and double checked the settings... will check again tonight when I get home from work.

I've traced dll execution through the create and execute procs, all fine there, but haven't been able to work out why the comport callback won't fire.

Sounds like you've got it working fine there?
put breakpoint in procedure TComThread.ComPort1ReceiveCallBack(Data: String); and see if it fires at all.
btw. do you have to send anything through com port to get any response or once connected you should receive data?

ziolko.
Ok,

I've traced it all the way through TComThread.Execute (ComPort unit) to the line that is

WAIT_OBJECT_0 + 1: Synchronize( EventHandler )
(line 657)
At which line, I hit F8 to step another line, and it never comes back.

Tracing (F7) through that line i end up at line 9583 of the Classes unit...

WaitForSingleObject(SyncProc.Signal, INFINITE);

Never get past this line.
Oh and the device connected to the port is constantly streaming data, no need to send any data to get a response...
if you reached this: WAIT_OBJECT_0 + 1: Synchronize( EventHandler ) it means theres something coming to com port
so something is wrong in firing events, don't debug Synchronize() with F7 instead put break point in TCommThread.EventHandler; in ComPort.pas
and see if it comes to FOnReceive( CharsToRead ) line, if it comes there with F7 you should go to TComPort.ReceiveNotify() within this mthod you should reach FReceiveCallBack( TempBuf ) line then with F7 you should jump to TComThread.ComPort1ReceiveCallBack();

in other words program flow should be:
TCommThread.EventHandler
TComPort.ReceiveNotify()
TComThread.ComPort1ReceiveCallBack()

check where this flow is broken and we'll see what can be done about it:)

ziolko.
Breakpoint on line ClearCommError( hCom, ErrorMask, @ComStat );  (590), nothing at all.

Although when I fire closeport....   which calls FreeAndNil(ComThread);, it does fire TCommThread.EventHandler;... goes through to if Assigned( FOnReceive ) then, which apparently is unassigned at that point.  Commenting out that line give an AV on the next line ...FOnReceive( CharsToRead );, read of address 00000000

Fun and games! :-)
Ok this is just getting weird... maybe I'm doomed or something!  (Or maybe It's trying to tell me I can't program and to find another job...)

I commented out synchronize in....    WAIT_OBJECT_0 + 1: {Synchronize(}EventHandler //)
And added ShowMessage(Data); in

procedure TComThread.ComPort1ReceiveCallBack(Data: String);
begin
  ShowMessage(Data);
  ReallocMem(FBuffer, Length(Data) + 1);


And with alot of complaining It spat some data out to my memo.  As soon as I remove the ShowMessage, nothing.

Also throws an exception in the graphics unit (canvas does not allow drawing) after about the second piece of data is received. (related to the show message I assume??)

Actually, just played a bit more, put the synchronise back in.  That cures the graphics error and data still coming out (with no complaining)... so that explains the exception.

So now, if I put in the showmessage, I get data, if I take it out... I dont.
I have confirmed that all 3 lines

  ReallocMem(FBuffer, Length(Data) + 1);
  StrPCopy(FBuffer, Data);
  Synchronize(SendBackData);

in procedure TComThread.ComPort1ReceiveCallBack(Data: String);
are being compiled.

>>Breakpoint on line ClearCommError( hCom, ErrorMask, @ComStat );  (590), nothing at all.

you mean it hangs there??
try also change it a bit:

if not ClearCommError( hCom, ErrorMask, @ComStat ) then
  ShowMessage(SysErrorMessge(GetLastError))

ziolko.
Now that I put the showmessage into ComPort1ReceiveCallBack it is breaking on ClearComError.

Without the showmessage, It doesn't get anywhere near it... so doesn't even get a chance to hang.
oops sorry missed your comment (20821994), didn't refresh my webbrowser.

by "don't debug Synchronize() with F7" I meant don't try to step in with F7 not remove Synchronize :)
without Synchonization you'll always get AVs.. a lot of them:)

>>So now, if I put in the showmessage, I get data, if I take it out... I dont.
that's very strange strange, try to put Sleep() instead on ShowMessage, I know this is not a nice solution but I'm courious if it works.

if you have some spare time you can try comport from courceforge:
http://sourceforge.net/project/showfiles.php?group_id=76595&package_id=77312&release_id=380733

ziolko.
>> by "don't debug Synchronize() with F7" I meant don't try to step in with F7 not remove Synchronize :)
without Synchonization you'll always get AVs.. a lot of them:)

Yup I know, just experimenting...

Sleep(1), 1000 and 5000 did nothing.  I also tried MessageBox instead of Showmessage. didn't work either.

Seems our beloved comport has a fetish for showmessage.

Will try SF comport now.
one more thing,
you said that if you call ShowMessage() in TComThread.ComPort1ReceiveCallBack then you have data, you mean data is calledback from DLL to EXE? if so is what you've been expecting or just some trash?

ziolko.
Yes, data is passed back to the EXE from DLL and put in memo. Data is exactly what I was expecting.
this is just wild guess but...

procedure TComThread.ComPort1ReceiveCallBack(Data: String);
begin
  ProcessMessages;
  ReallocMem(FBuffer, Length(Data) + 1);
  StrPCopy(FBuffer, Data);
  Synchronize(SendBackData);  
end;

ziolko.
Really, really weird.

Now, It won't work even if I put showmessage in... (I un-installed component to install SF one... both have same classname.  reinstalled this one after uninstalling sf one again (to try ProcessMessages).

But, If I remove Synchronize from both Synchronize(SendBackData); and WAIT_OBJECT_0 + 1: Synchronize(EventHandler) it works fine.

As soon as I put synchronize back in on either of those lines it stops.

Any chance it could be something to do with a thread (main) taking all the execution time and not releasing it?
I thought about it (removing synchronize) I was afraid that it will lock itself but I did small test and I had no problems.
also if you don't use synchronization at all you'll get AV sooner or later.

>>Any chance it could be something to do with a thread (main) taking all the execution time and not releasing it?
I don't think so, it's rather problem of of calling synchronize twice

Synchronize(EventHandler) - that's first synchronize and EventHandler calls FOnReceive( CharsToRead ) which calls TComThread.ComPort1ReceiveCallBack and tshi calls Synchronize(SendBackData), this way it's synchronize within synchronize

so try also this:
  ReallocMem(FBuffer, Length(Data) + 1);
  StrPCopy(FBuffer, Data);
  SendBackData;  

ziolko.
Tried that, with Synchronize(EventHandler), no go.
Removed Synchronize from EventHandler, works fine.
Added Synchronize to SendBackData, no go.

2nd one appears to work ok, but as you say, no synchronization will result in an AV sooner or later so I'm a little uneasy about leaving it like that....
you can synchronize it your self but try sourceforge comport with sync method smWindowSync if it fails we'll get rid of Synchronize() and do it other way.

ziolko.
>>you can synchronize it yourself

jeeez that would require sync with VCL main thread... I need a break

ziolko.
I had a play with sourceforge CP, but stepping through the create proc it got to proc end and then jumped back to  

FComPort.ReceiveCallBack := ComPort1ReceiveCallBack;

over and over.

I even commented out the callback line and it still kept going back to it.  This is all getting way weird.  
Might use another copy of Delphi on a second PC here and get another compiler's opinion.

Break sounds good. I'm off to bed. Enjoy the rest of your afternoon.

 
note that CP from SF has different events, you should use OnRxChar and within handler call Read or ReadStr, also it has some more properties (ControlDTR, ControlRTS... ) to set so firstly make it work in simple EXE

get some good sleep:)

ziolko.
Hmmm this can't be right... looks like it's working!
I even have Synchronize around Sendbackdata!

Using SF comport...
Two main things there that got it to work.  I was using the ComDataPacket coz I couldn't workout how to get data out of the comport direct.  your last comment help me get rid of the datapacket and use onRXChar (which i looked at originally but there was only a count passed in... didn't think to look on the port itself.

Also... you were talking about smWindowSync up there ^... didn't mean much at the time, but just happened to be looking at comports properties and noticed smThreadSync. Changed it to window and she springs to life.  only works with window sync.

And I've even got data going from the app thru dll to the comport (opposite direction)...
Hit button, calls dll func SendData, that calls SendData in thread, which calls comports writestr function.
No synchronize anywhere in there which I believe I do not need.

So in summary, either I've really screwed something up which has made it work, or the old comport had issues with synchronization??

>>either I've really screwed something up which has made it work

this happens sometimes if you make some changes and forget to build (not just compile) both EXE and DLL

>>he old comport had issues with synchronization??

SF comport is a bit more advanced than the other one, but if you choose smThreadSync on SF CP it should work pretty much the same as other one. I always used SF component with window sync and never couldn't be arsed to work out what happens with other settings, why bother if it works fine for me:)

ziolko.

Hmmm true.

Well thanks for all your help on this and for not giving up after the first little bit.
I'll accept your first post as that technically was the answer to my question, but for anyone reading this, everything above helped in one way or another.

Thanks again for all your help.

Regards

Philip
Thanks again. Looks forward to working with you again in the future! :-)
>>Well thanks for all your help on this and for not giving up after the first little bit.

why would I give up?
I'm glad it works because I started to have doubts about what I did in earlier projects:)
sorry it took that long

ziolko.
Well you technically answered my question in the first post, so you could quite rightly have left it at that, and ignored all the sub-questions I bombarded you with :-)

>> sorry it took that long
No need to apologise, if you weren't there I'd still be searching the net and have made no progress!
>>Well you technically answered my question in the first post
it's not only about answering Qs it's about solving problems too:)

>>ignored all the sub-questions I bombarded you with :-)

I don't ignore sub-questions... well at least until someone wants to solve 2 different problems in single Q:)

ziolko.