Jump to content

ErikT

Members
  • Content Count

    25
  • Joined

  • Last visited

Everything posted by ErikT

  1. 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.
  2. 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?
  3. ErikT

    Ping-pong between two Application.ProcessMessages

    I have no idea what an immutable object is. Will need to look into that.
  4. 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/
  5. 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?
  6. 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.
  7. ErikT

    TeeChart constraints on automatic axis

    Okaaaay... Definitely one to consider. Thanks!
  8. 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.
  9. @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.
  10. @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.
  11. @DelphiUdIT Thanks, but I am not quite sure what I am looking at.
  12. 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?
  13. @Remy Lebeau Thank you for your comment.
  14. @Christophe E. After some further error-fixing, it now seems to work. Thank you very much! Best regards, Erik
  15. 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.
  16. 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?
  17. 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.
  18. @Angus Robertson Thanks Angus. This might be a good solution as well.
  19. @Kas Ob. Okay, I see that the "hacker" term has mellowed over time. Apparently, I'm a bit old.
  20. @DelphiUdIT Yeah, I'm using ARP to get the MAC address. I hadn't thought about using it to find the actual devices directly. That should be easy to implement. I've got it running using ICS Ping. Isn't as fast as I'd hoped, but does work. I will have a go with the ARP method, as I already have part of the code running.
  21. Hello @Kas Ob. Thank you for your comments. 1) Yes, I do use WireShark, although in this particular case it hasn't helped me a whole lot. No packet stand out, making me believe that the problem with Indy Ping is internal in its code, and not on the network. 2) Thanks, I may have a look at that. However, I find it a bit disconcerting, that the word hack/hackers show up seven times on the first page alone...
  22. @JonRobertson Thank you for the tip! Will have a look at it tomorrow.
  23. Hello @JonRobertson Yes, that is exactly what I am doing. I have implemented the ping method, which nearly works. The devices I am searching for do respond to a ping, so that is no issue. They are not computers, by the way. At the moment I'm struggling with the Indy Ping procedure, that apparently has a problem. After running a seemingly random number of times (e.g. 20), it raises an exception: Socket Error #10040 Message too long. This seems to be a known issue with Indy Ping, and I am searching for a functioning work-around for it. After raising the exception, I can't get Ping going again without restarting the application. Will continue the search for a fix tomorrow.
  24. Update: I found another example, which I actually got working. So now I can give a function an IP address, and get a MAC address in return. So now my task is boiled down to figuring out if each address exixts on the LAN, or not. When I get both things working, I'll post the code here for others to use.
×