September, 2011

  • The Old New Thing

    Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?


    If you look at the disassembly of functions inside Windows DLLs, you'll find that they begin with the seemingly pointless instruction MOV EDI, EDI. This instruction copies a register to itself and updates no flags; it is completely meaningless. So why is it there?

    It's a hot-patch point.

    The MOV EDI, EDI instruction is a two-byte NOP, which is just enough space to patch in a jump instruction so that the function can be updated on the fly. The intention is that the MOV EDI, EDI instruction will be replaced with a two-byte JMP $-5 instruction to redirect control to five bytes of patch space that comes immediately before the start of the function. Five bytes is enough for a full jump instruction, which can send control to the replacement function installed somewhere else in the address space.

    Although the five bytes of patch space before the start of the function consists of five one-byte NOP instructions, the function entry point uses a single two-byte NOP.

    Why not use Detours to hot-patch the function, then you don't need any patch space at all.

    The problem with Detouring a function during live execution is that you can never be sure that at the moment you are patching in the Detour, another thread isn't in the middle of executing an instruction that overlaps the first five bytes of the function. (And you have to alter the code generation so that no instruction starting at offsets 1 through 4 of the function is ever the target of a jump.) You could work around this by suspending all the threads while you're patching, but that still won't stop somebody from doing a CreateRemoteThread after you thought you had successfully suspended all the threads.

    Why not just use two NOP instructions at the entry point?

    Well, because a NOP instruction consumes one clock cycle and one pipe, so two of them would consume two clock cycles and two pipes. (The instructions will likely be paired, one in each pipe, so the combined execution will take one clock cycle.) On the other hand, the MOV EDI, EDI instruction consumes one clock cycle and one pipe. (In practice, the instruction will occupy one pipe, leaving the other available to execute another instruction in parallel. You might say that the instruction executes in half a cycle.) However you calculate it, the MOV EDI, EDI instruction executes in half the time of two NOP instructions.

    On the other hand, the five NOPs inserted before the start of the function are never executed, so it doesn't matter what you use to pad them. It could've been five garbage bytes for all anybody cares.

    But much more important than cycle-counting is that the use of a two-byte NOP avoids the Detours problem: If the code had used two single-byte NOP instructions, then there is the risk that you will install your patch just as a thread has finished executing the first single-byte NOP and is about to begin executing the second single-byte NOP, resulting in the thread treating the second half of your JMP $-5 as the start of a new instruction.

    There's a lot of patching machinery going on that most people don't even realize. Maybe at some point, I'll get around to writing about how the operating system manages patches for software that isn't installed yet, so that when you do install the software, the patch is already there, thereby closing the vulnerability window between installing the software and downloading the patches.

  • The Old New Thing

    Sending a window a WM_DESTROY message is like prank calling somebody pretending to be the police


    A customer was trying to track down a memory leak in their program. Their leak tracking tool produced the stacks which allocated memory that was never freed, and they all seemed to come from uxtheme.dll, which is a DLL that comes with Windows. The customer naturally contacted Microsoft to report what appeared to be a memory leak in Windows.

    I was one of the people who investigated this case, and the customer was able to narrow down the scenario which was triggering the leak. Eventually, I tracked it down. First, here's the thread that caused the leak:

    DWORD CALLBACK ThreadProc(void *lpParameter)
     // This CreateWindow caused uxtheme to allocate some memory
     HWND hwnd = CreateWindow(...);
     MSG msg;
     while (GetMessage(&msg, NULL, 0, 0)) {
     return 0;

    This thread creates an invisible window whose job is to do something until it is destroyed, at which point the thread is no longer needed. The window procedure for the window looks like this:

                             WPARAM wParam, LPARAM lParam)
     switch (uMsg) {
     ... business logic deleted ...
     case WM_DESTROY:
     return DefWindowProc(hwnd, uMsg, wParam, lParam);

    Sinec this is the main window on the thread, its destruction posts a quit message to signal the message loop to exit.

    There's nothing obviously wrong here that would cause uxtheme to leak memory. And yet it does. The memory is allocated when the window is created, and it's supposed to be freed when the window is destroyed. And the only time we exit the message loop is when the window is destroyed. So how is it that this thread manages to exit without destroying the window?

    The key is how the program signals this window that it should go away.

    void MakeWorkerGoAway()
     // Find the worker window if it is registered
     HWND hwnd = GetWorkerWindow();
     // If we have one, destroy it
     if (hwnd) {
      // DestroyWindow doesn't work for windows that belong
      // to other threads.
      // DestroyWindow(hwnd);
      SendMessage(hwnd, WM_DESTROY, 0, 0);

    The authors of this code first tried destroying the window with DestroyWindow but ran into the problem that you cannot destroy a window that belongs to a different thread. "But aha, since the DestroyWindow function sends the WM_DESTROY message, we can just cut out the middle man and send the message directly."

    Well, yeah, you can do that, but that doesn't actually destroy the window. It just pretends to destroy the window by prank-calling the window procedure and saying "Ahem, um, yeah, this is the, um, window manager? (stifled laughter) And, um, like, we're just calling you to tell you, um, you're being destroyed. (giggle) So, um, you should like pack up your bags and (snort) sell all your furniture! (raucous laughter)"

    The window manager sends the WM_DESTROY message to a window as part of the window destruction process. If you send the message yourself, then you're making the window think that it's being destroyed, even though it isn't. (Because it's DestroyWindow that destroys windows.)

    The victim window procedure goes through its "Oh dear, I'm being destroyed, I guess I'd better clean up my stuff" logic, and in this case, it unregisters the worker window and posts a quit message to the message loop. The message loop picks up the WM_QUIT and exits the thread.

    And that's the memory leak: The thread exited before all its windows were destroyed. That worker window is still there, because it never got DestroyWindow'd. Since the window wasn't actually destroyed, the internal memory used to keep track of the window didn't get freed, and there you have your leak.

    "You just got punk'd!"

    The correct solution is for the MakeWorkerGoAway function to send a message to the worker window to tell it, "Hey, I'd like you to go away. Please call DestroyWindow on yourself." You can invent a private message for this, or you can take advantage of the fact that the default behavior of the WM_CLOSE message is to destroy the window. Since our window procedure doesn't override WM_CLOSE, the message will fall through to DefWindowProc which will convert the WM_CLOSE into a DestroyWindow.

    Now that you understand the difference between destroying a window and prank-calling a window telling it is being destroyed, you might be able to help Arno with his problem.

  • The Old New Thing

    Why is the registry a hierarchical database instead of a relational one?


    Commenter ton asks why the registry was defined as a hierarchical database instead of a relational database.

    Heck, it's not even a hierarchical database!

    The original registry was just a dictionary; i.e., a list of name/value pairs, accessed by name. In other words, it was a flat database.

    .txt txtfile
    txtfile Text Document
    txtfile\DefaultIcon notepad.exe,1
    txtfile\shell open
    txtfile\shell\open\command notepad %1

    If you turned your head sideways and treated the backslashes as node separators, you could sort of trick yourself into believing that this resulted in something vaguely approximating a hierarchical database, and a really lame one at that (since each node held only one piece of data).

    When you choose your data structures, you necessarily are guided by the intended use pattern and the engineering constraints. One important engineering constraint was that you have to minimize memory consumption. All of the registry code fit in 16KB of memory. (Recall that Windows 3.1 had to run on machines with only 1MB of memory.)

    Okay, what is the usage pattern of the registry? As originally designed, the registry was for recording information about file types. We have the file types themselves (txtfile), properties about those file types (DefaultIcon), verbs associated with those file types (open), and verb implementations (command or ddeexec). Some verb implementations are simple (command involves just a single string describing the command line); others are complex (ddeexec requires the execute string, the application, and the topic, plus an optional alternate execute string).

    • Given a file type and a property, retrieve the value of that property.
    • Given a file type and a verb, retrieve information about how to perform that verb.
    • The set of properties can be extended.
    • The set of property schemata can be extended.
    • The set of verbs can be extended.
    • The set of verb implementations can be extended.

    Since the properties and verb implementations can be extended, you can't come up with a single schema that covers everything. For example, over the years, new file type properties have been added such as ContentType, OpenWithList, and ShellNew. The first one is a simple string; the second is a list of strings, and the third is a complex key with multiple variants. Meanwhile, additional verb implementations have been added, such as DropTarget.

    Given the heterogeneity of the data the registry needs to keep track of, imposing some sort of uniform schema is doomed to failure.

    "But you can just update the schemata each time the registration is extended."

    That creates its own problems. For example, to support roaming user profiles, you need a single registry hive to work on multiple versions of the operating system. If version N+1 adds a new schema, but then the profile roams to a machine running version N, then that registry hive will be interpreted as corrupted since it contains data that matches no valid schema.

    "Well, then include the schemata with the roaming profile so that when the older operating system sees the hive, it also sees the updated schemata."

    This is trickier than it sounds, because when the profile roams to the newer operating system, you presumably want the schemata to be upgraded and written back into the user profile. It also assumes that the versioning of the schemata is strictly linear. (What if you roam a user profile from a Windows XP machine to a Server 2003 machine? Neither is a descendant of the other.)

    But what kills this proposal is that it makes it impossible for a program to "pre-register" properties for a future version of the operating system. Suppose a new schema is added in version N+1, like, say, the IDropTarget verb implementation. You write a program that you want to run on version N as well as on version N+1. If your installer tries to register the version N+1 information, it will fail since there is no schema for it. But that means that when the user upgrades to version N+1, they don't get the benefit of the version N+1 feature. In order to get the version N+1 feature to work, they have to reinstall the program so the installer says, "Oh, now I can register the version +1 information."

    "Well, then allow applications to install a new schema whenever they need to."

    In other words, make it a total free-for-all. In which case, why do you need a schema at all? Just leave it as an unregulated collection of name/value pairs governed by convention rather than rigid rules, as long as the code which writes the information and the code which reads it agree on the format of the information and where to look for it.

    Hey, wow, that's what the registry already is!

    And besides, if you told somebody, "Hi, yeah, in order to support looking up four pieces of information about file types, Windows 3.1 comes with a copy of SQL Server," they would think you were insane. That's like using a bazooka to kill a mosquito.

    What are you planning on doing with this relational database anyway? Are you thinking of doing an INNER JOIN on the registry? (Besides, the registry is already being abused enough already. Imagine if it were a SQL server: Everybody would store all their data in it!)

    ton explains one way applications could use this advanced functionality:

    An application would have a table or group of tables in relational style registry. A group of settings would be a row. A single setting would be a column. Is it starting to become clearer now how SQL like statements could now be used to constrain what gets deleted and added? How good is your understanding of SQL and DBMS?

    You know what most application authors would say? They would say "Are you mad? You're saying that I need to create a table with one column for each setting? And this table would have a single row (since I have only one application)? All this just so I can save my window position? Screw it, I'm going back to INI files." What'll happen in practice is that everybody will create a table with two columns, a string called name and a blob called value. Now we've come full circle: We have our flat database again.

    And how would they make sure the name of their table doesn't collide with the name of a table created by another application? Probably by encoding the company name and application name into the name of the table, according to some agreed-upon convention. Like say, the Settings table used by the LitSoft program written by LitWare would be called LitWare_LitSoft_Settings. So querying a value from this table would go something like

    SELECT value FROM PerUser.LitWare_LitSoft_Settings
        WHERE name = "WindowPosition"

    Hey, this looks an awful lot like


    One of ton's arguments for using a relational database is that it permits enforcement of referential integrity. But I would argue that in the general case, you don't want strict enforcement of referential integrity. Suppose you uninstall a program. The uninstaller tries to delete the program registration, but that registration is being referenced by foreign keys in other tables. These references were not created by the application itself; perhaps the shell common dialog created them as part of its internal bookkeeping. If the registry blocked the deletion, then the uninstall would fail. "Cannot uninstall application because there's still a reference to it somewhere." And that reference might be in Bob's user profile, from that time Bob said, "Hey can I log onto your machine quickly? I need to look up something." Bob is unlikely to come back to your machine any time soon, so his user profile is just going to sit there holding a reference to that application you want to uninstall for an awfully long time. "Hi, Bob, can you come by my office? I need you to log on so I can uninstall an app."

    So let's assume it goes the other way: The registry automatically deletes orphaned foreign key rows. (And for hives that are not currently available, it just remembers that those foreign key rows should be deleted the next time they are loaded. Nevermind that that list of "foreign key rows that should be deleted the next time Bob logs on" is going to get pretty long.)

    Now suppose you're uninstalling a program not because you want to get rid of it, but because you're doing an uninstall/reinstall troubleshooting step. You uninstall the program, all the orphaned foreign key rows are automatically deleted, then you reinstall the program. Those orphaned foreign key rows are not undeleted; they remain deleted. Result: You lost some settings. This is the reason why you don't clean up per-user data when uninstalling programs.

    Enforcing referential integrity also means that you can't create anticipatory references. One example of this was given earlier, where you register something on version N even though the feature doesn't get activated until the user upgrades to version N+1. More generally, Program X may want to create a reference to Program Y at installation, even if program Y isn't installed yet. (For example, X is a Web browser and Y is a popular plug-in.) The Program Y features remain dormant, because the attempt by Program X to access Program Y will fail, but once the user installs Program Y, then the Program Y features are magically "turned on" in Program X.

    Consider, as an even more specific example, the "kill bit" database. There, the goal isn't to "turn on" features of Program Y but to turn them off. Imagine if referential integrity were enforced: You couldn't kill an ActiveX control until after it was installed!

  • The Old New Thing

    Throwing garbage on the sidewalk: The sad history of the rundll32 program


    During the development of Windows Vista, the application comaptibility team traced a bunch of issues back to people corrupting the stack by using the rundll32 program to call functions that were not designed to be called by rundll32.

    The problems were often subtle. For example, a batch file which used rundll32 incorrectly ended up hanging because the rundll32 process never returned. The misaligned stack resulted in registers being restored from the stack incorrectly, and then the cleanup code inside rundll32 ends up getting confused and wedging itself. The programs got away with it on previous versions of Windows by sheer luck. The version of the compiler used by Windows Vista contains different optimizations, and it ended up arranging stack variables and using registers differently, and what in previous versions of Windows was some corruption that went largely unnoticed became corruption that resulted in the program getting stuck in an infinite loop. Lucky no longer.

    I was asked to come up with a solution for this problem, to fix the rundll32 program so it was more resilient to people who used it incorrectly. To fix other people's bugs for them.

    The solution: Before calling the function, push a hundred bytes of garbage onto the stack (in case the called function pops too many bytes off the stack) and save the stack pointer in a global variable. After the function returns, restore the stack pointer, in case the called function pops too many or too few bytes off the stack. I think I may even have saved the processor registers in global variables, I forget.

    Do not consider this free license to continue abusing the rundll32 program. When the pet store opens on Sundays, that doesn't mean that it's okay to keep throwing garbage on the sidewalk.

  • The Old New Thing

    Appearing to succeed is a valid form of undefined behavior, but it's still undefined


    A customer requested a clarification on the MSDN documentation for the HeapFree function.

    The MSDN documentation says that if the lpMem parameter is NULL, then the behavior is undefined. Is this true?

    As explicitly stated in MSDN, the behavior is undefined. Observe that the annotation on the lpMem parameter is __in, which means that the parameter must be a non-NULL value provided by the caller. (If NULL were permitted, the annotation would have been __in_opt.)

    Undefined behavior means that anything can happen. The program might crash immediately. It might crash five minutes later. It might send email to your boss saying that you screwed up and then read you Vogon poetry. Or maybe not.

    MSDN says don't do it, so don't do it.

    The customer explained why they were interested in knowing more information about undefined behavior:

    We were interested because there is a mismatch between the semantics of a function we are implementing (where NULL is valid and ignored) and the function HeapFree we are using as the implementation. It looks like Windows Vista returns TRUE if you pass NULL.

    If there is a mismatch in semantics between the function you are implementing and the function you are calling, it is your responsibility as the programmer to bridge the gap. The customer didn't say what function they were implementing, but I'm guessing it was something like operator delete. Since your function accepts NULL but HeapFree doesn't, it is your responsibility to filter out NULL parameters.

    void operator delete(void* ptr) throw ()
     if (ptr != NULL)
      HeapFree(CustomHeap, 0, ptr);

    This concept goes by the fancy name of the Adapter Pattern. The less fancy name is wrapper function.

    And the value returned by HeapFree on Windows Vista is irrelevant. Pretending to succeed is a valid form of undefined behavior, because anything qualifies as undefined behavior.

    (Of course, you can't assume that returning TRUE will always be the result of triggering undefined behavior. After all, if you could rely on it, then it wouldn't be undefined any more!)

  • The Old New Thing

    What's the story with the parameters to the WM_INPUT_DEVICE_CHANGE message?


    A customer found these strange macros in winuser.h:

    #if (_WIN32_WINNT >= 0x0601)
    #define GET_DEVICE_CHANGE_WPARAM(wParam)  (LOWORD(wParam))
    #elif (_WIN32_WINNT >= 0x0501)
    #define GET_DEVICE_CHANGE_LPARAM(lParam)  (LOWORD(lParam))
    #endif /* (_WIN32_WINNT >= 0x0601) */

    According to the documentation for the WM_INPUT_DEVICE_CHANGE message, the wParam is the operation code and the lParam is a handle to the device that changed. Given that definition, the correct macro would be GET_DEVICE_CHANGE_WPARAM. What's up with the bogus GET_DEVICE_CHANGE_LPARAM macro?

    The macro was incorrectly defined in Windows Vista. In the Windows 7 version of the Platform SDK, the correct macro was added, but in order to avoid introducing a breaking change to existing code, the old broken macro remains in place in order to retain bug-for-bug compatibility with existing code.

    Even though the macro didn't work, there is a good possibility that code exists which relied on it anyway. For example, people may have read the documentation, read the macro, realized that the macro was wrong, and worked around the bug like this:

     return OnInputDeviceChange(GET_DEVICE_CHANGE_LPARAM(wParam),

    To avoid breaking this code, the old broken definition remains in the header file. But at least it's defined only if you say that you want the Windows Vista version of the header file, so at least people won't use the bad macro going forward.

  • The Old New Thing

    Why are the building numbers on Microsoft main campus so erratic?


    Carrie complains that the building numbers on Microsoft main campus are completely random. Why is building 22 near buildings 40 and 41, far, far away from building 24?

    Because the Microsoft campus evolved.

    Many many years ago, the space on which the central Microsoft campus resides was a mill. Eventually it became an office park, and when Microsoft decided to move its headquarters there, it carved out a little wooded area and constructed four buildings, logically numbered 1 through 4.

    Later, the campus expanded, and plans were drawn up for three more buildings, logically numbered (and placed) 5 through 7. Two of those buildings were constructed, but the third was not built for reasons shrouded in mystery. When the campus expanded a third time, the new buildings were numbered 8 through 11. Presumably, at this point, there were still plans to construct Building 7 someday, so the number remained assigned to the planned-but-not-yet-built building. (Even if the Building 7 plans had been abandoned, the number had already been used in the plans submitted to the City of Redmond, and revising them would have entailed additional paperwork for no real benefit aside from satisfying some anal-retentive compulsion to ensure that every number was used. People who worry about this probably are also waiting for DirectX 4.)

    The campus grew, and each time new buildings were added, they received the next available number. The result of this was that buildings with consecutive numbers could very well end up far apart on campus.

    When the Microsoft main campus expanded across highway 520, the people in charge of assigning numbers decided to assign numbers starting at 100 for buildings on the other side of the highway. Mind you, they didn't stick to that plan rigidly, as there are some buildings numbered in the high 90's on that part of the campus.

    Once the idea of assigning non-consecutive numbers was breached, the number-assigning people went to town. There is a cluster of buildings in the 40's, another in the 50's (with Building 50 being an outlier), and another in the 80's.

    So at least the numbers for newer buildings are a bit less crazy. But if you're looking for an older building, you're going to have a rough time of it.

    Maybe if the original building-numbering people had had the foresight to name the buildings after their GPS coordinates.

    Bonus chatter: In 2009, the building-numbering people tried to rename Buildings 116 through 119 to Studios E through H, presumably because they were across the street from Studios A through D. This "Rebranding Project" was largely mocked. (And of course, just to make things confusing, the new names appear to have been assigned randomly.)

    Bonus chatter 2: The original Building 100 was demolished to make way for The Commons. The soon-to-be-displaced residents of Building 100 had a "demolition party" on their last day in the building, wherein they went around spraying graffiti, smashing walls with sledgehammers, that sort of thing.

  • The Old New Thing

    Why does my asynchronous I/O complete synchronously?


    A customer was creating a large file and found that, even though the file was opened with FILE_FLAG_OVERLAPPED and the Write­File call was being made with an OVERLAPPED structure, the I/O was nevertheless completing synchronously.

    Knowledge Base article 156932 covers some cases in which asynchronous I/O will be converted to synchronous I/O. And in this case, it was scenario number three in that document.

    The reason the customer's asynchronous writes were completing synchronously is that all of the writes were to the end of the file. It so happens that in the current implementation of NTFS, writes which extend the length of the file always complete synchronously. (More specifically, writes which extend the valid data length are forced synchronous.)

    We saw last time that merely calling Set­End­Of­File to pre-extend the file to the final size doesn't help, because that updates the file size but not the valid data length. To avoid synchronous behavior, you need to make sure your writes do not extend the valid data length. The suggestions provided in yesterday's article apply here as well.

  • The Old New Thing

    Microspeak: The bug farm


    In its most general sense, the term bug farm refers to something that is a rich source of bugs.

    It is typically applied to code which is nearly unmaintainable. Code can arrive in this state through a variety of means.

    • Poor initial design.
    • An initial design that has been pushed far beyond its original specification (resulting in features built on top of other features in weird ways).
    • Overwhelming compatibility constraints such that the tiniest perturbation is highly likely to cause some application somewhere to stop working.
    • Responsibility for the code residing in people whom we shall euphemistically describe as "failing to meet your personal standards of code quality."

    The term is most often used as a cautionary term, calling attention to areas where there is high risk that code you're about to write is going to result in a bug farm.

    Aren't we setting ourselves up for a bug farm?
    This could easily lead to a bug farm from different lifetimes for this various state objects.

    The term is quite popular at Microsoft (pre-emptive snarky comment: because Microsoft software is all one giant bug farm). Here are some citations just from

    Layout runs under disable processing. The reason we did that is because, well, reentrant layout is a bug farm.
    A lot of testers suddenly realized that case sensitivity is a veritable bug farm on a project that thinks it is ready to go, but has not yet tried it.
    That type of implicit vs. explicit inference also turned out to be a bug farm.
    Did you forget to handle an entire set of test cases? Is the features implementation overly complex and going to be a bug farm?
  • The Old New Thing

    Why can't I PostMessage the WM_COPYDATA message, but I can SendMessageTimeout it with a tiny timeout?


    After receiving the explanation of what happens to a sent message when Send­Message­Timeout reaches its timeout, a customer found that the explanation raised another question: If the window manager waits until the receiving thread finishes processing the message, then why can't you post a WM_COPY­DATA message? "After all, Send­Message­Timeout with a very short timeout isn't all that different from Post­Message."

    Actually, Send­Message­Timeout with a very short timeout is completely different from Post­Message.

    Let's set aside the one crucial difference that, unlike messages posted by Post­Message, which cannot be recalled, the Send­Message­Timeout function will cancel the message entirely if the receiving thread does not process messages quickly enough.

    Recall that messages posted to a queue via Post­Message are retrieved by the Get­Message function and placed in a MSG structure. Once that's done, the window manager disavows any knowledge of the message. It did its job: It placed the message in the message queue and produced it when the thread requested the next message in the queue. What the program does with the message is completely up in the air. There's no metaphysical requirement that the message be dispatched to its intended recipient. (In fact, you already know of a common case where messages are "stolen" from their intended recipients: Dialog boxes.)

    In principle, the message pump could do anything it wants to the message. Dispatch it immediately, steal the message, throw the message away, eat the message and post a different message, even save the message in its pocket for a rainy day.

    By contrast, there's nothing you can do to redirect inbound non-queued messages. They always go directly to the window procedure.

    The important difference from the standpoint of messages like WM_COPY­DATA is that with sent messages, the window manager knows when message processing is complete: When the window procedure returns. At that time, it can free the temporary buffers used to marshal the message from the sender to the recipient. If the message were posted, the window manager would never be sure.

    Suppose the message is placed in a MSG structure as the result of a call to GetMessage. Now the window manager knows that the receiving thread has the potential for taking action on the message and the buffers need to be valid. But how would it know when the buffers can be freed? "Well you can wait until the exact same parameters get passed in a MSG structure to the Dispatch­Message function." But what if the message loop discards the message? Or what if it decides to dispatch it twice? Or what if it decides to smuggle it inside another message?

    Posted messages have no guarantee of delivery nor do they provide any information as to when the message has been definitely processed, or even if it has been processed at all. If the window manager let you post a WM_COPY­DATA message, it would have to use its psychic powers to know when the memory can be freed.

Page 1 of 3 (26 items) 123