How to Fix Memory Leaks in Java

本文详细探讨了Java内存泄露的发生机制、诊断步骤和解决策略,通过实例展示了如何利用工具进行内存泄露分析,并提供了针对不同规模内存泄露的排查技巧。重点介绍了如何使用Heap Dump进行内存泄露定位,以及如何利用Eclipse Memory Analyzer Tool(MAT)和商业工具进行深入分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

How to Fix Memory Leaks in Java

|

Your pager hasn’t been sleeping well. It periodically wakes you up in the middle of the night to tell you that your server is firing off “OutOfMemoryError” messages. Worse still, your significant other forcibly relocated you to the couch and told you not to return until your pager stops buzzing.

Sound familiar? If so, you may have a case of memory leak induced insomnia, but fortunately we’ve got a cure for what ails you. This tutorial will teach you everything you need to know to ease your suffering, including what memory leaks are, why they happen, and how to diagnose and fix ‘em.

In this article we’ll focus on techniques that will enable you to address memory leaks with any commercial or free/open source memory profiler; we’re not here to recommend one tool over another. After all, the most important thing is that you fix the problem and get some rest, not the tool you use to get it done.

Before You Start

Ever heard the story about how Java has “automatic memory management”—you know, the one that someone in marketing upgraded to an epic tale about how you’ll never have to worry about memory leaks ever again? As is often the case, the truth is more complex than the marketing department made it out to be. While it’s true that Java’s garbage collector (GC) helps to eliminate the most common memory leak issues from applications, it is unfortunately still possible to experience memory leaks in Java. However, they happen a lot less often than they used to in the C or C++ days.

Many people believe that black magic and complex tools are required to fix memory leaks. This undeserved reputation is caused by the lack of good explanations of what they are and what to do when you encounter them. But with the proper tools and knowledge to fix memory leaks, they aren’t nearly as intimidating.

Methodology

In this article we’ll cover everything from memory leak basics to analyzing heap dumps, so—whether you’re an experienced Java developer or encountering Java memory leaks for the first time—you’ll be better prepared to deal with memory leaks by the time you reach the conclusion. We won’t outline a series of steps, like “do ABC with commercial tool XYZ and don’t ask why,” as that approach doesn’t work and implies that remedies are more complex then they really are. Instead, we’ll give you the background information necessary to address memory leaks, with emphasis placed on particular steps you’ll need to execute. Similarly, we’ll assume that you can learn on your own how to use the memory profiler of your choice; what’s missing is an understanding of what the tool is trying to do and why, so that will be the focus of this article.

Java will be used for all examples, so all information in this article directly applies to Java applications running standalone or as a part of J2EE/JEE/Tomcat-based application server. But remember, although our primary focus is on Java, most of the process of diagnosing and fixing memory leaks described herein applies to other languages with garbage collectors. So even if you’re using Ruby, C#, or Python, there should be something for you in this article.

What Are Memory Leaks?

Let’s start by describing how memory leaks happen in Java. Java implements automatic garbage collection (GC), and once you stop using an object you can depend on the garbage collector to collect it. While additional details of the collection process are important when tuning GC performance, for the sole purpose of fixing a memory leak we can safely ignore them.

When is memory eligible for GC? Let’s take a look at an example:


gc-eligable

We don't have to do anything special to make an object eligible for GC—we just eliminate any references to it, and it "magically" disappears and stops using memory. That's why we say that Java performs "automatic" GC.

Why "eligible" for GC? Because objects are not collected immediately. GC is not instantaneous and comes with some performance impacts. Consequently, Java doesn't immediately collect every object that is eligible for collection; it typically postpones collection until a more convenient time later on. The way to think about GC in Java is that it's a "lazy bachelor" that hates taking out the trash and typically postpones the process for some period of time. However, if the trash can begins to overflow, Java immediately takes it out. In other words, if memory becomes scarce, Java immediately runs GC to free memory.

Since we don't need to do anything special in order to dispose of objects in Java, how do memory leaks happen in Java? Memory leaks occur when a program never stops using an object, thus keeping a permanent reference to it.

Let's take a look at an example that helps illustrate this point. The following code will cause all available memory in the JVM to be exhausted:

memory_leaks_figure6

When no more memory is remaining, an OutOfMemoryError alert will be thrown and generate an exception like this:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at
MemoryLeakDemo.main(MemoryLeakDemo.java:14)

In the example above, we continue adding new elements to the list memoryLeakArea without ever removing them. In addition, we keep references to the memoryLeakArea, thereby preventing GC from collecting the list itself. So although there is GC available, it cannot help because we are still using memory. The more time passes the more memory we use, which in effect requires an infinite amount memory for this program to continue running.

This is an example of unbounded memory leak—the longer the program runs, the more memory it takes. So even if the memory size is increased, the application will still run out of memory at a later date.

Meat & Potatoes


Fixing Memory Leaks


As illustrated by the flowchart below, the process of fixing memory leaks is fairly simple:



memory_leaks_figure1

Memory leaks are misunderstood creatures. Just getting an OutOfMemoryError alert doesn’t necessary mean that you're suffering from a memory leak. So, before you dive into "fixing" the problem, you must first find out whether or not a memory leak actually exists. If a memory leak does in fact exist, the next step is to determine which objects are leaking and uncover the source of the memory leak. Then, you fix it.

We'll skip past the initial steps and dive right into diagnosing whether or not the problem is a memory leak.

Is My Program Leaking Memory?


Not every OutOfMemoryError alert indicates that a program is suffering from a memory leak. Some programs simply need more memory to run. In other words, some OutOfMemoryError alerts are caused by the load, not by the passage of time, and as a result they indicate the need for more memory in a program rather than a memory leak.


To distinguish between a memory leak and an application that simply needs more memory, we need to look at the "peak load" concept. When program has just started no users have yet used it, and as a result it typically needs much less memory then when thousands of users are interacting with it. Thus, measuring memory usage immediately after a program starts is not the best way to gauge how much memory it needs! To measure how much memory an application needs, memory size measurements should be taken at the time of peak load—when it is most heavily used.

The graph below shows the memory usage in a healthy Java application that does not suffer from memory leaks, with the peak load occurring around 10 AM and application usage drastically decreasing at 5 PM. Naturally, the peak load on business applications often correlates with normal business hours.

memory_leaks_figure7

The application illustrated by the chart above reaches its peak load around 10 AM and needs around 900MB of memory to run. This is normal behavior for an application suffering from no memory leaks; the difference in memory requirements throughout the day is caused solely by the user load.

Now, let's suppose that we have a memory leak in the application. The primary characteristic of memory leaks is that memory requirements increase as a function of time, not as a function of the load. Let's see how the application would look after running for a few days with a memory leak and the same peak user loads reached around 10 AM every day:

memory_leaks_figure8

Because peak loads on the system are similar every morning but memory usage is growing over a period of a few days, this picture indicates a strong possibility of memory leaks. If the program eventually started suffering from OutOfMemory exceptions, it would be a very strong indication that there’s a problem with memory leaks. The picture above shows a memory leak of about 100MB per day.

Note that the key to this example is that the only thing changing is the amount of time the system is up—the system peak load doesn’t change over time. This is not the case for all businesses. For example, the peak load for a tax preparation service is seasonal, as there are likely more users on the system in April than July.

There is one special case that should be noted here: a program that needs to be restarted periodically in order to prevent it from crashing with an OutOfMemoryError alert. Imagine that on the previous graph the max memory size was 1100MB. If the program started with about 900MB of memory used, it would take about 48 hours to crash because it leaks about 100MB of memory per day. Similarly, if the max memory size was set to 1000MB, the program would crash every 24 hours. However, if the program was regularly restarted more often than this interval, it would appear that all is fine.

Regularly scheduled restarts may appear to help, but also might make “upward sloping memory use” (as shown in the previous graph) more difficult to notice because the graph is cut short before the pattern emerges. In a case like this, you’ll need to look more carefully at the memory usage, or try to increase the available memory so that it’s easier to see the pattern.

Monitoring Memory in Java

As you are already aware, you need to measure memory that is free and used inside of the JVM, not memory that the JVM process is using. In other words, the top/Task Manager/Activity Monitor will measure how much memory your Java process is using, but that’s not what you need. You need a tool that can look inside the JVM process and tell you how much memory is available for your program running inside the JVM.

In addition, keep in mind that Java GC is not a constant process—it runs in intervals. The memory usage that you see in the JVM is usually higher then what your program needs at the moment, as GC hasn’t yet run. Remember, lazy bachelors usually have some trash in their apartments waiting to be taken out.

So, what you need to investigate is not current memory usage, but rather the average usage over a long period of time. For example, if your program is currently using 100MB of memory and five seconds later it’s using 101MB, that’s not an indication of a memory leak because GC might free up memory when it eventually runs. But if your program’s memory usage increases over a long period of time under constant usage, you might have trouble on your hands.

There are a couple of options for measuring the amount of memory a program uses. The simplest one, which does not require any tools and works even with production systems, is the Verbose GC log.

Verbose GC Log

The Verbose GC log is defined when the JVM process is started. There are a couple of switches that can be used:

  1. -verbose:gc — prints basic information about GC to the standard output
  2. -XX:+PrintGCTimeStamps — prints the times that GC executes
  3. -XX:+PrintGCDetails — prints statistics about different regions of memory in the JVM
  4. -Xloggc:<file> — logs the results of GC in the given file

The following is an example of the output generated for Tomcat running in the default configuration with all of the previous switches enabled:

1.854: [GC 1.854: [DefNew: 570K->62K(576K), 0.0012355 secs] 2623K->2175K(3980K), 0.0012922 secs]


1.871: [GC 1.871: [DefNew: 574K->55K(576K), 0.0009810 secs] 2687K->2229K(3980K), 0.0010752 secs]


1.881: [GC 1.881: [DefNew: 567K->30K(576K), 0.0007417 secs] 2741K->2257K(3980K), 0.0007947 secs]


1.890: [GC 1.890: [DefNew: 542K->64K(576K), 0.0012155 secs] 2769K->2295K(3980K), 0.0012808 secs]

