[Webinar] Streamline your web hosting managementRegister Today

x
?
Solved

Comport DLL-type interface with streaming data

Posted on 2008-02-02
39
Medium Priority
?
2,319 Views
Last Modified: 2013-12-03
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
0
Comment
Question by:philly_tee
  • 20
  • 19
39 Comments
 
LVL 21

Accepted Solution

by:
ziolko earned 2000 total points
ID: 20803927
well first of all I suggest get rid of form :)

consider creating thread in DLL which will deal with comms if you cracked callbacks thats great just remember to synchronize callback calls from DLL's thread back to app.

ziolko.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20804028
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
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20804357
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.
0
Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

 
LVL 3

Author Comment

by:philly_tee
ID: 20807222
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

0
 
LVL 21

Expert Comment

by:ziolko
ID: 20808418
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.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20808422
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
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20812536
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
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20812562
will take a look at this one more.
do you use ComPort from Sourceforge.net?

ziolko.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20812667
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
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20812706
ok, let's stay with component you use, i'l ldownload it and try to get it working

ziolko.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20812807
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20818495
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?
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20821298
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20821700
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20821781
Oh and the device connected to the port is constantly streaming data, no need to send any data to get a response...
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20821810
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20821869
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! :-)
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20821994
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.

0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822004
>>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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822031
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.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822088
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822114
>> 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.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822221
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822235
Yes, data is passed back to the EXE from DLL and put in memo. Data is exactly what I was expecting.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822298
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822372
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?
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822430
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822464
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....
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822504
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.
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822555
>>you can synchronize it yourself

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

ziolko.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20822641
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.

 
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20822681
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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20828327
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??

0
 
LVL 21

Expert Comment

by:ziolko
ID: 20829800
>>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.

0
 
LVL 3

Author Comment

by:philly_tee
ID: 20830224
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
0
 
LVL 3

Author Closing Comment

by:philly_tee
ID: 31427455
Thanks again. Looks forward to working with you again in the future! :-)
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20830303
>>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.
0
 
LVL 3

Author Comment

by:philly_tee
ID: 20830315
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!
0
 
LVL 21

Expert Comment

by:ziolko
ID: 20830366
>>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.
0

Featured Post

Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
This is an update to some code that someone else posted on Experts Exchange. It is an alternate approach, I think a little easier to use, & makes sure that things like the Task Bar will update.
The purpose of this video is to demonstrate how to set up the WordPress backend so that each page automatically generates a Mailchimp signup form in the sidebar. This will be demonstrated using a Windows 8 PC. Tools Used are Photoshop, Awesome…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…

612 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