ErikT 1 Posted yesterday at 11:16 AM This might be answered somewhere around here, but I simply cannot find it.  The scenario In order to avoid using Application.ProcessMessages when uploading a heap of data to an external device (which takes time), I'm currently learning more about threads. I have done this a little bit before, but never with an object that has events. I am using the CPDrv TCommPortDriver (ComDrv32) component for communicating with a serial port. I have used this a lot, and am quite familiar with it. I just haven't used it in a thread before,  When I create the component manually, I don't have the luxury of having the IDE set the event system up for me behind the scenes. This is what I am trying to do now. Before I paint myself into a corner, I would very much appreciate if someone would confirm that I am doing it the right way – or let me know if I am on the wrong track. I have searched through several web sites, picking up bits and pieces here and there, and this is what I have come up with:  My code I have defined a type for the "on receive data" event procedure (TCommPortReceiveData). In the definition of the thread, I use this type when declaring the event procedure (CommPortReceiveData). The lines I find relevant for this question are commented. unit Upload; interface uses System.Classes, System.SysUtils, CPDrv, TeCom, TeComStdRegs; type TCommPortReceiveData = procedure(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal) of object; // Type for receive data event procedure TUploadThread = class(TThread) private CommPortDriver1 : TCommPortDriver; // Declare an identifier for the TCommPortDriver object AbortUpld : Boolean; ProgressVal : Word; FName : string; PortNo : Byte; ModAddress : Byte; ModType : Cardinal; CommPortReceiveData : TCommPortReceiveData; // Declare the receive data event procedure procedure SendTelegram(TxTelegram : tcBuffer; TxTelegramLength : Word); procedure Execute; override; public constructor Create(AOwner : TComponent; FileName : string; PortNumber : Byte; Address : Byte; ModuleType : Cardinal); reintroduce; destructor Destroy; procedure AbortUpload; function Progress : Word; end; In the constructor, I create the object CommPortDriver1, and link the receive data event to my event procedure: constructor TUploadThread.Create(AOwner: TComponent; FileName: string; PortNumber : Byte; Address : Byte; ModuleType : Cardinal); begin inherited Create(false); AbortUpld := false; FName := FileName; PortNo := PortNumber; ModAddress := Address; ModType := ModuleType; CommPortDriver1 := TCommPortDriver.Create(AOwner); // Create COM port driver object CommPortDriver1.OnReceiveData := CommPortReceiveData; // Link event to procedure end; Now, in the code example above, I would have expected to type CommPortDriver1.OnReceiveData := @CommPortReceiveData; , to set the address of the procedure in CommPortDriver1.OnReceiveData. But then I get the error "Incompatible types: 'TReceiveDataEvent' and 'Pointer'"  Finally, I write the event procedure itself, using the same parameters as the object's definition of the event: procedure CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); // Event handler for received data var i : Word; RxBuffer : tcBuffer; begin if (DataSize > 0) then begin Move(PByteArray(DataPtr)[0], RxBuffer[0], DataSize); ... It compiles, and I may make it work. By I'm toggling between "That wasn't so bad" and "This can't really be it, can it?"  One thing I find odd is I can't declare the event procedure as procedure TUploadThread.CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); // Event handler for received data var i : Word; RxBuffer : tcBuffer; ... When I do that, I get the error "Declaration of 'CommPortReceiveData' differs from previous declaration". I guess that this has something to do with the way the procedure has been declared in the TUploadThread type.  A quick sanity check would be greatly appreciated. When I get this working properly, I intend post something for others to use as an example. Share this post Link to post
PeaShooter_OMO 15 Posted yesterday at 12:14 PM (edited) 59 minutes ago, ErikT said: CommPortReceiveData : TCommPortReceiveData; The CommPortReceiveData member you declared is incorrectly declared and should be somethin like: (Note that the parameters should be the same as CommPortDriver1.OnReceiveData) procedure CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); This should be a method of the class and in there you will handle the data you received. The way you did is not what you want and is what you would have done if you wanted an Event property to be added tou your class.. like so: private FOnCommDataReceive : TCommDataReceiveEvent public property OnCommDataReceive : TCommDataReceiveEvent read FOnCommDataReceive write FOnCommDataReceive; As a side note; try and use Delphi naming standards. Generally accepted, your class fields should have F prefixed to their names. Edited yesterday at 12:19 PM by PeaShooter_OMO 1 Share this post Link to post
Anders Melander 1848 Posted yesterday at 12:15 PM 47 minutes ago, ErikT said: Now, in the code example above, I would have expected to type CommPortDriver1.OnReceiveData := @CommPortReceiveData; , to set the address of the procedure in CommPortDriver1.OnReceiveData. But then I get the error "Incompatible types: 'TReceiveDataEvent' and 'Pointer'" CommPortReceiveData is a method pointer. @CommPortReceiveData is a pointer to a method pointer.  You have declared TCommPortReceiveData as a "procedure of object". That makes it a method pointer so this: procedure CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); is wrong (it's not a method; It's a procedure) while this: procedure TUploadThread.CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); is correct.  52 minutes ago, ErikT said: When I do that, I get the error "Declaration of 'CommPortReceiveData' differs from previous declaration". I guess that this has something to do with the way the procedure has been declared in the TUploadThread type.  You haven't declared the method. You have declared a pointer to a method. Your declaration in TUploadThread should look like this instead: TUploadThread = class(TThread) private ...other stuff... procedure CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); ...more stuff... end;  1 Share this post Link to post
ErikT 1 Posted yesterday at 12:27 PM (edited) Thanks a lot, @PeaShooter_OMOand @Anders Melander  My project will be temporarily halted by a trip to the vet, and then I'll have a closer look at your posts.   Edit: Okay, I had a quick look anyway. So what I gather from this is that I declare the event procedure (method) like I would do with any other in the class, and then the line CommPortDriver1.OnReceiveData := CommPortReceiveData; makes the link between the OnReceiveData event and my method?  And line TCommPortReceiveData = procedure(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal) of object; should never have been there in the first place? Edited yesterday at 12:34 PM by ErikT Share this post Link to post
PeaShooter_OMO 15 Posted yesterday at 12:47 PM Coming back to the threading; Remember that Thread-Safety is important and you need to be certain that the ComPortDriver does not do thread-UNsafe things. 1 Share this post Link to post
Anders Melander 1848 Posted yesterday at 01:05 PM 22 minutes ago, ErikT said: makes the link between the OnReceiveData event and my method? Yes.  22 minutes ago, ErikT said: should never have been there in the first place? It doesn't hurt but if you are not referencing the type then you might as well delete it.  10 minutes ago, PeaShooter_OMO said: you need to be certain that the ComPortDriver does not do thread-UNsafe things.  I couldn't spot any obvious issues after a brief look through the source.  One thing I would do though, is to move the TCommPortDriver.Create into the Execute method so it is constructed in the context of the thread, instead of the calling thread (which is supposedly the main thread). 1 Share this post Link to post
ErikT 1 Posted yesterday at 03:00 PM Thanks guys – this is very much appreciated! All in all, the solution is simpler and makes much more sense than I feared.  I have no idea if the COMPortDriver does thread-unsafe stuff. But I guess that I am going to find out. Share this post Link to post
ErikT 1 Posted yesterday at 03:11 PM 1 hour ago, Anders Melander said: One thing I would do though, is to move the TCommPortDriver.Create into the Execute method so it is constructed in the context of the thread, instead of the calling thread (which is supposedly the main thread).  Makes perfect sense. However, it isn't obvious to me which TComponent would be the owner. Share this post Link to post
Anders Melander 1848 Posted yesterday at 03:21 PM 1 minute ago, ErikT said: it isn't obvious to me which TComponent would be the owner. You don't need an owner; Just specify nil as the owner in the constructor and remember that you have to destroy the object in the thread destructor (or on exiting Execute, if you create it there). Â Generally, the purpose of Owner is to have a TComponent destroyed automatically when the owner is destroyed, which is handy when you place stuff on a form. For dynamically created components it is better to control the lifetime (Create/Destroy) explicitly so you have complete control of what is going on and when. It also makes the code easier to understand when you don't rely on implicit behavior. 1 Share this post Link to post
ErikT 1 Posted yesterday at 03:32 PM Thanks a lot @Anders Melander This, too, makes sense. Dang, I'm learning stuff today! Â My programming expertise is in microcontrollers. Bare metal. This is a very different world. Â @PeaShooter_OMOÂ Thank you for the tip regarding naming conventions. I have found the style guide, and have modified my code accordingly. Share this post Link to post
PeaShooter_OMO 15 Posted yesterday at 08:30 PM (edited) 4 hours ago, ErikT said: My programming expertise is in microcontrollers. Bare metal. This is a very different world It really is not that much different. I personally think Delphi can be much more alike to microcontroller programming than some other languages. You allocate memory, you remember to deallocate it. A good microcontroller programmer probably has that ingrained. You most probably have an advantage to those starting their programming career on multi-core, mega-memory, full fledged operating system computers; you are conscious about space and performance. Just like on microcontrollers there are ways of doing things in Delphi that might work but most of the time there are the correct ways to do things. You learn as you go. Threading is so cool once you get the hang of it, just like pointers. The main rule is; if it is not an atomic operation when accessing/changing some memory then you must be conscious about which thread is accessing what and when and whether changing it can cause another thread some issues. Read up on thread-safety if you have not already. Ask questions here as there are brilliant and very knowledgeable Delphi experts on this forum. Anders Melander being one of them. Edited yesterday at 08:33 PM by PeaShooter_OMO 1 Share this post Link to post
Anders Melander 1848 Posted 23 hours ago 1 hour ago, PeaShooter_OMO said: You most probably have an advantage to those starting their programming career on multi-core, mega-memory, full fledged operating system computers; you are conscious about space and performance. I agree. One of the things that have helped me tremendously over the years as a software developer is my (hobby) background in first analog electronics and then digital, hardware and then software. It gives one a deep understanding of how things work and why. There was also an early phase where I ventured into chemistry but, although I got really good at blowing shit up, I don't think that particular skill set has helped me much 🙂  (and thanks 😘 ) 2 Share this post Link to post
ErikT 1 Posted 14 hours ago (edited) 10 hours ago, PeaShooter_OMO said: It really is not that much different. I personally think Delphi can be much more alike to microcontroller programming than some other languages. You allocate memory, you remember to deallocate it. A good microcontroller programmer probably has that ingrained. You most probably have an advantage to those starting their programming career on multi-core, mega-memory, full fledged operating system computers; you are conscious about space and performance. Just like on microcontrollers there are ways of doing things in Delphi that might work but most of the time there are the correct ways to do things. You learn as you go. Threading is so cool once you get the hang of it, just like pointers. The main rule is; if it is not an atomic operation when accessing/changing some memory then you must be conscious about which thread is accessing what and when and whether changing it can cause another thread some issues. Read up on thread-safety if you have not already. Ask questions here as there are brilliant and very knowledgeable Delphi experts on this forum. Anders Melander being one of them. Well... one of the things that I find very different is that when I write code for a microcontroller, nothing is "hidden" from me. There is nothing going on in the background, without my knowledge. My while (1) main loop is always running. If I happen to exit it, there is nothing. In Delphi, Qt and the like, there is always a bunch of stuff that I don't see. If I make a tiny program that is made solely from events (button clicks, timers, edit field changes and such), none of my code is doing anything until an event is triggered. This makes the whole thing very abstract to me.  One of the microcontroller-equivalents to events is interrupts. For some reason, these are much more tangible to me. Received a byte on USART2, and the receive interrupt is enabled? Okay, let's jump to the code at the address listed in the interrupt vector table. Done. (Keeping in mind the non-atomic operations, of course.) Right now I'm trying to figure out home made events in Delphi. That's quite a different story. 😕  BUT: I definitely agree with you that having a solid understanding of hardware and the "small" stuff is a great advantage when moving to programming computers. It must be much harder going the other way: "Hey, where did all my megabytes go? Why do I get 20 or more interrupts when I only press the button once? And when I release it as well? Why doesn't the rest of the World pause, when I pause my emulator?" (Real-life situations.)  I thank you and @Anders Melander for your help regarding my issue. Now I'll try to get into the home made event stuff.  P.S. Anders, at the university, I sucked big time at chemistry. I think it takes a special breed of people to truly understand that stuff. Edited 14 hours ago by ErikT Share this post Link to post
PeaShooter_OMO 15 Posted 13 hours ago (edited) @ErikT I can assure you it still is pretty much the same.  The Main application loop has been created for you by Delphi and that is inside the Application instance of TApplication and its setup you will find inside the DPR file (Project Source file). It is similar to what you will find in the Arduino ecosystem where the "Sketch" file is basicaly abstracted from the actual Main loop. The guys from Arduino thought it best to provide newbies two procedures; "setup" and "loop" in a sketch but there is another piece of code that has a Main loop that calls Setup and then just loops the Loop procedure.  Delphi's Main loop receives Windows Messages from the underlying OS. Almost like interrupts they inform the application what is happening in repect to hardware and OS related events. The Delphi Application instance will then act upon some of those messages or will send the rest on to all corners of its fiefdom and any object, window or control that wants to can have those messages and react.  For instance a Mouse Move event in the OS will cause the OS to send a correlating message to the Application instance if need be. The Application instance will check who should get the messages and then pass it on. A Button might receive the message if it is found that the mouse moved over the button's area and then the button can redraw with a highlighted display to show that the mouse is over it.  All these happen the whole time, in a loop the same as your Main loop checking and waiting for interrupts.  Below is Arduino's main.cpp. You don't see this when you use the Arduino IDE. Just like Delphi. Threading is just an extension of this whole process that allows you to do mutiple actions that seems to be happening at the same time. In the old days of mono-core CPUs it was done in a way where the OS will give each thread a time slice which will make it look as if the threads did their thing at the same time but it was still a sequential process. Nowadays with multi-core CPUs each core will be doing that which means that a single core will be giving its own threads each a time slice but across cores many threads might really be running at the same time. In an environment like Delphi you can be totally unaware of most of this and still create a great application and never even encounter any of the behind-the-scenes code. But it is good to know how these things work because it broadens your ability to figure out why a bug is doing what it is doing. If your Delphi edition has the source code for the VCL and FMX then go into that code and check it out.  #include <Arduino.h> // Declared weak in Arduino.h to allow user redefinitions. int atexit(void (* /*func*/ )()) { return 0; } // Weak empty variant initialization function. // May be redefined by variant files. void initVariant() __attribute__((weak)); void initVariant() { } void setupUSB() __attribute__((weak)); void setupUSB() { } int main(void) { init(); initVariant(); #if defined(USBCON) USBDevice.attach(); #endif setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; }  Edited 13 hours ago by PeaShooter_OMO 1 Share this post Link to post
DelphiUdIT 205 Posted 13 hours ago Compared to microcontroller programming, you have to think of an application in Delphi as if it were a stack for microcontroller services: like a Bluetooth stack or a TCP/IP stack. You are building something that has to communicate with other entities, you are building a stack.... Your program has to communicate with all the related entities, starting from the individual "components" that you use. You have to understand that your program is part (if you build it for Windows, but the same conditions apply for other OSes) of an ecosystem that includes some basic functional rules. In your case of a microcontroller, I don't think you have built a TCP/IP stack from scratch, you will use one of those available (among other things, all manufacturers provide at least one TCP/IP stack). With that stack, your code has to "talk" to it and interact. And "things" happen that are not under the control of your code, even beyond the IRQs. With an application in Delphi it is the same, only that instead of interacting within an island (your microcontroller) you are acting within a continent (the Windows ecosystem). You have to learn the rules and basic functions, as you learned to use a TCP/IP stack. But, the world is more bigger than an island ..... Share this post Link to post
ErikT 1 Posted 10 hours ago @PeaShooter_OMO I never ever use Arduino. But thank you for the comparison.  @DelphiUdIT You are correct – I have not built a TCP/IP stack from scratch. It is already hard-coded in the Wiznet W5500 Ethernet controller that I talk to via SPI. 😉 But I totally get your point, and I've made many other things in microcontrollers that could be stand-ins for the TCP/IP stack. You are very much nailing the trouble I have: With the microcontroller, I am in control of the island. When I write software for a computer, I govern only a tiny speck in a continent that is controlled by someone else. And many of the continent's rules are pretty difficult to figure out.  On that note, googling how to do something specific comes with a serious caveat: More than once, I have found erroneous or overly complicated solutions (e.g. on stackoverflow and such sites), that can really send a newbie like me in the wrong direction. This is exactly what happened in this case. Thankfully, @PeaShooter_OMO and @Anders Melander were so kind to guide me back on track – and to a very simple solution, I might add.  I prefer to ask questions on this forum, but I want to at least try to find my way first. I don't wish to be known as the "Please code it for me" guy. Share this post Link to post
DelphiUdIT 205 Posted 8 hours ago 1 hour ago, ErikT said: I prefer to ask questions on this forum, but I want to at least try to find my way first. I don't wish to be known as the "Please code it for me" guy. Don't worry, when you feel the need just ask. Someone will help you. You'll find that Delphi (Pascal) will help you on the way. Â Also try to have a quick read about the various topics in the various manuals available, there are several available even for free. You can also do a search on the forum and you will find several links about this. Â Embarcadero's online wiki can also help you. Enjoy and good works. 1 Share this post Link to post