The most important set of numbers is located in the second column after the second -> (e.g., in the top line shown it is 2623K->2175K(3980K). These numbers indicate that as a result of GC, we are using around 2200K of memory at the end of each GC cycle.

This trace is not an indication of a memory leak—it shows a short-term trend with less then a second between samples, and that’s why we must observe long-term trends. However, if the Verbose GC log showed that the program was using around 2200K of memory after running for two days, and after running for 10 days it was using 2GB of memory (even after GC had just run), we could then conclude that there’s a memory leak.

All the information that needs to be collected in order to determine if a memory leak exists can be found in the results of the Verbose GC logs. The other memory monitoring tools we’ll cover in this article simply provide more information in a form that’s easier to interpret.

Monitoring the Java Process

The following approach works for any Java process, including standalone clients as well as application servers like JBoss and servlet containers like Tomcat. It is based on starting the Java process with JMX monitoring enabled and attaching with the JMX monitoring tools. We’ll use Tomcat in the following example.

To start Tomcat or the Java process with JMX monitoring enabled, use the following options when starting JVM:

  • -Dcom.sun.management.jmxremote — enables JMX monitoring
  • -Dcom.sun.management.jmxremote.port=<port> — controls the port for JMX monitoring

Note that if you’re on a production system, you’ll most likely want to secure your JVM before running it with these parameters. For that, you can specify these additional options:

  • com.sun.management.jmxremote.ssl
  • com.sun.management.jmxremote.authenticate

Once started, you can use JConsole or VisualVM to attach to the process. Note that later JDK 6 versions include VisualVM.

This is an example of the JConsole monitoring Tomcat. As shown in the example below, click on the Memory tab to get memory information:



memory_leaks_figure9

Again, we're interested in the long-term trends of heap memory usage, not trends from just a few minutes of running.

Finally, note that monitoring tools like Hyperic can typically show historical trends over longer periods of time than VisualVM or JConsole. In addition, tools like Hyperic allow for more fine-grained control over operations that are permitted by users. As a result, we recommend using monitoring tools for production systems use, while all the other tools discussed in this section are more appropriate for developers or "first aid" in the absence of the real monitoring tools.

Speed Kills


We now know how to find out whether or not a memory leak exists, and we can even determine the speed at which we're leaking memory (e.g., 100MB per day). But are we better off with a "slow" leak (e.g., 1MB per day) or a "fast" leak (e.g., 500MB/hour)?


It depends. With a "slow" leak, it takes longer for the system to run out of memory. For example, if we have 256MB of free memory remaining at the peak load time, it can take quite a long time to run out of memory with a 1MB/day memory leak. On the other hand, a faster memory leak makes it easier to reproduce and fix the problem.

One of the best ways to quickly determine whether there's a fast memory leak or you simply need more memory to run at the peak time is to allot more memory to the program and see what happens. If you increase the available heap in a Java program and the time between crashes increases, you are likely suffering from memory leak.

Let's assume that we're lucky enough to have a fast memory leak—on the order of 100MB per hour—on our hands, and that the initial memory picture looks like this:



In this example, we can expect to run out of memory after about five hours of running time.

How To Find Leaked Objects in a Fast Memory Leak


Somewhat surprisingly, it is much easier to debug large memory leaks than small memory leaks. The reason is that memory leaks present a sort of "needle in the haystack" type problem—you need to find the leaked objects amongst all the other objects in the program.


Suppose that the program we're debugging just ran out of memory. If the program initially had 512MB of free memory and now has none, it is obvious that the leaked objects used about 512MB of memory. The figure below illustrates this example:



In this situation, half of the objects in memory have been leaked! If we randomly select an object, there's a 50% chance that it has leaked. And as memory leaks usually consist of objects of a few classes, you can get a really good start on determining which objects are leaking memory by sorting memory usage based on aggregate memory use of all objects of the same class.

What if the ratio is less favorable (e.g., 512MB heap size, initially used memory 480MB, and 32MB of leaked objects at the end)? If you have an easy to reproduce memory leak, you can increase the heap size because an unbounded memory leak will eventually fill any amount of memory allotted to the program. So, you can increase the heap to 1GB, reproduce the memory leak, and get 544MB of leaked objects in a 1GB of heap.

If we had a way to look at the "complete picture" of memory, it would be fairly easy to pinpoint leaked objects. Fortunately, there's a way to do exactly this: heap dump the process.

Dump me Gently


A heap dump is a list of objects in the memory of JVM as well as the content of the memory occupied by those objects. It preserves the value of any attributes of the objects, including references to other objects. In other words, a heap dump gives you a complete picture of the memory.


There are multiple tools that allow you to dump heap in a Java process:

  • If you're using JDK 6, you can use tool called jmap on any platform.

  • If you're using JDK 5, the situation is slightly more complex:

    • If you're running UNIX (Linux, Solaris, OS X) with JDK 5 you can use jmap.

    • If you're using JDK 5 update 14 or later, you can use the -XX:+HeapDumpOnCtrlBreak option when starting JVM, then use the CTRL+BREAK key combination on Windows (or CTRL + \ on UNIX) to dump the heap.

    • If you're running Windows and using JDK 5 pre-update 14, you'll soon wish you weren't. Trying to reproduce the problem with a more recent JDK is probably the best bet here.



Some tools like VisualVM and memory profilers allow you to initiate a heap dump from the GUI, but you don’t need any fancy tools here—jmap will do just fine. As it provides the most general case, we'll use jmap in the next example.


Before you dump heap, be sure to keep the following issues in mind:

  • Programs in the JVM should be paused for the duration of the heap dump, which might take anywhere from ten seconds to several minutes. Many enterprise applications—and users of those applications—don’t take kindly to pauses that long, which may cause various timeouts to expire. So don’t try this at home or in production (unless the application is already a goner)!

  • Heap dumps are saved on disk, and the files might be fairly large. A good rule is to make sure that you have at least twice the size of the physical memory free on the disk before you initiate a memory dump.



With those final words of caution out of the way, you should now be ready to run the following command:

jmap -heap:live,format=b,file=FILENAME PID


Note that the -F option, which will dump non-responsive programs, might be useful on UNIX systems, but is not available on Windows. Note also that JDK 6 includes the option +XX:+HeapDumpOnOutOfMemoryError that will dump heap whenever the OutOfMemoryError alert is encountered. This can be a useful option, but keep in mind that it has the potential to consume significant amounts of disk space.


You now have a heap dump in the file FILENAME and are ready to analyze it.

What's In "Leaked" Memory?


With the heap dump complete, we can now take a look at the memory and find out what's really causing the memory leak.


Suppose that objects are holding references to each other as illustrated by the picture below. For the sake of easy calculation, let's assume that each object is 100 bytes, so that all of them together occupy 600 bytes of memory.

memory_leaks_figure2

Now, suppose that the program holds reference to object A for a prolonged period of time. As a result, objects B, C, D, E, and F are all ineligible for garbage collection, and we have the following amount of memory leaking:

  • 100 bytes for object A

  • 500 bytes for objects B, C, D, E and F that are retained due to the retention of object A



So, holding reference to object A causes a memory leak of 600 bytes. The shallow heap of object A is 100 bytes (object A itself), and the retained heap of object A is 600 bytes.


Although objects A through F are all leaked, the real cause of the memory leak is the program holding reference to object A. So how can we fix the root cause of this leak? If we first identify that object F is leaked, we can follow the reference chain back through objects D, C and A to find the cause of the memory leak. However, there are some complications to this "follow the reference chain" process:

  • Reference chains can be really long, so manually following them can be time consuming

  • An object is sometimes retained by more then one object, and there can even be circles involved as shown in the picture below:



memory_leaks_figure3

If we start following inbound references from object F in this example, we have to choose between following object C or object D. In addition, there's the possibility of getting caught in a circle by repeatedly following the path between objects D, E and B. On this small diagram it's easy to see that the root cause is holding object A, but when you're dealing with a situation that involves hundreds of thousands of objects (as any self-respecting memory leak does) you quickly realize that manually following the reference chain be very complex and time consuming.


This is where some shortcuts can come in handy:

  • If we had a tool that allowed us to play a "what would happen if I remove this reference" type of guessing game, we could run experiments that help locate the cause. For example, we could see that if we removed reference from "Cause of Leak" to A in the diagram above, objects A through F would all be freed. Some tools (like Quest's JProbe) have this capability.

  • If the memory leak is large and we have a tool that allows us to sort objects by retained heap, we'll get an even greater head start because the objects with the largest retained heap are usually the cause of large memory leaks.



Now that we understand what memory leaks are and how they can be corrected, let's find out how to fix them by analyzing heap dumps.

Tools for Dealing with Heap Dumps


Strictly speaking, you don’t need any tools that are not already part of the JDK. JDK ships with a tool called jhat, which you can use to inspect the heap dump. The process of fixing memory leaks is the same with all tools, and it's our opinion that no single tool is "light years" ahead of the others when it comes to fixing memory leaks.


Although jhat will get the job done, better tools often provide a few extra helpful features:

  • Present a better summary of heap statistics.

  • Sort objects by retained heap. In other words, some tools can tell you the memory usage of an object and all other objects that are referenced by it, as well as list the objects referenced by other objects. This makes it much faster to diagnose the cause of a memory leak.

  • Function on machines that have less memory then the size of the heap dump. For example, they'll allow you to analyze a 16GB heap dump from your server on a machine with only 1GB of physical memory.



There are several free tools that are useful for analyzing heap dumps in Java. One that's widely used and is even included with the later versions of the JVM 6 is VisualVM. VisualVM is nice tool that gives you just enough to resolve memory leaks, and it shows heap dumps and relations between objects in graphical form.


Feature-wise, one step above VisualVM is the Eclipse Memory Analyzer Tool (MAT), a free tool that includes a lot of additional options. Although it's still in incubation phase as of publication of this article, MAT is free and we've found it to be extremely useful.

Commercial products like JProfiler, YourKit, and JProbe are also excellent tools for debugging memory leaks. These applications include a few options that go above and beyond VisualVM and MAT, but they're certainly not necessary to successfully debug memory leaks. Unless you already have a license for one of these commercial tools, we recommend trying MAT first.

Analyzing the Heap Dump for Fast Memory Leaks
It's usually easy to find a fast memory leak. A lot of memory is leaked, and you simply need to find the big hog that leaked all that memory.

Analyzing the heap dump is done in order to:

  • Find objects that are "leaking"

  • Find the root cause of the memory leak



If you have a fast memory leak and are able to reproduce it in such a way as to make the leaked objects a significant portion of the final memory picture, then determining which objects are leaked is simple because they occupy a significant portion of the memory. To determine which objects are leaked, sort the classes by total memory usage of all instances of each class. The objects near the top of the list are usually the leaked objects. Then, you can follow the reference chain to them until you find the cause of the memory leak.


A useful heuristic here is that if you sort all the objects by retained heap, you can find the objects that are likely the root cause of the memory leak. Again, an example:

memory_leaks_figure4

If we assume that each instance of object C is 100 bytes, then holding object A resulted in a memory leak of almost 1GB! In other words, the retained heap of object A in this example is about 1GB. So, if we were investigating this memory leak, it would be fairly obvious that we should start there. Remember, the Eclipse Memory Analyzer Tool (MAT) allows you to sort the heap dump by the retained heap usage of the objects, so it would have been easy to use MAT to determine the retained heap of object A.

Slow and Small Memory Leaks


It always helps if you can easily reproduce the problem, but what if the memory leak is really small and slow? What should you do if after a few days you can reproduce only a 10MB leak in a heap that contains 2GB of objects? In situations like this, looking for the cause of the problem can feel like finding a needle in the haystack.


One solution in this case is the brute force approach—simply devote enough time looking at the objects to find the problem. While that will ultimately work, there's another way to address the problem if you have an idea of which operations are leaking memory. If you know or suspect which operations leak memory, you can find even relatively slow memory leaks.

The secret here is to compare heap dumps, as this highlights objects that are present in one heap dump but not in another. For example, suppose that objects A, B, and C are present in the first heap dump, while objects A, B, C, D, and E are present in the second heap dump. In this case, objects D and E are the difference.

Comparing heap dumps makes it easier to find memory leaks because it effectively reduces the size of the "haystack" containing your needle. Comparing heap dumps can easily reduce the size of the objects for which you need to examine heaps from 2GBs to a few KB.

Many commercial profilers as well as Eclipse MAT can be used to compare heaps. Once you've selected a tool that can compare heaps, simply follow the process explained in the following diagram:

memory_leaks_figure5

In effect, what you’re doing is creating two snapshots, one before and one after executing the use case that you know is leaking memory. That way, you significantly reduce the number of objects that you need to investigate to find leaked objects. After you find the objects that leaked, you can find the cause of the memory leak as described in the previous section.

Why perform garbage collection before taking a snapshot? Because some tools won’t automatically perform GC for you, and consequently the snapshots might include objects that are no longer reachable. Many modern tools will perform GC before taking the snapshot, but if you are unsure whether or not your tool performs full GC before taking a snapshot it is recommended that you do so yourself. That way you don’t have to worry about objects that are eligible for GC but have not yet been collected.

Among the objects that are created during the use case, some are supposed to be there because they are the result of the use case execution (e.g., we created a new customer, and that customer object should be retained), while other objects are the memory leak. However, since the only objects present in the difference between the snapshots are the ones that were created during the use case, the size of the haystack you need to look through is significantly reduced.

Practical Problems

Why do memory leaks sometimes take a long time to fix if the process is this simple? In our opinion, the main reasons are:

  • There isn’t a sufficient amount of easily available information about fixing Java memory leaks. We hope this article will help improve the situation.
  • It can be difficult to reliably reproduce an issue, and a lot of time is typically required to reproduce an issue before you can really start addressing it.
  • People lack the right tools for the job—in particular, memory profilers. With many free profilers available and reasonably low prices for commercial profilers, there’s no reason you shouldn’t have good tools in your toolbox.
  • There are a few practical issues with the use of tools and techniques that might be perceived as road blocks the first time someone tries them. We’ll address some of these issues below.

Fortunately, the practical issues most commonly encountered aren’t very difficult to solve. The most common problems are:

  • You can’t load the snapshot because you don’t have enough memory in your development box. For example, this can easily happen if you have 64-bit servers with 8GB of memory allocated to the JVM. You might not have physical memory in your development box, or you might be using the 32-bit JVM on your development box, but the tool you’re using insists on loading most of the snapshot into memory. If you like the idea of having a really powerful development box, use this situation to your advantage and ask your boss for a new machine. Alternatively, try using a tool with less of an appetite for memory, like Eclipse MAT.
  • You can’t increase your memory on the server to get a bigger set of leaked objects, yet you need a bigger set of leaked objects to speed up the process of finding the memory leak. This often happens with 32-bit JVMs. One option in this situation is to apply the techniques described above for finding slow memory leaks, although this approach requires a significant amount of time. Alternatively, you can try to reproduce problem on a 64-bit JVM, which will allow you to increase the memory. If the problem causing the memory leak is in your application, changing the JVM is extremely unlikely to “hide” the leak.
  • The memory leak is not in objects of just one class—you’re leaking objects from thousands of classes, so it’s difficult to find leaked objects. Or, your graph of object relations is so complex that you can find the initially-leaked objects but can’t locate the cause. In either case, you’ve got quite a pickle on your hands. The only words of encouragement we can offer are that the situation will eventually improve, as the longer you track object references the better you’ll get at it. One other piece of advice: you should probably call your significant other and let him or her know that you won’t be home for dinner.
  • You’re getting an OutOfMemoryError alert with a new, different format. For example, you might be looking at something like this:
    Exception in thread “pool-2-thread-1″ java.lang.OutOfMemoryError: GC overhead limit exceeded.
    This message can happen with some GC settings that limit the overhead of the GC. It often indicates a memory leak, although there are some corner cases in which problems with GC performance can cause it. The best way to distinguish the cause of this message (is it a GC misconfiguration or a memory leak?) is to examine the pattern of errors. If they happen with regularity (in other words, they’re a function of time the program is running), then it’s a memory leak. If they happen only occasionally under heavy loads and clear later (in other words, they’re not a function of running time), they might indicate a need to tweak the GC or increase available memory.
  • 3rd party caching systems can be setup so that caches are allowed to expand and fill up the available memory the program doesn’t need. However, if the program no longer needs more memory, the cache automatically releases it. The point here is that in the absence of any OutOfMemoryError alerts, continually increasing memory usage does not necessary indicate a memory leak. Both “used memory grows as function of time” and “free memory eventually runs out” conditions are necessary in order to determine the presence of a memory leak.

Finishing Up

This article described the methodology and techniques necessary to fix memory leaks. These techniques are universal—they apply to any profiler, and some of them even apply to languages other than Java that use garbage collection. The information presented above should give you everything you need in order to use the tools of your choice to investigate and fix memory leaks.

The proper usage of specific tools will be discussed in future articles, which will focus primarily on free tools and problems in Java. However, if there’s enough interest we might tackle other languages as well. If you’re interested in follow-up articles related to memory leak resolution in a different environment or language, please leave a comment or contact us at docs-at-openlogic.com.

Additional Resources

The following links provide additional information on the behavior of GC in Java as well as different tools that you may find useful.

The above article originally appeared on Wazi, a clearinghouse for the timeliest thinking on open source software."

我做了一个宠物智能投喂系统,现在我有一个stm32f103c6单片机外接8MHz的晶振,其PA1连作为RXD,PA2作为TXD,PB0、PB1连接了发光二极管的负极,发光二极管的正极接的电源,发光二极管用来模拟电机转动状态,在收到喂食的指令后PB0先亮两秒,PB1先不亮,然后在PB1亮两秒PB0熄灭,最后一起闪烁两秒表示喂食已完成,我创建了一个mfc程序(PCMFCDLG),添加了5个Combo Box控件分别用来选择端口号(控件类,m_cbPort)、波特率(控件类,m_cbBaud)、校验位(控件类,m_cbParity)、数据位(控件类,m_cbDate)以及停止位(控件类,m_cbStop),4个按钮控件分别用来打开串口连接stm32单片机、关闭串口、立即投喂、定时投喂,3个Edit control控件(value类,m_strTime1/2/3)用来定时3个时间点实现定时投喂,最后还有一个List Box(控件类,m_listStatus)用来显示执行状态,在PC机与单片机没有建立连接的时候除了连接按钮,其他按钮都不能点击,在定时按钮没有按下的时候edit control控件不能输入,在单片机执行完相应操作后,会返还给PC机信息,然后显示在list box里面如mfc界面点击立即投喂,单片机执行完操作后返回给pc端mfc界面一个标志,告诉pc端操作已完成,然后在mfc界面的list里面显示对应操作已完成。 /* Module : SerialPort.h Purpose: Interface for an C++ wrapper class for serial ports Copyright (c) 1999 - 2015 by PJ Naughter. All rights reserved. Copyright / Usage Details: You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) when your product is released in binary form. You are allowed to modify the source code in any way you want except you cannot modify the copyright details at the top of each module. If you want to distribute source code with your application, then you are only allowed to distribute versions released by the author. This is to maintain a single distribution point for the source code. */ // Macros / Structs etc #pragma once #ifndef __SERIALPORT_H__ #define __SERIALPORT_H__ #ifndef CSERIALPORT_EXT_CLASS #define CSERIALPORT_EXT_CLASS #endif //#ifndef CSERIALPORT_EXT_CLASS #ifndef _Out_writes_bytes_ #define _Out_writes_bytes_(X) #endif //#ifndef _Out_writes_bytes_ #ifndef __out_data_source #define __out_data_source(X) #endif //#ifndef __out_data_source #ifndef _Out_writes_bytes_to_opt_ #define _Out_writes_bytes_to_opt_(X,Y) #endif //#ifndef _Out_writes_bytes_to_opt_ #ifndef _Out_writes_bytes_opt_ #define _Out_writes_bytes_opt_(X) #endif //#ifndef _Out_writes_bytes_opt_ #ifndef _In_reads_bytes_opt_ #define _In_reads_bytes_opt_(X) #endif //#ifndef _In_reads_bytes_opt_ #ifndef _In_ #define _In_ #endif //#ifndef _In_ #ifndef _In_z_ #define _In_z_ #endif //#ifndef _In_z_ #ifndef _Inout_opt_ #define _Inout_opt_ #endif //#ifndef _Inout_opt_ #ifndef _Out_opt_ #define _Out_opt_ #endif //#ifndef _Out_opt_ #ifndef _Out_ #define _Out_ #endif //#ifndef _Out_ #ifndef _Inout_ #define _Inout_ #endif //#ifndef _Inout_ #ifndef _In_opt_ #define _In_opt_ #endif //#ifndef _In_opt_ // Includes /// #include <sal.h> #ifndef CSERIALPORT_MFC_EXTENSTIONS #include <exception> #include <string> #endif //#ifndef CSERIALPORT_MFC_EXTENSTIONS /// Classes /// #ifdef CSERIALPORT_MFC_EXTENSIONS class CSERIALPORT_EXT_CLASS CSerialException : public CException #else class CSERIALPORT_EXT_CLASS CSerialException : public std::exception #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS { public: //Constructors / Destructors CSerialException(DWORD dwError); //Methods #ifdef CSERIALPORT_MFC_EXTENSIONS #ifdef _DEBUG virtual void Dump(CDumpContext& dc) const; #endif //#ifdef _DEBUG #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS #if _MSC_VER >= 1700 virtual BOOL GetErrorMessage(_Out_z_cap_(nMaxError) LPTSTR lpszError, _In_ UINT nMaxError, _Out_opt_ PUINT pnHelpContext = NULL); #else virtual BOOL GetErrorMessage(__out_ecount_z(nMaxError) LPTSTR lpszError, __in UINT nMaxError, __out_opt PUINT pnHelpContext = NULL); #endif #ifdef CSERIALPORT_MFC_EXTENSIONS CString GetErrorMessage(); #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS //Data members DWORD m_dwError; }; class CSERIALPORT_EXT_CLASS CSerialPort { public: //Enums enum FlowControl { NoFlowControl, CtsRtsFlowControl, CtsDtrFlowControl, DsrRtsFlowControl, DsrDtrFlowControl, XonXoffFlowControl }; enum Parity { NoParity = 0, OddParity = 1, EvenParity = 2, MarkParity = 3, SpaceParity = 4 }; enum StopBits { OneStopBit, OnePointFiveStopBits, TwoStopBits }; //Constructors / Destructors CSerialPort(); virtual ~CSerialPort(); //General Methods void Open(_In_ int nPort, _In_ DWORD dwBaud = 9600, _In_ Parity parity = NoParity, _In_ BYTE DataBits = 8, _In_ StopBits stopBits = OneStopBit, _In_ FlowControl fc = NoFlowControl, _In_ BOOL bOverlapped = FALSE); void Open(_In_z_ LPCTSTR pszPort, _In_ DWORD dwBaud = 9600, _In_ Parity parity = NoParity, _In_ BYTE DataBits = 8, _In_ StopBits stopBits = OneStopBit, _In_ FlowControl fc = NoFlowControl, _In_ BOOL bOverlapped = FALSE); void Close(); void Attach(_In_ HANDLE hComm); HANDLE Detach(); operator HANDLE() const { return m_hComm; }; BOOL IsOpen() const { return m_hComm != INVALID_HANDLE_VALUE; }; #ifdef CSERIALPORT_MFC_EXTENSIONS #ifdef _DEBUG void Dump(_In_ CDumpContext& dc) const; #endif //#ifdef _DEBUG #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS //Reading / Writing Methods void Read(_Out_writes_bytes_(dwNumberOfBytesToRead) __out_data_source(FILE) void* lpBuffer, _In_ DWORD dwNumberOfBytesToRead); void Read(_Out_writes_bytes_to_opt_(dwNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) void* lpBuffer, _In_ DWORD dwNumberOfBytesToRead, _In_ OVERLAPPED& overlapped, _Inout_opt_ DWORD* lpNumberOfBytesRead = NULL); void ReadEx(_Out_writes_bytes_opt_(dwNumberOfBytesToRead) __out_data_source(FILE) LPVOID lpBuffer, _In_ DWORD dwNumberOfBytesToRead, _Inout_ LPOVERLAPPED lpOverlapped, _In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); DWORD Write(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) const void* lpBuffer, _In_ DWORD dwNumberOfBytesToWrite); void Write(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) const void* lpBuffer, _In_ DWORD dwNumberOfBytesToWrite, _In_ OVERLAPPED& overlapped, _Out_opt_ DWORD* lpNumberOfBytesWritten = NULL); void WriteEx(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) LPCVOID lpBuffer, _In_ DWORD dwNumberOfBytesToWrite, _Inout_ LPOVERLAPPED lpOverlapped, _In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); void TransmitChar(_In_ char cChar); DWORD GetOverlappedResult(_In_ OVERLAPPED& overlapped, _Out_ DWORD& dwBytesTransferred, _In_ BOOL bWait); void CancelIo(); DWORD BytesWaiting(); DWORD BytesPending(); //Configuration Methods void GetConfig(_In_ COMMCONFIG& config); static void GetDefaultConfig(_In_ int nPort, _Out_ COMMCONFIG& config); static void GetDefaultConfig(_In_z_ LPCTSTR pszPort, _Out_ COMMCONFIG& config); void SetConfig(_In_ COMMCONFIG& Config); static void SetDefaultConfig(_In_ int nPort, _In_ COMMCONFIG& config); static void SetDefaultConfig(_In_z_ LPCTSTR pszPort, _In_ COMMCONFIG& config); //Misc RS232 Methods void ClearBreak(); void SetBreak(); void ClearError(_Out_ DWORD& dwErrors); void GetStatus(_Out_ COMSTAT& stat); void GetState(_Out_ DCB& dcb); void SetState(_In_ DCB& dcb); void Escape(_In_ DWORD dwFunc); void ClearDTR(); void ClearRTS(); void SetDTR(); void SetRTS(); void SetXOFF(); void SetXON(); void GetProperties(_Inout_ COMMPROP& properties); void GetModemStatus(_Out_ DWORD& dwModemStatus); //Timeouts void SetTimeouts(_In_ COMMTIMEOUTS& timeouts); void GetTimeouts(_Out_ COMMTIMEOUTS& timeouts); void Set0Timeout(); void Set0WriteTimeout(); void Set0ReadTimeout(); //Event Methods void SetMask(_In_ DWORD dwMask); void GetMask(_Out_ DWORD& dwMask); void WaitEvent(_Inout_ DWORD& dwMask); BOOL WaitEvent(_Inout_ DWORD& dwMask, _Inout_ OVERLAPPED& overlapped); //Queue Methods void Flush(); void Purge(_In_ DWORD dwFlags); void TerminateOutstandingWrites(); void TerminateOutstandingReads(); void ClearWriteBuffer(); void ClearReadBuffer(); void Setup(_In_ DWORD dwInQueue, _In_ DWORD dwOutQueue); //Static methods static void ThrowSerialException(_In_ DWORD dwError = 0); protected: //Member variables HANDLE m_hComm; //Handle to the comms port }; #endif //#ifndef __SERIALPORT_H__ /* Module : SerialPort.cpp Purpose: Implementation for an C++ wrapper class for serial ports Created: PJN / 31-05-1999 History: PJN / 03-06-1999 1. Fixed problem with code using CancelIo which does not exist on 95. 2. Fixed leaks which can occur in sample app when an exception is thrown PJN / 16-06-1999 1. Fixed a bug whereby CString::ReleaseBuffer was not being called in CSerialException::GetErrorMessage PJN / 29-09-1999 1. Fixed a simple copy and paste bug in CSerialPort::SetDTR PJN / 08-05-2000 1. Fixed an unreferrenced variable in CSerialPort::GetOverlappedResult in VC 6 PJN / 10-12-2000 1. Made class destructor virtual PJN / 15-01-2001 1. Attach method now also allows you to specify whether the serial port is being attached to in overlapped mode 2. Removed some ASSERTs which were unnecessary in some of the functions 3. Updated the Read method which uses OVERLAPPED IO to also return the bytes read. This allows calls to WriteFile with a zeroed overlapped structure (This is required when dealing with TAPI and serial communications) 4. Now includes copyright message in the source code and documentation. PJN / 24-03-2001 1. Added a BytesWaiting method PJN / 04-04-2001 1. Provided an overriden version of BytesWaiting which specifies a timeout PJN / 23-04-2001 1. Fixed a memory leak in DataWaiting method PJN / 01-05-2002 1. Fixed a problem in Open method which was failing to initialize the DCB structure incorrectly, when calling GetState. Thanks to Ben Newson for this fix. PJN / 29-05-2002 1. Fixed an problem where the GetProcAddress for CancelIO was using the wrong calling convention PJN / 07-08-2002 1. Changed the declaration of CSerialPort::WaitEvent to be consistent with the rest of the methods in CSerialPort which can operate in "OVERLAPPED" mode. A note about the usage of this: If the method succeeds then the overlapped operation has completed synchronously and there is no need to do a WaitForSingle/MultipleObjects. If any other unexpected error occurs then a CSerialException will be thrown. See the implementation of the CSerialPort::DataWaiting which has been rewritten to use this new design pattern. Thanks to Serhiy Pavlov for spotting this inconsistency. PJN / 20-09-2002 1. Addition of an additional ASSERT in the internal _OnCompletion function. 2. Addition of an optional out parameter to the Write method which operates in overlapped mode. Thanks to Kevin Pinkerton for this addition. PJN / 10-04-2006 1. Updated copyright details. 2. Addition of a CSERIALPORT_EXT_CLASS and CSERIALPORT_EXT_API macros which makes the class easier to use in an extension dll. 3. Removed derivation of CSerialPort from CObject as it was not really needed. 4. Fixed a number of level 4 warnings in the sample app. 5. Reworked the overlapped IO methods to expose the LPOVERLAPPED structure to client code. 6. Updated the documentation to use the same style as the web site. 7. Did a spell check of the HTML documentation. 8. Updated the documentation on possible blocking in Read/Ex function. Thanks to D Kerrison for reporting this issue. 9. Fixed a minor issue in the sample app when the code is compiled using /Wp64 PJN / 02-06-2006 1. Removed the bOverlapped as a member variable from the class. There was no real need for this setting, since the SDK functions will perform their own checking of how overlapped operations should 2. Fixed a bug in GetOverlappedResult where the code incorrectly checking against the error ERROR_IO_PENDING instead of ERROR_IO_INCOMPLETE. Thanks to Sasho Darmonski for reporting this bug. 3. Reviewed all TRACE statements for correctness. PJN / 05-06-2006 1. Fixed an issue with the creation of the internal event object. It was incorrectly being created as an auto-reset event object instead of a manual reset event object. Thanks to Sasho Darmonski for reporting this issue. PJN / 24-06-2006 1. Fixed some typos in the history list. Thanks to Simon Wong for reporting this. 2. Made the class which handles the construction of function pointers at runtime a member variable of CSerialPort 3. Made AfxThrowSerialPortException part of the CSerialPort class. Thanks to Simon Wong for reporting this. 4. Removed the unnecessary CSerialException destructor. Thanks to Simon Wong for reporting this. 5. Fixed a minor error in the TRACE text in CSerialPort::SetDefaultConfig. Again thanks to Simon Wong for reporting this. 6. Code now uses new C++ style casts rather than old style C casts where necessary. Again thanks to Simon Wong for reporting this. 7. CSerialException::GetErrorMessage now uses the strsafe functions. This does mean that the code now requires the Platform SDK if compiled using VC 6. PJN / 25-06-2006 1. Combined the functionality of the CSerialPortData class into the main CSerialPort class. 2. Renamed AfxThrowSerialPortException to ThrowSerialPortException and made the method public. PJN / 05-11-2006 1. Minor update to stdafx.h of sample app to avoid compiler warnings in VC 2005. 2. Reverted the use of the strsafe.h header file. Instead now the code uses the VC 2005 Safe CRT and if this is not available, then we fail back to the standard CRT. PJN / 25-01-2007 1. Minor update to remove strsafe.h from stdafx.h of the sample app. 2. Updated copyright details. PJN / 24-12-2007 1. CSerialException::GetErrorMessage now uses the FORMAT_MESSAGE_IGNORE_INSERTS flag. For more information please see Raymond Chen's blog at http://blogs.msdn.com/oldnewthing/archive/2007/11/28/6564257.aspx. Thanks to Alexey Kuznetsov for reporting this issue. 2. Simplified the code in CSerialException::GetErrorMessage somewhat. 3. Optimized the CSerialException constructor code. 4. Code now uses newer C++ style casts instead of C style casts. 5. Reviewed and updated all the TRACE logging in the module 6. Replaced all calls to ZeroMemory with memset PJN / 30-12-2007 1. Updated the sample app to clean compile on VC 2008 2. CSerialException::GetErrorMessage now uses Checked::tcsncpy_s if compiled using VC 2005 or later. PJN / 18-05-2008 1. Updated copyright details. 2. Changed the actual values for Parity enum so that they are consistent with the Parity define values in the Windows SDK header file WinBase.h. This avoids the potential issue where you use the CSerialPort enum parity values in a call to the raw Win32 API calls. Thanks to Robert Krueger for reporting this issue. PJN / 21-06-2008 1. Code now compiles cleanly using Code Analysis (/analyze) 2. Updated code to compile correctly using _ATL_CSTRING_EXPLICIT_CONSTRUCTORS define 3. The code now only supports VC 2005 or later. 4. CSerialPort::Read, Write, GetOverlappedResult & WaitEvent now throw an exception irrespective of whether the last error is ERROR_IO_PENDING or not 5. Replaced all calls to ZeroMemory with memset PJN / 04-07-2008 1. Provided a version of the Open method which takes a string instead of a numeric port number value. This allows the code to support some virtual serial port packages which do not use device names of the form "COM%d". Thanks to David Balazic for suggesting this addition. PJN / 25-01-2012 1. Updated copyright details. 2. Updated sample app and class to compile cleanly on VC 2010 and later. PJN / 28-02-2015 1. Updated sample project settings to more modern default values. 2. Updated copyright details. 3. Reworked the CSerialPort and CSerialPortException classes to optionally compile without MFC. By default the classes now use STL classes and idioms but if you define CSERIALPORT_MFC_EXTENSTIONS the classes will revert back to the MFC behaviour. 4. Remove logic to use GetProcAddress to access CancelIO functionality. 5. Updated the code to clean compile on VC 2013 6. Added SAL annotations to all the code 7. Addition of a GetDefaultConfig method which takes a string 8. Addition of a SetDefaultConfig method which takes a string PJN / 26-04-2015 1. Removed unnecessary inclusion of WinError.h 2. Removed the CSerialPort::DataWaiting method as it depends on the port being open in overlapped mode. Instead client code can simply call CSerialPort::WaitEvent directly themselves. Removing this method also means that the CSerialPort::m_hEvent handle has not also been removed. 3. The CSerialPort::WriteEx method has been reworked to expose all the parameters of the underlying WriteFileEx API. This rework also fixes a memory leak in WriteEx which can sometimes occur. This reworks also means that the CSerialPort::_OnCompletion and CSerialPort::_OnCompletion methods have been removed. Thanks to Yufeng Huang for reporting this issue. 4. The CSerialPort::ReadEx method has been reworked to expose all the parameters of the underlying ReadFileEx API. This rework also fixes a memory leak in ReadEx which can sometimes occur. This reworks also means that the CSerialPort::_OnCompletion and CSerialPort::_OnCompletion methods have been removed. Thanks to Yufeng Huang for reporting this issue. Copyright (c) 1996 - 2015 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com) All rights reserved. Copyright / Usage Details: You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) when your product is released in binary form. You are allowed to modify the source code in any way you want except you cannot modify the copyright details at the top of each module. If you want to distribute source code with your application, then you are only allowed to distribute versions released by the author. This is to maintain a single distribution point for the source code. */ // Includes #include "pch.h" #include "SerialPort.h" #ifndef __ATLBASE_H__ #pragma message("To avoid this message, please put atlbase.h in your pre compiled header (normally stdafx.h)") #include <atlbase.h> #endif //#ifndef __ATLBASE_H__ // Defines / #ifdef CSERIALPORT_MFC_EXTENSIONS #ifdef _DEBUG #define new DEBUG_NEW #endif //#ifdef _DEBUG #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS //Implementation /// #if _MSC_VER >= 1700 BOOL CSerialException::GetErrorMessage(_Out_z_cap_(nMaxError) LPTSTR lpszError, _In_ UINT nMaxError, _Out_opt_ PUINT pnHelpContext) #else BOOL CSerialException::GetErrorMessage(__out_ecount_z(nMaxError) LPTSTR lpszError, __in UINT nMaxError, __out_opt PUINT pnHelpContext) #endif { //Validate our parameters ATLASSERT(lpszError != NULL); if (pnHelpContext != NULL) *pnHelpContext = 0; //What will be the return value from this function (assume the worst) BOOL bSuccess = FALSE; LPTSTR lpBuffer; DWORD dwReturn = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, m_dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT), reinterpret_cast<LPTSTR>(&lpBuffer), 0, NULL); if (dwReturn == 0) *lpszError = _T('\0'); else { bSuccess = TRUE; Checked::tcsncpy_s(lpszError, nMaxError, lpBuffer, _TRUNCATE); LocalFree(lpBuffer); } return bSuccess; } #ifdef CSERIALPORT_MFC_EXTENSIONS CString CSerialException::GetErrorMessage() { CString rVal; LPTSTR pstrError = rVal.GetBuffer(4096); GetErrorMessage(pstrError, 4096, NULL); rVal.ReleaseBuffer(); return rVal; } #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS CSerialException::CSerialException(DWORD dwError) : m_dwError(dwError) { } #ifdef CSERIALPORT_MFC_EXTENSIONS #ifdef _DEBUG void CSerialException::Dump(_In_ CDumpContext& dc) const { CObject::Dump(dc); dc << _T("m_dwError = ") << m_dwError << _T("\n"); } #endif //#ifdef _DEBUG #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS CSerialPort::CSerialPort() : m_hComm(INVALID_HANDLE_VALUE) { } CSerialPort::~CSerialPort() { Close(); } void CSerialPort::ThrowSerialException(_In_ DWORD dwError) { if (dwError == 0) dwError = ::GetLastError(); ATLTRACE(_T("Warning: throwing CSerialException for error %d\n"), dwError); #ifdef CSERIALPORT_MFC_EXTENSIONS CSerialException* pException = new CSerialException(dwError); THROW(pException); #else CSerialException e(dwError); throw e; #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS } #ifdef CSERIALPORT_MFC_EXTENSIONS #ifdef _DEBUG void CSerialPort::Dump(CDumpContext& dc) const { dc << _T("m_hComm = ") << m_hComm << _T("\n"); } #endif //#ifdef _DEBUG #endif //#ifdef CSERIALPORT_MFC_EXTENSIONS void CSerialPort::Open(_In_z_ LPCTSTR pszPort, _In_ DWORD dwBaud, _In_ Parity parity, _In_ BYTE DataBits, _In_ StopBits stopBits, _In_ FlowControl fc, _In_ BOOL bOverlapped) { Close(); //In case we are already open //Call CreateFile to open the comms port m_hComm = CreateFile(pszPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, bOverlapped ? (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED) : 0, NULL); if (m_hComm == INVALID_HANDLE_VALUE) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::Open, Failed to open the comms port, Error:%u\n"), dwLastError); //ThrowSerialException(dwLastError); return; } //Get the current state prior to changing it DCB dcb; dcb.DCBlength = sizeof(DCB); GetState(dcb); //Setup the baud rate dcb.BaudRate = dwBaud; //Setup the Parity switch (parity) { case EvenParity: { dcb.Parity = EVENPARITY; break; } case MarkParity: { dcb.Parity = MARKPARITY; break; } case NoParity: { dcb.Parity = NOPARITY; break; } case OddParity: { dcb.Parity = ODDPARITY; break; } case SpaceParity: { dcb.Parity = SPACEPARITY; break; } default: { ATLASSERT(FALSE); break; } } //Setup the data bits dcb.ByteSize = DataBits; //Setup the stop bits switch (stopBits) { case OneStopBit: { dcb.StopBits = ONESTOPBIT; break; } case OnePointFiveStopBits: { dcb.StopBits = ONE5STOPBITS; break; } case TwoStopBits: { dcb.StopBits = TWOSTOPBITS; break; } default: { ATLASSERT(FALSE); break; } } //Setup the flow control dcb.fDsrSensitivity = FALSE; switch (fc) { case NoFlowControl: { dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = FALSE; dcb.fOutX = FALSE; dcb.fInX = FALSE; break; } case CtsRtsFlowControl: { dcb.fOutxCtsFlow = TRUE; dcb.fOutxDsrFlow = FALSE; dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; dcb.fOutX = FALSE; dcb.fInX = FALSE; break; } case CtsDtrFlowControl: { dcb.fOutxCtsFlow = TRUE; dcb.fOutxDsrFlow = FALSE; dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; dcb.fOutX = FALSE; dcb.fInX = FALSE; break; } case DsrRtsFlowControl: { dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = TRUE; dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; dcb.fOutX = FALSE; dcb.fInX = FALSE; break; } case DsrDtrFlowControl: { dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = TRUE; dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; dcb.fOutX = FALSE; dcb.fInX = FALSE; break; } case XonXoffFlowControl: { dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = FALSE; dcb.fOutX = TRUE; dcb.fInX = TRUE; dcb.XonChar = 0x11; dcb.XoffChar = 0x13; dcb.XoffLim = 100; dcb.XonLim = 100; break; } default: { ATLASSERT(FALSE); break; } } //Now that we have all the settings in place, make the changes SetState(dcb); } void CSerialPort::Open(_In_ int nPort, _In_ DWORD dwBaud, _In_ Parity parity, _In_ BYTE DataBits, _In_ StopBits stopBits, _In_ FlowControl fc, _In_ BOOL bOverlapped) { //Form the string version of the port number TCHAR szPort[12]; _stprintf_s(szPort, sizeof(szPort) / sizeof(TCHAR), _T("\\\\.\\COM%d"), nPort); //Delegate the work to the other version of Open Open(szPort, dwBaud, parity, DataBits, stopBits, fc, bOverlapped); } void CSerialPort::Close() { if (IsOpen()) { //Close down the comms port CloseHandle(m_hComm); m_hComm = INVALID_HANDLE_VALUE; } } void CSerialPort::Attach(_In_ HANDLE hComm) { Close(); //Validate our parameters, now that the port has been closed ATLASSERT(m_hComm == INVALID_HANDLE_VALUE); m_hComm = hComm; } HANDLE CSerialPort::Detach() { //What will be the return value from this function HANDLE hComm = m_hComm; m_hComm = INVALID_HANDLE_VALUE; return hComm; } void CSerialPort::Read(_Out_writes_bytes_(dwNumberOfBytesToRead) __out_data_source(FILE) void* lpBuffer, _In_ DWORD dwNumberOfBytesToRead) { //Validate our parameters ATLASSERT(IsOpen()); DWORD dwBytesRead = 0; if (!ReadFile(m_hComm, lpBuffer, dwNumberOfBytesToRead, &dwBytesRead, NULL)) { DWORD dwLastError = GetLastError(); if (ERROR_IO_PENDING != dwLastError) ATLTRACE(_T("CSerialPort::Read, Failed in call to ReadFile, Error:%u\n"), dwLastError); // ThrowSerialException(dwLastError); } } void CSerialPort::Read(_Out_writes_bytes_to_opt_(dwNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) void* lpBuffer, _In_ DWORD dwNumberOfBytesToRead, _In_ OVERLAPPED& overlapped, _Inout_opt_ DWORD* lpNumberOfBytesRead) { //Validate our parameters ATLASSERT(IsOpen()); *lpNumberOfBytesRead = 0; if (overlapped.hEvent) ResetEvent(overlapped.hEvent); if (!ReadFile(m_hComm, lpBuffer, dwNumberOfBytesToRead, lpNumberOfBytesRead, &overlapped)) { DWORD dwLastError = GetLastError(); if (ERROR_IO_PENDING != dwLastError) { ATLTRACE(_T("CSerialPort::Read, Failed in call to ReadFile, Error:%u\n"), dwLastError); } // ThrowSerialException(dwLastError); } } void CSerialPort::ReadEx(_Out_writes_bytes_opt_(dwNumberOfBytesToRead) __out_data_source(FILE) LPVOID lpBuffer, _In_ DWORD dwNumberOfBytesToRead, _Inout_ LPOVERLAPPED lpOverlapped, _In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { //Validate our parameters ATLASSERT(IsOpen()); if (!ReadFileEx(m_hComm, lpBuffer, dwNumberOfBytesToRead, lpOverlapped, lpCompletionRoutine)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::ReadEx, Failed in call to ReadFileEx, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } DWORD CSerialPort::Write(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) const void* lpBuffer, _In_ DWORD dwNumberOfBytesToWrite) { //Validate our parameters ATLASSERT(IsOpen()); DWORD dwBytesWritten = 0; if (!WriteFile(m_hComm, lpBuffer, dwNumberOfBytesToWrite, &dwBytesWritten, NULL)) { DWORD dwLastError = GetLastError(); if (ERROR_IO_PENDING != dwLastError) ATLTRACE(_T("CSerialPort::Write, Failed in call to WriteFile, Error:%u\n"), dwLastError); //ThrowSerialException(dwLastError); } return dwBytesWritten; } void CSerialPort::Write(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) const void* lpBuffer, _In_ DWORD dwNumberOfBytesToWrite, _In_ OVERLAPPED& overlapped, _Out_opt_ DWORD* lpNumberOfBytesWritten) { //Validate our parameters ATLASSERT(IsOpen()); if (!WriteFile(m_hComm, lpBuffer, dwNumberOfBytesToWrite, lpNumberOfBytesWritten, &overlapped)) { DWORD dwLastError = GetLastError(); if (ERROR_IO_PENDING != dwLastError) { ATLTRACE(_T("CSerialPort::Write, Failed in call to WriteFile, Error:%u\n"), dwLastError); } // ThrowSerialException(dwLastError); } } void CSerialPort::WriteEx(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) LPCVOID lpBuffer, _In_ DWORD dwNumberOfBytesToWrite, _Inout_ LPOVERLAPPED lpOverlapped, _In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { //Validate our parameters ATLASSERT(IsOpen()); if (!WriteFileEx(m_hComm, lpBuffer, dwNumberOfBytesToWrite, lpOverlapped, lpCompletionRoutine)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::WriteEx, Failed in call to WriteFileEx, Error:%u\n"), dwLastError); //ThrowSerialException(dwLastError); } } DWORD CSerialPort::GetOverlappedResult(_In_ OVERLAPPED& overlapped, _Out_ DWORD& dwBytesTransferred, _In_ BOOL bWait) { //Validate our parameters DWORD dwLastError = 0; ATLASSERT(IsOpen()); dwBytesTransferred = 0; if (!::GetOverlappedResult(m_hComm, &overlapped, &dwBytesTransferred, bWait)) { dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetOverlappedResult, Failed in call to GetOverlappedResult, Error:%u\n"), dwLastError); //ThrowSerialException(dwLastError); } return dwLastError; } void CSerialPort::CancelIo() { //Validate our parameters ATLASSERT(IsOpen()); if (!::CancelIo(m_hComm)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("Failed in call to CancelIO, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } DWORD CSerialPort::BytesWaiting() { //Validate our parameters //ATLASSERT(IsOpen()); if (!IsOpen()) return 0; //Check to see how many characters are unread COMSTAT stat; GetStatus(stat); return stat.cbInQue; } DWORD CSerialPort::BytesPending() { if (!IsOpen()) return 0; //Check to see how many characters are unwrite COMSTAT stat; GetStatus(stat); return stat.cbOutQue; } void CSerialPort::TransmitChar(_In_ char cChar) { //Validate our parameters ATLASSERT(IsOpen()); if (!TransmitCommChar(m_hComm, cChar)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::TransmitChar, Failed in call to TransmitCommChar, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::GetConfig(_In_ COMMCONFIG& config) { //Validate our parameters ATLASSERT(IsOpen()); DWORD dwSize = sizeof(COMMCONFIG); if (!GetCommConfig(m_hComm, &config, &dwSize)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetConfig, Failed in call to GetCommConfig, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetConfig(_In_ COMMCONFIG& config) { //Validate our parameters ATLASSERT(IsOpen()); DWORD dwSize = sizeof(COMMCONFIG); if (!SetCommConfig(m_hComm, &config, dwSize)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetConfig, Failed in call to SetCommConfig, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetBreak() { //Validate our parameters ATLASSERT(IsOpen()); if (!SetCommBreak(m_hComm)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetBreak, Failed in call to SetCommBreak, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::ClearBreak() { //Validate our parameters ATLASSERT(IsOpen()); if (!ClearCommBreak(m_hComm)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::ClearBreak, Failed in call to SetCommBreak, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::ClearError(_Out_ DWORD& dwErrors) { //Validate our parameters ATLASSERT(IsOpen()); if (!ClearCommError(m_hComm, &dwErrors, NULL)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::ClearError, Failed in call to ClearCommError, Error:%u\n"), dwLastError); //ThrowSerialException(dwLastError); } } void CSerialPort::GetDefaultConfig(_In_ int nPort, _Out_ COMMCONFIG& config) { //Create the device name as a string TCHAR szPort[12]; _stprintf_s(szPort, sizeof(szPort) / sizeof(TCHAR), _T("COM%d"), nPort); return GetDefaultConfig(szPort, config); } void CSerialPort::GetDefaultConfig(_In_z_ LPCTSTR pszPort, _Out_ COMMCONFIG& config) { DWORD dwSize = sizeof(COMMCONFIG); if (!GetDefaultCommConfig(pszPort, &config, &dwSize)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetDefaultConfig, Failed in call to GetDefaultCommConfig, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetDefaultConfig(_In_ int nPort, _In_ COMMCONFIG& config) { //Create the device name as a string TCHAR szPort[12]; _stprintf_s(szPort, sizeof(szPort) / sizeof(TCHAR), _T("COM%d"), nPort); return SetDefaultConfig(szPort, config); } void CSerialPort::SetDefaultConfig(_In_z_ LPCTSTR pszPort, _In_ COMMCONFIG& config) { DWORD dwSize = sizeof(COMMCONFIG); if (!SetDefaultCommConfig(pszPort, &config, dwSize)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetDefaultConfig, Failed in call to SetDefaultCommConfig, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::GetStatus(_Out_ COMSTAT& stat) { //Validate our parameters ATLASSERT(IsOpen()); DWORD dwErrors; if (!ClearCommError(m_hComm, &dwErrors, &stat)) { DWORD dwLastError = GetLastError(); stat.cbInQue = 0; ATLTRACE(_T("CSerialPort::GetStatus, Failed in call to ClearCommError, Error:%u\n"), dwLastError); // ThrowSerialException(dwLastError); } } void CSerialPort::GetState(_Out_ DCB& dcb) { //Validate our parameters ATLASSERT(IsOpen()); if (!GetCommState(m_hComm, &dcb)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetState, Failed in call to GetCommState, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetState(_In_ DCB& dcb) { //Validate our parameters ATLASSERT(IsOpen()); if (!SetCommState(m_hComm, &dcb)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetState, Failed in call to SetCommState, Error:%u\n"), dwLastError); // ThrowSerialException(dwLastError); } } void CSerialPort::Escape(_In_ DWORD dwFunc) { //Validate our parameters ATLASSERT(IsOpen()); if (!EscapeCommFunction(m_hComm, dwFunc)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::Escape, Failed in call to EscapeCommFunction, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::ClearDTR() { Escape(CLRDTR); } void CSerialPort::ClearRTS() { Escape(CLRRTS); } void CSerialPort::SetDTR() { Escape(SETDTR); } void CSerialPort::SetRTS() { Escape(SETRTS); } void CSerialPort::SetXOFF() { Escape(SETXOFF); } void CSerialPort::SetXON() { Escape(SETXON); } void CSerialPort::GetProperties(_Inout_ COMMPROP& properties) { //Validate our parameters ATLASSERT(IsOpen()); if (!GetCommProperties(m_hComm, &properties)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetProperties, Failed in call to GetCommProperties, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::GetModemStatus(_Out_ DWORD& dwModemStatus) { //Validate our parameters ATLASSERT(IsOpen()); if (!GetCommModemStatus(m_hComm, &dwModemStatus)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetModemStatus, Failed in call to GetCommModemStatus, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetMask(_In_ DWORD dwMask) { //Validate our parameters ATLASSERT(IsOpen()); if (!SetCommMask(m_hComm, dwMask)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetMask, Failed in call to SetCommMask, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::GetMask(_Out_ DWORD& dwMask) { //Validate our parameters ATLASSERT(IsOpen()); if (!GetCommMask(m_hComm, &dwMask)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetMask, Failed in call to GetCommMask, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::Flush() { //Validate our parameters ATLASSERT(IsOpen()); if (!FlushFileBuffers(m_hComm)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::Flush, Failed in call to FlushFileBuffers, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::Purge(_In_ DWORD dwFlags) { //Validate our parameters ATLASSERT(IsOpen()); if (!PurgeComm(m_hComm, dwFlags)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::Purge, Failed in call to PurgeComm, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::TerminateOutstandingWrites() { Purge(PURGE_TXABORT); } void CSerialPort::TerminateOutstandingReads() { Purge(PURGE_RXABORT); } void CSerialPort::ClearWriteBuffer() { Purge(PURGE_TXCLEAR); } void CSerialPort::ClearReadBuffer() { Purge(PURGE_RXCLEAR); } void CSerialPort::Setup(_In_ DWORD dwInQueue, _In_ DWORD dwOutQueue) { //Validate our parameters ATLASSERT(IsOpen()); if (!SetupComm(m_hComm, dwInQueue, dwOutQueue)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::Setup, Failed in call to SetupComm, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::SetTimeouts(_In_ COMMTIMEOUTS& timeouts) { //Validate our parameters ATLASSERT(IsOpen()); if (!SetCommTimeouts(m_hComm, &timeouts)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::SetTimeouts, Failed in call to SetCommTimeouts, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::GetTimeouts(_Out_ COMMTIMEOUTS& timeouts) { //Validate our parameters ATLASSERT(IsOpen()); if (!GetCommTimeouts(m_hComm, &timeouts)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::GetTimeouts, Failed in call to GetCommTimeouts, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } void CSerialPort::Set0Timeout() { COMMTIMEOUTS Timeouts; memset(&Timeouts, 0, sizeof(Timeouts)); Timeouts.ReadIntervalTimeout = MAXDWORD; SetTimeouts(Timeouts); } void CSerialPort::Set0WriteTimeout() { COMMTIMEOUTS Timeouts; GetTimeouts(Timeouts); Timeouts.WriteTotalTimeoutMultiplier = 0; Timeouts.WriteTotalTimeoutConstant = 0; SetTimeouts(Timeouts); } void CSerialPort::Set0ReadTimeout() { COMMTIMEOUTS Timeouts; GetTimeouts(Timeouts); Timeouts.ReadIntervalTimeout = MAXDWORD; Timeouts.ReadTotalTimeoutMultiplier = 0; Timeouts.ReadTotalTimeoutConstant = 0; SetTimeouts(Timeouts); } void CSerialPort::WaitEvent(_Inout_ DWORD& dwMask) { //Validate our parameters ATLASSERT(IsOpen()); if (!WaitCommEvent(m_hComm, &dwMask, NULL)) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::WaitEvent, Failed in call to WaitCommEvent, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } } BOOL CSerialPort::WaitEvent(_Inout_ DWORD& dwMask, _Inout_ OVERLAPPED& overlapped) { //Validate our parameters ATLASSERT(IsOpen()); ATLASSERT(overlapped.hEvent != NULL); BOOL bSuccess = WaitCommEvent(m_hComm, &dwMask, &overlapped); if (!bSuccess) { DWORD dwLastError = GetLastError(); ATLTRACE(_T("CSerialPort::WaitEvent, Failed in call to WaitCommEvent, Error:%u\n"), dwLastError); ThrowSerialException(dwLastError); } return bSuccess; } #pragma once #include "afxwin.h" // 自定义串口异常类 class CSerialException : public CException { public: DWORD m_dwError; CSerialException(DWORD dwError); }; // 串口类声明 class CSerialPort { public: enum Parity { None, Odd, Even }; enum StopBits { One = 1, OnePointFive = 2, Two = 3 }; CSerialPort(); virtual ~CSerialPort(); // 添加 virtual 关键字 BOOL Open(LPCTSTR lpszPort, DWORD dwBaudRate, Parity parity, BYTE byDataBits, StopBits stopBits); void Close(); BOOL IsOpen() const { return m_hComm != INVALID_HANDLE_VALUE; } DWORD Write(const void* lpBuf, DWORD dwCount); DWORD Read(void* lpBuf, DWORD dwCount); DWORD BytesWaiting(); void Set0Timeout(); private: HANDLE m_hComm; }; // CPCMFCDlg 对话框 class CPCMFCDlg : public CDialogEx { public: CPCMFCDlg(CWnd* pParent = nullptr); virtual ~CPCMFCDlg(); #ifdef AFX_DESIGN_TIME enum { IDD = IDD_PCMFC_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); virtual BOOL OnInitDialog(); protected: HICON m_hIcon; // 控件变量 CComboBox m_cbPort; CComboBox m_cbBaud; CComboBox m_cbParity; CComboBox m_cbDate; CComboBox m_cbStop; CListBox m_listStatus; // 数据变量 CString m_strTime1; CString m_strTime2; CString m_strTime3; // 状态变量 BOOL m_bConnected; BOOL m_bTimerActive; BOOL m_bThreadRunning; // 串口对象 CSerialPort m_SerialPort; HANDLE m_hThread; // 辅助函数 void UpdateControls(); UINT ReadThreadFunc(); static UINT ReadThread(LPVOID pParam); BOOL ValidateTimeFormat(const CString& strTime); // 消息处理 DECLARE_MESSAGE_MAP() afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnBnClickedBtnOpen(); afx_msg void OnBnClickedBtnClose(); afx_msg void OnBnClickedBtnFeedNow(); afx_msg void OnBnClickedBtnFeedTimer(); afx_msg LRESULT OnUpdateStatus(WPARAM wParam, LPARAM lParam); afx_msg void OnTimer(UINT_PTR nIDEvent); afx_msg void OnDestroy(); }; #include "pch.h" #include "PCMFC.h" #include "PCMFCDlg.h" #include "afxdialogex.h" #include <winbase.h> #include <tchar.h> #include <atlstr.h> #ifdef _DEBUG #define new DEBUG_NEW #endif #define FEED_COMMAND 0x01 #define COMPLETE_SIGNAL 0xFF #define WM_UPDATE_STATUS (WM_USER + 100) // CSerialException 实现 CSerialException::CSerialException(DWORD dwError) : m_dwError(dwError) {} // CSerialPort 成员函数实现 CSerialPort::CSerialPort() : m_hComm(INVALID_HANDLE_VALUE) {} CSerialPort::~CSerialPort() { if (IsOpen()) Close(); } BOOL CSerialPort::Open(LPCTSTR lpszPort, DWORD dwBaudRate, Parity parity, BYTE byDataBits, StopBits stopBits) { m_hComm = CreateFile(lpszPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (m_hComm == INVALID_HANDLE_VALUE) throw new CSerialException(GetLastError()); DCB dcb = { 0 }; dcb.DCBlength = sizeof(DCB); if (!GetCommState(m_hComm, &dcb)) throw new CSerialException(GetLastError()); dcb.BaudRate = dwBaudRate; dcb.ByteSize = byDataBits; switch (parity) { case None: dcb.Parity = NOPARITY; break; case Odd: dcb.Parity = ODDPARITY; break; case Even: dcb.Parity = EVENPARITY; break; } switch (stopBits) { case One: dcb.StopBits = ONESTOPBIT; break; case OnePointFive: dcb.StopBits = ONE5STOPBITS; break; case Two: dcb.StopBits = TWOSTOPBITS; break; } dcb.fBinary = TRUE; dcb.fDtrControl = DTR_CONTROL_ENABLE; dcb.fRtsControl = RTS_CONTROL_ENABLE; if (!SetCommState(m_hComm, &dcb)) throw new CSerialException(GetLastError()); // 设置超时 COMMTIMEOUTS timeouts = { 0 }; timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(m_hComm, &timeouts)) throw new CSerialException(GetLastError()); return TRUE; } void CSerialPort::Close() { if (m_hComm != INVALID_HANDLE_VALUE) { CloseHandle(m_hComm); m_hComm = INVALID_HANDLE_VALUE; } } DWORD CSerialPort::Write(const void* lpBuf, DWORD dwCount) { DWORD dwBytesWritten = 0; OVERLAPPED ov = { 0 }; ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, &ov)) { if (GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, INFINITE); GetOverlappedResult(m_hComm, &ov, &dwBytesWritten, FALSE); } else { throw new CSerialException(GetLastError()); } } CloseHandle(ov.hEvent); return dwBytesWritten; } DWORD CSerialPort::Read(void* lpBuf, DWORD dwCount) { DWORD dwBytesRead = 0; OVERLAPPED ov = { 0 }; ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!ReadFile(m_hComm, lpBuf, dwCount, &dwBytesRead, &ov)) { if (GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, INFINITE); GetOverlappedResult(m_hComm, &ov, &dwBytesRead, FALSE); } else { throw new CSerialException(GetLastError()); } } CloseHandle(ov.hEvent); return dwBytesRead; } DWORD CSerialPort::BytesWaiting() { COMSTAT comStat; DWORD dwErrors; if (!ClearCommError(m_hComm, &dwErrors, &comStat)) throw new CSerialException(GetLastError()); return comStat.cbInQue; } void CSerialPort::Set0Timeout() { COMMTIMEOUTS timeouts = { 0 }; timeouts.ReadIntervalTimeout = MAXDWORD; timeouts.ReadTotalTimeoutConstant = 0; timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = 0; if (!SetCommTimeouts(m_hComm, &timeouts)) throw new CSerialException(GetLastError()); } // CPCMFCDlg 实现 CPCMFCDlg::CPCMFCDlg(CWnd* pParent) : CDialogEx(IDD_PCMFC_DIALOG, pParent) , m_strTime1(_T("08:00")) , m_strTime2(_T("12:00")) , m_strTime3(_T("18:00")) , m_bConnected(FALSE) , m_bTimerActive(FALSE) , m_bThreadRunning(FALSE) , m_hThread(NULL) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } CPCMFCDlg::~CPCMFCDlg() { if (m_hThread) { CloseHandle(m_hThread); } } void CPCMFCDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_COMBO_PORT, m_cbPort); DDX_Control(pDX, IDC_COMBO_BAUD, m_cbBaud); DDX_Control(pDX, IDC_COMBO_PARITY, m_cbParity); DDX_Control(pDX, IDC_COMBO_DATA, m_cbDate); DDX_Control(pDX, IDC_COMBO_STOP, m_cbStop); DDX_Control(pDX, IDC_LIST, m_listStatus); DDX_Text(pDX, IDC_EDIT_TIME1, m_strTime1); DDX_Text(pDX, IDC_EDIT_TIME2, m_strTime2); DDX_Text(pDX, IDC_EDIT_TIME3, m_strTime3); } BEGIN_MESSAGE_MAP(CPCMFCDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON_OPEN, &CPCMFCDlg::OnBnClickedBtnOpen) ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CPCMFCDlg::OnBnClickedBtnClose) ON_BN_CLICKED(IDC_BUTTON_NOWFEED, &CPCMFCDlg::OnBnClickedBtnFeedNow) ON_BN_CLICKED(IDC_BUTTON_TIMEFEED, &CPCMFCDlg::OnBnClickedBtnFeedTimer) ON_MESSAGE(WM_UPDATE_STATUS, &CPCMFCDlg::OnUpdateStatus) ON_WM_TIMER() ON_WM_DESTROY() END_MESSAGE_MAP() BOOL CPCMFCDlg::OnInitDialog() { CDialogEx::OnInitDialog(); SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); // 初始化串口选择下拉框 for (int i = 1; i <= 16; i++) { CString port; port.Format(_T("COM%d"), i); m_cbPort.AddString(port); } m_cbPort.SetCurSel(0); // 初始化波特率下拉框 CString baudRates[] = { _T("9600"), _T("19200"), _T("38400"), _T("57600"), _T("115200") }; for (auto& baud : baudRates) { m_cbBaud.AddString(baud); } m_cbBaud.SetCurSel(0); // 初始化校验位下拉框 CString parityOptions[] = { _T("None"), _T("Odd"), _T("Even") }; for (auto& parity : parityOptions) { m_cbParity.AddString(parity); } m_cbParity.SetCurSel(0); // 初始化数据位下拉框 CString dataBits[] = { _T("5"), _T("6"), _T("7"), _T("8") }; for (auto& bits : dataBits) { m_cbDate.AddString(bits); } m_cbDate.SetCurSel(3); // 初始化停止位下拉框 CString stopBits[] = { _T("1"), _T("1.5"), _T("2") }; for (auto& stop : stopBits) { m_cbStop.AddString(stop); } m_cbStop.SetCurSel(0); // 初始禁用控件 UpdateControls(); return TRUE; } void CPCMFCDlg::UpdateControls() { BOOL bConnected = m_SerialPort.IsOpen(); GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(!bConnected); GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(bConnected); GetDlgItem(IDC_BUTTON_NOWFEED)->EnableWindow(bConnected); GetDlgItem(IDC_BUTTON_TIMEFEED)->EnableWindow(bConnected); BOOL bTimerActive = m_bTimerActive; GetDlgItem(IDC_EDIT_TIME1)->EnableWindow(bConnected && !bTimerActive); GetDlgItem(IDC_EDIT_TIME2)->EnableWindow(bConnected && !bTimerActive); GetDlgItem(IDC_EDIT_TIME3)->EnableWindow(bConnected && !bTimerActive); } BOOL CPCMFCDlg::ValidateTimeFormat(const CString& strTime) { if (strTime.GetLength() != 5) return FALSE; if (strTime[2] != ':') return FALSE; int hour = _ttoi(strTime.Left(2)); int minute = _ttoi(strTime.Mid(3)); return (hour >= 0 && hour <= 23) && (minute >= 0 && minute <= 59); } UINT CPCMFCDlg::ReadThread(LPVOID pParam) { CPCMFCDlg* pDlg = (CPCMFCDlg*)pParam; return pDlg->ReadThreadFunc(); } UINT CPCMFCDlg::ReadThreadFunc() { BYTE buffer[1]; while (m_bThreadRunning) { try { DWORD dwBytes = m_SerialPort.BytesWaiting(); if (dwBytes > 0) { if (m_SerialPort.Read(buffer, 1) > 0) { if (buffer[0] == COMPLETE_SIGNAL) { CString* pMsg = new CString(_T("喂食操作完成")); PostMessage(WM_UPDATE_STATUS, (WPARAM)pMsg, 0); } } } Sleep(50); } catch (CSerialException* e) { CString* pMsg = new CString(); pMsg->Format(_T("串口读取错误: %d"), e->m_dwError); PostMessage(WM_UPDATE_STATUS, (WPARAM)pMsg, 0); delete e; break; } } return 0; } void CPCMFCDlg::OnBnClickedBtnOpen() { CString strPort; m_cbPort.GetWindowText(strPort); strPort = _T("\\\\.\\") + strPort; CString strBaud; m_cbBaud.GetLBText(m_cbBaud.GetCurSel(), strBaud); DWORD dwBaud = _ttoi(strBaud); int nParity = m_cbParity.GetCurSel(); CString strDataBits; m_cbDate.GetLBText(m_cbDate.GetCurSel(), strDataBits); BYTE byDataBits = (BYTE)_ttoi(strDataBits); int nStopBits = m_cbStop.GetCurSel(); try { CSerialPort::Parity parity = static_cast<CSerialPort::Parity>(nParity); CSerialPort::StopBits stopBits = static_cast<CSerialPort::StopBits>(nStopBits); m_SerialPort.Open(strPort, dwBaud, parity, byDataBits, stopBits); m_SerialPort.Set0Timeout(); m_bConnected = TRUE; m_bThreadRunning = TRUE; m_hThread = (HANDLE)_beginthreadex(NULL, 0, ReadThread, this, 0, NULL); m_listStatus.AddString(_T("串口打开成功")); } catch (CSerialException* e) { CString strError; strError.Format(_T("打开串口失败: 错误代码 %d"), e->m_dwError); m_listStatus.AddString(strError); delete e; m_bConnected = FALSE; } UpdateControls(); } void CPCMFCDlg::OnBnClickedBtnClose() { m_bThreadRunning = FALSE; if (m_hThread) { WaitForSingleObject(m_hThread, 1000); CloseHandle(m_hThread); m_hThread = NULL; } try { if (m_SerialPort.IsOpen()) { m_SerialPort.Close(); m_listStatus.AddString(_T("串口已关闭")); } } catch (CSerialException* e) { delete e; } m_bConnected = FALSE; UpdateControls(); } void CPCMFCDlg::OnBnClickedBtnFeedNow() { BYTE cmd = FEED_COMMAND; try { if (m_SerialPort.IsOpen()) { m_SerialPort.Write(&cmd, 1); m_listStatus.AddString(_T("喂食命令已发送")); } } catch (CSerialException* e) { CString strError; strError.Format(_T("发送失败: 错误代码 %d"), e->m_dwError); m_listStatus.AddString(strError); delete e; } } void CPCMFCDlg::OnBnClickedBtnFeedTimer() { if (m_bTimerActive) { KillTimer(1); m_bTimerActive = FALSE; GetDlgItem(IDC_BUTTON_TIMEFEED)->SetWindowText(_T("开始定时")); m_listStatus.AddString(_T("定时器已停止")); } else { UpdateData(TRUE); // 验证时间格式 if (!ValidateTimeFormat(m_strTime1) || !ValidateTimeFormat(m_strTime2) || !ValidateTimeFormat(m_strTime3)) { MessageBox(_T("时间格式无效! 请使用HH:MM格式"), _T("错误"), MB_ICONERROR); return; } m_bTimerActive = TRUE; SetTimer(1, 1000, NULL); // 每秒检查一次 GetDlgItem(IDC_BUTTON_TIMEFEED)->SetWindowText(_T("停止定时")); m_listStatus.AddString(_T("定时器已启动")); } UpdateControls(); } void CPCMFCDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { CTime now = CTime::GetCurrentTime(); CString strNow = now.Format(_T("%H:%M")); if (strNow == m_strTime1 || strNow == m_strTime2 || strNow == m_strTime3) { OnBnClickedBtnFeedNow(); } } CDialogEx::OnTimer(nIDEvent); } LRESULT CPCMFCDlg::OnUpdateStatus(WPARAM wParam, LPARAM lParam) { CString* pStr = (CString*)wParam; if (pStr) { m_listStatus.AddString(*pStr); delete pStr; } return 0; } void CPCMFCDlg::OnDestroy() { CDialogEx::OnDestroy(); if (m_bTimerActive) { KillTimer(1); } m_bThreadRunning = FALSE; if (m_hThread) { WaitForSingleObject(m_hThread, 1000); CloseHandle(m_hThread); m_hThread = NULL; } if (m_SerialPort.IsOpen()) { m_SerialPort.Close(); } } void CPCMFCDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } HCURSOR CPCMFCDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } "public: void __cdecl CSerialPort::Set0Timeout(void)" (?Set0Timeout@CSerialPort@@QEAAXXZ) 已经在 PCMFCDlg.obj 中定义 "public: unsigned long __cdecl CSerialPort::BytesWaiting(void)" (?BytesWaiting@CSerialPort@@QEAAKXZ) 已经在 PCMFCDlg.obj 中定义 "public: unsigned long __cdecl CSerialPort::Write(void const *,unsigned long)" (?Write@CSerialPort@@QEAAKPEBXK@Z) 已经在 PCMFCDlg.obj 中定义 "public: void __cdecl CSerialPort::Close(void)" (?Close@CSerialPort@@QEAAXXZ) 已经在 PCMFCDlg.obj 中定义 "public: virtual __cdecl CSerialPort::~CSerialPort(void)" (??1CSerialPort@@UEAA@XZ) 已经在 PCMFCDlg.obj 中定义 "public: __cdecl CSerialPort::CSerialPort(void)" (??0CSerialPort@@QEAA@XZ) 已经在 PCMFCDlg.obj 中定义 "public: __cdecl CSerialException::CSerialException(unsigned long)" (??0CSerialException@@QEAA@K@Z) 已经在 PCMFCDlg.obj 中定义 找到一个或多个多重定义的符号 请你解决这些问题
最新发布
06-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值