Jump to content

ErikT

Members
  • Content Count

    32
  • Joined

  • Last visited

Everything posted by ErikT

  1. ErikT

    Event handler in thread

    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.
  2. ErikT

    Event handler in thread

    @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.
  3. ErikT

    Event handler in thread

    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.
  4. ErikT

    Event handler in thread

    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.
  5. ErikT

    Event handler in thread

    Makes perfect sense. However, it isn't obvious to me which TComponent would be the owner.
  6. ErikT

    Event handler in thread

    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.
  7. ErikT

    Event handler in thread

    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?
  8. This one might generate some frowns... I've used Application.ProcessMessages quite often, for several reasons. Most of my applications communicate with an external device via a serial port, USB or Ethernet. Each transaction can take some time, and since it is often crucial that my application gets a response before moving on, I include a while loop (with a timeout). In this loop, apart from checking if data has been received, I make sure to make an Application.ProcessMessages call, so the rest of my application isn't blocked. In the past, this has worked fine. Why this method? Because I think multiple threads are way too difficult to work with, so I don't use them unless it is absolutely necessary. And, as I wrote, it works fine. Until now, that is. In my current application, I communicate almost continuously with the external device, to get some monitoring values, status bits and such from the device. This is run by a timer, as this is the only way I can figure out how to make something run seemingly continuously. A bit like the while (1) loop in a C program for a microcontroller. Apart from the timer based "main loop", I have a few buttons that can be used for sending various commands to the device. These access the same physical port as the "main loop", so I have some boolean variables that are set and cleared, so one process can wait for the other to finish. Now for the juicy part. I have had problems with the application seemingly stalling once in a while, and couldn't figure out why. When this happens, debug stepping just keeps looping through a limited number of instructions in the CPU tab. So for quite some time, I didn't know which part of my own code started the problem. I also noticed that the application wasn't actually completely stalled, as there was a separate timer that kept feeding data into some graphs. So it was clearly only certain parts that didn't work. Now I added some global debug variables, and added lines in the code, giving the variables different values. Such as (...) repeat ButtonDebugValue := 2; while (CommBusy or CommCycle) do begin ButtonDebugValue := 3; Application.ProcessMessages; ButtonDebugValue := 4; (...) I did that in the suspected procedures, and made sure to place some before and after Application.ProcessMessages, as I had become particularly suspicious about this. As it turned out, two events (OnTimer and OnClick) were both stuck in an Application.ProcessMessages procedure. At the same time. Not even rolling around in the loop, but just stuck in the procedure. My theory: Application.ProcessMessages messes up if it is called more than once at a time. What I mean is: Process C (the button click) is waiting for process B (the timer), which is waiting for process A (comms receive). When the Application.ProcessMessages call in one event allows a different Application.ProcessMessages call in another event, things go south. The application doesn't stall as such, but those two Application.ProcessMessages never get any further, They are just blocked. What do you think? Is this theory plausible? I am not in need of help to fix this – I would just like to know if I am right or wrong. And maybe let this act as a warning against frivolous use of Application.ProcessMessages.
  9. I am unsure if this is even possible in Delphi. I have a GUI, designed for customer use, My employer wishes to have a version of the same software for internal use, where some extra features are available. I intend to put these extra features on an extra TabSheet in a PageControl object. For regular customers, this TabSheet is invisible. My intention was to have two different build configurations: One for customers and one for internal use. Then use a preprocessor symbol to let the code know which configuration it is. However, I have read elsewhere, that Delphi doesn't have a preprocessor, so is this even possible to do? I have defined this symbol in the IDE: Then, in the FormCreate procedure, I have added this line: {$IFDEF __BuilderGUI} TabSheet4.TabVisible := true; {$ENDIF} However, the pascal line is never run. The compiler doesn't seem to "see" the symbol. I've also tried defining the symbol this way (even though I didn't believe in it): If I add {$DEFINE __BuilderGUI} in my code, the $IFDEF recognizes the symbol and acts accordingly. So at least that part works. Am I doing something wrong in the symbol definition, or is what I'm trying to do simply not possible? Should I instead include the $DEFINE in a file that is not included in the customer configuration?
  10. ErikT

    Ping-pong between two Application.ProcessMessages

    I have no idea what an immutable object is. Will need to look into that.
  11. ErikT

    Ping-pong between two Application.ProcessMessages

    Of course! If the calls happen in the "wrong" order, so process B's Application.ProcessMessages call ends up waiting for process A to end, then they are well and surely stuck there. Thanks! Often, the only feasible way around using an Application.ProcessMessages call is to use multiple threads. And, to quote "dummzeuch" in a blog entry, that opens another can of worms. https://blog.dummzeuch.de/2018/09/29/calling-application-processmessages-in-a-delphi-program/
  12. I am using TeeChart in a GUI that continuously displays some readouts from a device. Generally, it works fine, but there is one annoyance. I use the automatic axis scaling, but would like to be able to set some constraints to that. A sort of "maximum zoom". The thing is that when a readout has been quite stable for a while, the automatic scaling feature zooms in so much that the numeric resolution becomes annoyingly visible. Example: If the example above is allowed to run for a while, the chart may be complete filled with one big blue rectangle due to this. It would be nice to be able to set a minimum distance between minimum and maximum, so the left axis in the example above would go from 42 to 52, for instance, with the blue graph being nicely centered in the chart. However, I haven't found such a setting. But since I don't exactly understand every explanation of the properties and methods in the help document, I wonder if I just haven't found it. So... does anyone have a cool solution, or do I need to go the complicated way of setting the axis minimum and maximum manually in my code, based on the series values?
  13. ErikT

    TeeChart constraints on automatic axis

    When I began poking around, it turned out to be not-so-complicated after all. I've made this piece of code, which seems to work nicely: procedure AdjustAxis(WhichChart : TChart; MinInterval : Double); var ChartMaxY : Double; ChartMinY : Double; ChartCenter : Double; begin ChartMaxY := WhichChart.MaxYValue(WhichChart.LeftAxis); ChartMinY := WhichChart.MinYValue(WhichChart.LeftAxis); if ((ChartMaxY - ChartMinY) < MinInterval) then begin ChartCenter := (ChartMaxY + ChartMinY) / 2; WhichChart.LeftAxis.SetMinMax(ChartCenter - (MinInterval / 2), ChartCenter + (MinInterval / 2)); WhichChart.LeftAxis.AutomaticMaximum := false; WhichChart.LeftAxis.AutomaticMinimum := false; end else begin WhichChart.LeftAxis.AutomaticMaximum := true; WhichChart.LeftAxis.AutomaticMinimum := true; end; end; The parameters are WhichChart, which is the name of the chart, and MinInterval, which is the smallest acceptable numerical difference between minimum and maximum on the left axis. Must be positive, by the way. The chart graph is centered between maximum and minimum in the chart window. Of course it can be made smarter. For instance, this only works on the left axis. Feel free to use and modify.
  14. ErikT

    TeeChart constraints on automatic axis

    Okaaaay... Definitely one to consider. Thanks!
  15. I have run into a snag regarding MAC addresses, or retrieving them from devices. I don't know if this is the correct forum for this question, but it is the best that comes to my mind. Background: I have created a GUI that communicates with a PCB that has a standard Ethernet port (wired). My GUI searches for available IP addresses, and then retrieves the MAC address for each of these, using the WinAPi.IpHlpApi SendARP function. The 36 most significant bits of the MAC address are then used for filtering. Recently, my lab has been moved to a new location, with no wired access to the Router / DHCP server. Since my PCB only has a standard wired Ethernet port, I have set up an access point in "client mode", to make a tiny wired network in the lab. (TP-Link WA801N) This seems to work nicely, except... The problem: My GUI cannot find the PCB, and I've figured out that the reported MAC address is wrong when the PCB is connected to the access point. I can find the board's IP address with Advanced IP Scanner, but the reported MAC address is the same as the access point's MAC address. For security, I have partially obscured the MAC addresses, but the two MAC addresses marked with yellow are identical. The DOS command arp -a says basically the same thing. However, if I connect a computer to the same ethernet switch as my PCB (with a network cable), then I get the correct MAC address through that connection. Can anyone explain what is going on here? Is it natural that an access point overrides the MAC addresses of the connected devices? Could there be some obscure setting that I need to change? Or is there something wrong with the access point? Any help will be greatly appreciated.
  16. @Attila Kovacs Thanks, but I have no idea how to do that in the Sagecom router that I have. Cannot find any menu item like that. However, it won't really make a difference, because if this can happen at my place, then it can happen at the customers as well. So even if I do get it solved here, it won't be a general solution. I will just have to use a different method to identify the devices.
  17. @Brian Evans Yes, sounds plausible. A very good guess. Thank you for that! I think I'll write to the manufacturer of the access point and ask what is going on, and if there is a way to make it behave. My device is definitely not changing its MAC address by itself, as the firmware isn't designed to do that.
  18. @DelphiUdIT Thanks, but I am not quite sure what I am looking at.
  19. I am trying to make a thread, and thought that I had understood how to pass initial values to that thread while creating it. I have used examples from Embarcadero, combined with examples from threads in this forum. However, I get an access violation at the first line of code in the constructor. Type definition of thread and form: type TPingThread = class(TThread) private LocOwnIP : in_addr; LocFromIP : Byte; LocToIP : Byte; Progress : Byte; LastByte : Byte; AddressList : TStringList; LocOwner : TComponent; Ping1 : TPing; protected procedure Execute; override; public constructor Create(AOwner : TComponent; OwnIP : in_addr; FromIP : Byte; ToIP : Byte); reintroduce; destructor Destroy; function GetProgress : Byte; function GetLastByte : Byte; end; type TForm1 = class(TForm) ListBox1: TListBox; Label1: TLabel; Label2: TLabel; Button1: TButton; Label9: TLabel; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } PingThread1 : TPingThread; end; var Form1: TForm1; Definition of constructor, destructor and Execute procedure: constructor TPingThread.Create(AOwner : TComponent; OwnIP : in_addr; FromIP : Byte; ToIP : Byte); begin inherited Create(false); // <-- Access violation happens here LocOwnIP := OwnIP; LocFromIP := FromIP; LocToIP := ToIP; LocOwner := AOwner; end; destructor TPingThread.Destroy; begin AddressList.Free; inherited Destroy; end; procedure TPingThread.Execute; var IPAddressRec : in_addr; i : Byte; TempInt : Integer; PingResult : Integer; begin AddressList := TStringList.Create; AddressList.Clear; Ping1 := TPing.Create(LocOwner); for i := LocFromIP to LocToIP do begin IPAddressRec.S_un_b.s_b4 := i; if (IPAddressRec.S_addr <> LocOwnIP.S_addr) then begin LastByte := i; if (LocFromIP < LocToIP) then Progress := ((i - LocFromIP) * 100) DIV (LocToIP - LocFromIP); Ping1.Address := inet_ntoa(IPAddressRec); PingResult := Ping1.Ping; if (PingResult <> 0) then AddressList.Add(Ping1.Address); end; end; end; The thread is created in a button click event: procedure TForm1.Button1Click(Sender: TObject); var DelayVal : TDateTime; Handle1 : THandle; OwnIP : in_addr; TempStr : string; begin // blah-blah... PingThread1.Create(Form1, OwnIP, 1, 31); Handle1 := PingThread1.Handle; while ((PingThread1 <> nil) and (WaitForSingleObject(Handle1, 0) <> WAIT_OBJECT_0)) do // blah-blah PingThread1.Destroy; end; As soon as TPingThread hits its first line of code, I get an access violation. It doesn't matter if it is the "inherited Create" line that comes first, or if I rearrange it like this: constructor TPingThread.Create(AOwner : TComponent; OwnIP : in_addr; FromIP : Byte; ToIP : Byte); begin LocOwnIP := OwnIP; // <-- Access violation happens here LocFromIP := FromIP; LocToIP := ToIP; LocOwner := AOwner; inherited Create(false); end; In the first example, I get the access violation on the "Inherited Create" line. In the second example, I get it on the LocOwnIP := OwnIP line. I don't understand why. I thought that the variables defined in the thread's private section was accessible to all parts of the thread. Also, I think that it looks very much like the examples I've used. Am I missing something here?
  20. @Remy Lebeau Thank you for your comment.
  21. @Christophe E. After some further error-fixing, it now seems to work. Thank you very much! Best regards, Erik
  22. Oh, crap. This is so obvious. I've stared myselft blind at that. Thanks! True. I need to destroy Ping1 when done. Well spotted. About blocking: This is an example, just to get the thread to work. In the final version, there is supposed to be a number of threads, and the main thread will not be blocked.
  23. Hi, I am writing a piece of software, that needs to find specific devices on a LAN. The functionality I need is more or less a scaled-down version of "Advanced IP Scanner", or similar software. My intention is to connect to all devices in a subnet, one at a time, and get the MAC address from each device. I can determine if a device is of interest based on the 36 most significant bits of the MAC address. Now, how do I do that? I have found a number of examples that give me the MAC address of my own computer, such as this: And other examples that don't tell much about what they actually do, such as this: https://www.swissdelphicenter.ch/en/showcode.php?id=651 Does anyone around here know how to get the MAC address of devices on a LAN in Delphi?
  24. So am I. I also began with BASIC in the eighties, although I would never call my self a "hacker". My core competences are in hardware and microcontrollers. Not computer programming. I got the ping search running with ICS, although terribly slow. It seems that the timeout won't go below about 450 ms. I had hoped to use about 100 ms, as my devices rarely spend more than 1 ms when responding to a ping. However, it is certainly much better to have something that works slowly, than the faster option that doesn't work. Thank you for the ICS tip. It seems that poeple have been complaining about the Indy Ping problem for more than a decade, and apparently nothing has happened.
×