반응형
MS 에서 만든 Memory - mapped 의 설계

MSDN 에서 가져온 내용입니다. 스터디 할겸  참고 자료로 매우 유용해보이네요.
http://msdn.microsoft.com/ko-kr/library/ms810613(en-us).aspx

Memory Technical Articles
Managing Memory-Mapped Files in Win32
 

Randy Kath
Microsoft Developer Network Technology Group

Created: February 9, 1993


Abstract

Determining which function or set of functions to use for managing memory in your Win32™ application is difficult without a solid understanding of how each group of functions works and the overall impact they each have on the Microsoft® Windows NT™ operating system. In an effort to simplify these decisions, this technical article focuses on the use of the memory-mapped file functions in Win32: the functions that are available, the way they are used, and the impact their use has on operating system resources. The following topics are discussed in this article:

  • Introduction to managing memory in Windows™ operating systems
  • What are memory-mapped files?
  • How are memory-mapped files implemented?
  • Sharing memory with memory-mapped files
  • Using memory-mapped file functions

In addition to this technical article, a sample application called ProcessWalker is included on the Microsoft Developer Network CD. This sample application is useful for exploring the behavior of memory-mapped files in a process, and it provides several useful implementation examples.

Introduction

This is one of three related technical articles—"Managing Virtual Memory in Win32," "Managing Memory-Mapped Files in Win32," and the upcoming "Managing Heap Memory in Win32"—that explain how to manage memory in applications for the Win32™ programming interface. In each article, this introduction identifies the basic memory components in the Win32 programming model and indicates which article to reference for specific areas of interest.

The first version of the Microsoft® Windows™ operating system introduced a method of managing dynamic memory based on a single global heap, which all applications and the system share, and multiple, private local heaps, one for each application. Local and global memory management functions were also provided, offering extended features for this new memory management system. More recently, the Microsoft C run-time (CRT) libraries were modified to include capabilities for managing these heaps in Windows using native CRT functions such as malloc and free. Consequently, developers are now left with a choice—learn the new application programming interface (API) provided as part of Windows version 3.1 or stick to the portable, and typically familiar, CRT functions for managing memory in applications written for Windows 3.1.

With the addition of the Win32 API, the number of choices increases. Win32 offers three additional groups of functions for managing memory in applications: memory-mapped file functions, heap memory functions, and virtual-memory functions. These new functions do not replace the existing memory management functions found in Windows version 3.1; rather, they provide new features that generally make life easier for developers when writing the memory management portions of their applications for Win32.

Figure 1. The Win32 API provides different levels of memory management for versatility in application programming.

In all, six sets of memory management functions exist in Win32, as shown in Figure 1, all of which were designed to be used independently of one another. So which set of functions should you use? The answer to this question depends greatly on two things: the type of memory management you want and how the functions relevant to it are implemented in the operating system. In other words, are you building a large database application where you plan to manipulate subsets of a large memory structure? Or maybe you're planning some simple dynamic memory structures, such as linked lists or binary trees? In both cases, you need to know which functions offer the features best suited to your intention and exactly how much of a resource hit occurs when using each function.

Table 1 categorizes the memory management function groups in Win32 and indicates which of the three technical articles in this series describes each group's behavior. Each technical article emphasizes the impact these functions have on the system by describing the behavior of the system in response to using the functions.

Table 1. Various Memory Management Functions Available in Win32

Memory set System resource affected Related technical article
Virtual memory functions A process's virtual address space
System pagefile
System memory
Hard disk space
"Managing Virtual Memory in Win32"
Memory-mapped file functions A process's virtual address space
System pagefile
Standard file I/O
System memory
Hard disk space
"Managing Memory-Mapped Files in Win32"
Heap memory functions A process's virtual address space
System memory
Process heap resource structure
"Managing Heap Memory in Win32"
Global heap memory functions A process's heap resource structure "Managing Heap Memory in Win32"
Local heap memory functions A process's heap resource structure "Managing Heap Memory in Win32"
C run-time reference library A process's heap resource structure "Managing Heap Memory in Win32"

Each technical article discusses issues surrounding the use of Win32-specific functions. For a better understanding of how the Windows NT™ operating system manages system memory, see "The Virtual-Memory Manager in Windows NT" on the Microsoft Developer Network CD (Technical Articles, Win32 and Windows NT Articles).

What Are Memory-Mapped Files?

Memory-mapped files (MMFs) offer a unique memory management feature that allows applications to access files on disk in the same way they access dynamic memory—through pointers. With this capability you can map a view of all or part of a file on disk to a specific range of addresses within your process's address space. And once that is done, accessing the content of a memory-mapped file is as simple as dereferencing a pointer in the designated range of addresses. So, writing data to a file can be as simple as assigning a value to a dereferenced pointer as in:

*pMem = 23;

Similarly, reading from a specific location within the file is simply:

nTokenLen = *pMem;

In the above examples, the pointer pMem represents an arbitrary address in the range of addresses that have been mapped to a view of a file. Each time the address is referenced (that is, each time the pointer is dereferenced), the memory-mapped file is the actual memory being addressed.

Note   While memory-mapped files offer a way to read and write directly to a file at specific locations, the actual action of reading/writing to the disk is handled at a lower level. Consequently, data is not actually transferred at the time the above instructions are executed. Instead, much of the file input/output (I/O) is cached to improve general system performance. You can override this behavior and force the system to perform disk transactions immediately by using the memory-mapped file function FlushViewOfFile explained later.

What Do Memory-Mapped Files Have to Offer?

One advantage to using MMF I/O is that the system performs all data transfers for it in 4K pages of data. Internally all pages of memory are managed by the virtual-memory manager (VMM). It decides when a page should be paged to disk, which pages are to be freed for use by other applications, and how many pages each application can have out of the entire allotment of physical memory. Since the VMM performs all disk I/O in the same manner—reading or writing memory one page at a time—it has been optimized to make it as fast as possible. Limiting the disk read and write instructions to sequences of 4K pages means that several smaller reads or writes are effectively cached into one larger operation, reducing the number of times the hard disk read/write head moves. Reading and writing pages of memory at a time is sometimes referred to as paging and is common to virtual-memory management operating systems.

Another advantage to using MMF I/O is that all of the actual I/O interaction now occurs in RAM in the form of standard memory addressing. Meanwhile, disk paging occurs periodically in the background, transparent to the application. While no gain in performance is observed when using MMFs for simply reading a file into RAM, other disk transactions can benefit immensely. Say, for example, an application implements a flat-file database file structure, where the database consists of hundreds of sequential records. Accessing a record within the file is simply a matter of determining the record's location (a byte offset within the file) and reading the data from the file. Then, for every update, the record must be written to the file in order to save the change. For larger records, it may be advantageous to read only part of the record into memory at a time as needed. Unfortunately, though, each time a new part of the record is needed, another file read is required. The MMF approach works a little differently. When the record is first accessed, the entire 4K page(s) of memory containing the record is read into memory. All subsequent accesses to that record deal directly with the page(s) of memory in RAM. No disk I/O is required or enforced until the file is later closed or flushed.

Note   During normal system paging operations, memory-mapped files can be updated periodically. If the system needs a page of memory that is occupied by a page representing a memory-mapped file, it may free the page for use by another application. If the page was dirty at the time it was needed, the act of writing the data to disk will automatically update the file at that time. (A dirty page is a page of data that has been written to, but not saved to, disk; for more information on types of virtual-memory pages, see "The Virtual-Memory Manager in Windows NT" on the Developer Network CD.)

The flat-file database application example is useful in pointing out another advantage of using memory-mapped files. MMFs provide a mechanism to map portions of a file into memory as needed. This means that applications now have a way of getting to a small segment of data in an extremely large file without having to read the entire file into memory first. Using the above example of a large flat-file database, consider a database file housing 1,000,000 records of 125 bytes each. The file size necessary to store this database would be 1,000,000 * 125 = 125,000,000 bytes. To read a file that large would require an extremely large amount of memory. With MMFs, the entire file can be opened (but at this point no memory is required for reading the file) and a view (portion) of the file can be mapped to a range of addresses. Then, as mentioned above, each page in the view is read into memory only when addresses within the page are accessed.

How Are They Implemented?

Since Windows NT is a page-based virtual-memory system, memory-mapped files represent little more than an extension of an existing, internal memory management component. Essentially all applications in Windows NT are represented in their entirety by one or more files on disk and a subset of those files resident in random access memory (RAM) at any given time. For example, each application has an executable file that represents pages of executable code and resources for the application. These pages are swapped into and out of RAM, as they are needed, by the operating system. When a page of memory is no longer needed, the operating system relinquishes control over the page on behalf of the application that owns it and frees it for use by another. When that page becomes needed again, it is re-read from the executable file on disk. This is called backing the memory with a file, in this case, the executable file. Similarly, when a process starts, pages of memory are used to store static and dynamic data for that application. Once committed, these pages are backed by the system pagefile, similar to the way the executable file is used to back the pages of code. Figure 2 is a graphical representation of how pages of code and data are backed on the hard disk.

Figure 2. Memory used to represent pages of code in processes for Windows NT are backed directly by the application's executable module while memory used for pages of data are backed by the system pagefile.

Treating both code and data in the same manner paves the way for propagating this functionality to a level where applications can use it, too—which is what Win32 does via memory-mapped files.

Shared Memory in Windows NT

Both code and data are treated the same way in Windows NT—both are represented by pages of memory and both have their pages backed by a file on disk. The only real difference is the file by which they are backed—code by the executable image and data by the system pagefile. Because of this, memory-mapped files are also able to provide a mechanism for sharing data between processes. By extending the memory-mapped file capability to include portions of the system pagefile, applications are able to share data that is backed by the pagefile. Shown in Figure 3, each application simply maps a view of the same portion of the pagefile, making the same pages of memory available to each application.

Figure 3. Processes share memory by mapping independent views of a common region in the system pagefile.

Windows NT's tight security system prevents processes from directly sharing information among each other, but MMFs provide a mechanism that works with the security system. In order for one process to share data with another via MMFs, each process must have common access to the file. This is achieved by giving the MMF object a name that both processes use to open the file.

Internally, a shared section of the pagefile translates into pages of memory that are addressable by more than one process. To do this, Windows NT uses an internal resource called a prototype page-table entry (PPTE). PPTEs enable more than one process to address the same physical page of memory. A PPTE is a system resource, so their availability and security is controlled by the system alone. This way processes can share data and still exist on a secure operating system. Figure 4 indicates how PPTEs are used in Windows NT's virtual addressing scheme.

Figure 4. Prototype page-table entries are the mechanism that permits pages of memory to be shared among processes.

One of the best ways to use an MMF for sharing data is to use it in a DLL (dynamic-link library). The PortTool application serves as a useful illustration. PortTool uses a DLL to provide its porting functionality and relies on the main application for the user interface. The reason for this is simple: Other applications can then also use the DLL functionality. That is, other editors that are programmable can import the porting functionality. Because it is entirely feasible for PortTool to be running while another editor that imports the PortTool DLL is also running, it is best to economize system resources as much as possible between the applications. PortTool does this by using an MMF for sharing the porting information with both processes. Otherwise, both applications would be required to load their own set of porting information while running at the same time, a waste of system resources. The PortTool code demonstrates sharing memory via an MMF in a DLL.


Using Memory-Mapped File Functions

Memory-mapped file functions can be thought of as second cousins to the virtual-memory management functions in Win32. Like the virtual-memory functions, these functions directly affect a process's address space and pages of physical memory. No overhead is required to manage the file views, other than the basic virtual-memory management that exists for all processes. These functions deal in reserved pages of memory and committed addresses in a process. The entire set of memory-mapped file functions are:

  • CreateFileMapping
  • OpenFileMapping
  • MapViewOfFile
  • MapViewOfFileEx
  • UnmapViewOfFile
  • FlushViewOfFile
  • CloseHandle

Each of these functions is individually discussed below, along with code examples that demonstrate their use.

Creating a File Mapping

To use a memory-mapped file, you start by creating a memory-mapped file object. The act of creating an MMF object has very little impact on system resources. It does not affect your process's address space, and no virtual memory is allocated for the object (other than for the internal resources that are necessary in representing the object). One exception, however, is that, if the MMF object represents shared memory, an adequate portion of the system pagefile is reserved for use by the MMF during the creation of the object.

The CreateFileMapping function is used to create the file-mapping object as demonstrated in the example listed below, a portion of PMEM.C, the source module from the ProcessWalker sample application.

case IDM_MMFCREATENEW:
    {
    char    szTmpFile[256];

    /* Create temporary file for mapping. */
    GetTempPath (256, szTmpFile);
    GetTempFileName (szTmpFile,
                     "PW",
                     0,
                     MMFiles[wParam-IDM_MMFCREATE].szMMFile);

    /* If file created, continue to map file. */
    if ((MMFiles[wParam-IDM_MMFCREATE].hFile =
           CreateFile (MMFiles[wParam-IDM_MMFCREATE].szMMFile,
                       GENERIC_WRITE | GENERIC_READ,
                       FILE_SHARE_WRITE,
                       NULL,
                       CREATE_ALWAYS,
                       FILE_ATTRIBUTE_TEMPORARY,
                       NULL)) != (HANDLE)INVALID_HANDLE_VALUE)
        goto MAP_FILE;
    }
    break;

case IDM_MMFCREATEEXIST:
    {
    char   szFilePath[MAX_PATH];
    OFSTRUCT   of;

    /* Get existing filename for mapfile. */
    *szFilePath = 0;
    if (!GetFileName (hWnd, szFilePath, "*"))
        break;

    /* If file opened, continue to map file. */
    if ((MMFiles[wParam-IDM_MMFCREATE].hFile =
            (HANDLE)OpenFile (szFilePath, &of, OF_READWRITE)) !=
                (HANDLE)HFILE_ERROR)
        goto MAP_FILE;
    }
    break;

case IDM_MMFCREATE:
    /* Associate shared memory file handle value. */
    MMFiles[wParam-IDM_MMFCREATE].hFile = (HANDLE)0xffffffff;

MAP_FILE:
    /* Create 20MB file mapping. */
    if (!(MMFiles[wParam-IDM_MMFCREATE].hMMFile =
        CreateFileMapping (MMFiles[wParam-IDM_MMFCREATE].hFile,
                           NULL,
                           PAGE_READWRITE,
                           0,
                           0x01400000,
                           NULL)))
        {
        ReportError (hWnd);
        if (MMFiles[wParam-IDM_MMFCREATE].hFile)
            {
            CloseHandle (MMFiles[wParam-IDM_MMFCREATE].hFile);
            MMFiles[wParam-IDM_MMFCREATE].hFile = NULL;
            }
        }
    break; /* from IDM_MMFCREATE */

In the sample code above, three cases are demonstrated. They represent creating a memory-mapped file by first creating a temporary disk file, creating a memory-mapped file from an existing file, and creating a memory-mapped file out of part of the system pagefile. In case IDM_MMFCREATENEW, a temporary file is created first, before the memory-mapped file. For case IDM_MMFCREATEEXIST, the File Open dialog is used to retrieve a filename, and that file is then opened before the memory-mapped file is created. In the third case, IDM_MMFCREATE, the memory-mapped file is created either using the system pagefile or using one of the standard files created in the two earlier cases.

Notice that the CreateFileMapping function need only be called once for all three different cases. The first parameter to the CreateFileMapping function, hFile, is used to supply the handle to the file that is to be memory-mapped. If the system pagefile is to be used, the value 0xFFFFFFFF must be specified instead. In the above examples, a structure is used to represent both the standard file and memory-mapped file information. In the example above, the hMMFile field in the structure MMFiles[wParam-IDM_MMFCREATE] is either 0xFFFFFFFF (its default value), or it is the value of the file handle retrieved in either of the earlier cases.

In all three cases, the memory-mapped file is specified to be 20 MB (0x01400000) in size, regardless of the size of any files created or opened for mapping. The fourth and fifth parameters, dwMaximumSizeHigh and dwMaximumSizeLow, are used to indicate the size of the file mapping. If these parameters indicate a specific size for the memory-mapped file when memory mapping a file other than the pagefile, the file on disk is fitted to this new size—whether larger or smaller makes no difference. As an alternative, when memory mapping a file on disk, you can set the size parameters to 0. In this case, the memory-mapped file will be the same size as the original disk file. When mapping a section of the pagefile, you must specify the size of the memory-mapped file.

The second parameter to the CreateFileMapping function, lpsa, is used to supply a pointer to a SECURITY_ATTRIBUTES structure. Since memory-mapped files are an object, they have the same security attributes that can be applied to every other object. A NULL value indicates that no security attributes are relevant to your use of the memory-mapped file.

The third parameter, fdwProtect, is used to indicate the type of protection to place on the entire memory-mapped file. You can use this parameter to protect the memory-mapped file from writes by specifying PAGE_READONLY or to permit read and write access with PAGE_READWRITE.

One other parameter of interest is the lpszMapName parameter, which can be used to give the MMF object a name. In order to open a handle to an existing file-mapping object, the object must be named. All that is required of the name is a simple string that is not already being used to identify another object in the system.

Obtaining a File-Mapping Object Handle

In order to map a view of a memory-mapped file, all you need is a valid handle to the MMF object. You can obtain a valid handle in one of several ways: by creating the object as described above, by opening the object with the OpenFileMapping function, by inheriting the object handle, or by duplicating the handle.

Opening a memory-mapped file object

To open a file-mapping object, the object must have been given a name during the creation of the object. A name uniquely identifies the object to this and other processes that wish to share the MMF object. The following portion of code from PORT.C shows how to open a file-mapping object by name.

/* Load name for file-mapping object. */
LoadString (hDLL, IDS_MAPFILENAME, szMapFileName, MAX_PATH);

/* After first process initializes, port data. */
if ((hMMFile = OpenFileMapping (FILE_MAP_WRITE, 
                                FALSE, 
                                szMapFileName)))
    /* Exit now since initialization was already performed by 
       another process. */
     return TRUE;

/* Retrieve path and file for ini file. */
if (!GetIniFile (hDLL, szIniFilePath))
    return FALSE;

/* Test for ini file existence and get length of file. */
if ((int)(hFile = (HANDLE)OpenFile (szIniFilePath, 
                                    &of, 
                                    OF_READ)) == -1)
    return FALSE;

else
    {
    nFileSize = GetFileSize (hFile, NULL);
    CloseHandle (hFile);
    }

/* Allocate a segment of the swap file for shared memory 2*Size 
   of ini file. */
if (!(hMMFile = CreateFileMapping ((HANDLE)0xFFFFFFFF,
                                    NULL,
                                    PAGE_READWRITE,
                                    0,
                                    nFileSize * 2,
                                    szMapFileName)))
    return FALSE;

The OpenFileMapping function requires only three arguments, the most important of these being the name of the object. As shown in the example, the name is simply a unique string. If the string is not unique to the system, the MMF object will not be created. Once the object exists, however, the name is guaranteed for the life of the object.

Also, note in the above example that the MMF object is opened first, possibly before the object has been created. This logic relies on the fact that, if the object does not already exist, the OpenFileMapping function will fail. This is useful in a DLL where the DLL's initialization code is called repeatedly, once for every process that attaches to it.

The sample from PORT.C above occurs in the DLL's initialization code that is called every time a DLL gets attached to another process. The first time it is called, the OpenFileMapping function fails because the object does not already exist. The logic, then, continues execution until it reaches the CreateFileMapping function, and it is there that the object is first created. Immediately after initially creating the object, the PortTool code initializes the data in the file mapping by writing porting-specific information to the memory-mapped file. To do this, the memory-mapped file is created with PAGE_READWRITE protection. All subsequent calls to the DLL's initialization function result in the OpenFileMapping function successfully returning a valid object handle. This way the DLL does not need to keep track of which process is the first to attach to the DLL.

Note that for every process that attaches to the DLL, the object name is retrieved from the same source—a string from the DLL's resource string table. Since the DLL is able to retrieve the object name from its own resource string table, the name is global to all processes, yet no process is actually aware of the name used. The DLL is able to effectively encapsulate this functionality while at the same time providing the benefit of shared memory to each process that attaches to the DLL.

The PortTool example presents a useful context for sharing memory. Yet, keep in mind that any file on disk could have been used in the same way. If an application were to implement some database services to several other applications, it could set up memory-mapped files using basic disk files, instead of the pagefile, and share that information in the same way. And as the first code listing illustrates, a temporary file could be used to share data instead of the pagefile.

Inheriting and duplicating memory-mapped file object handles

Ordinarily, for two processes to share a memory-mapped file, they must both be able to identify it by name. An exception to this is child processes, which can inherit their parent's handles. Most objects in Win32 can be explicitly targeted for inheritance or not. (Some objects are not inheritable, such as GDI object handles.) When creating an MMF object, a Boolean field in the optional SECURITY_ATTRIBUTES structure can be used to designate whether the handle is to be inheritable or not. If the MMF object handle is designated as inheritable, any child processes of the process that created the object can access the object through the same handle as their parent.

Literally, this means the child process can access the object by supplying the same handle value as the parent. Communicating that handle to the child process is another concern. The child process is still another process after all, having its own address space, so the handle variable itself is not transferable. Either some interprocess communication (IPC) mechanism or the command line can be used to communicate handle values to child processes.

Further, the DuplicateHandle function is provided to offer more control as to when handles can be inherited and not. This function can be used to create a duplicate handle of the original and can be used to change the inheritance state of the handle. An application can invoke this function to change an MMF object handle state to inheritable before passing the handle along to a child process, or it can do the opposite—it can take an inheritable handle and preserve it from being inherited.

Viewing Part of a Memory-Mapped File

Once obtained, the handle to the memory-mapped file object is used to map views of the file to your process's address space. Views can be mapped and unmapped at will while the MMF object exists. When a view of the file is mapped, system resources are finally allocated. A contiguous range of addresses, large enough to span the size of the file view, are now committed in your process's address space. Yet, even though the addresses have been committed for the file view, physical pages of memory are still only committed on a demand basis when using the memory. So, the only way to allocate a page of physical memory for a committed page of addresses in your memory-mapped file view is to generate a page fault for that page. This is done automatically the first time you read or write to any address in the page of memory.

To map a view of a memory-mapped file, use either the MapViewOfFile or the MapViewOfFileEx function. With both of these functions, a handle to a memory-mapped file object is a required parameter. The following example shows how the PortTool sample application implements this function.

/* Map a view of this file for writing. */
lpMMFile = (char *)MapViewOfFile (hMMFile, 
                                  FILE_MAP_WRITE, 
                                  0, 
                                  0, 
                                  0);

In this example, the entire file is mapped, so the final three parameters are less meaningful. The first parameter specifies the file-mapping object handle. The second parameter indicates the access mode for the view of the file. This can be FILE_MAP_READ, FILE_MAP_WRITE, or FILE_MAP_ALL_ACCESS, provided the protection on the file-mapping object permits it. If the object is created with PAGE_READWRITE protection, all of these access types are available. If, on the other hand, the file is created with PAGE_READONLY protection, the only access type available is FILE_MAP_READ. This allows the object creator control over how the object can be viewed.

The second and third parameters are used to indicate the low and high halves, respectively, of a 64-bit offset into the memory-mapped file. This offset from the start of the memory-mapped file is where the view is to begin. The final parameter indicates how much of the file is to be viewed. This parameter can be set to 0 to indicate that the entire file is to be mapped. In that case, the 64-bit offset value is ignored.

The function returns a pointer to the location in the process's address space where the file view has been mapped. This is an arbitrary location in your process, depending on where the contiguous range of addresses are available. If you want to map the file view to a specific set of addresses in your process, the MapViewOfFileEx function provides this capability. This function simply adds an additional parameter, lpvBase, to indicate the location in your process to map the view. The return value to MapViewOfFileEx is the same value as lpvBase if the function is successful; otherwise, it is NULL. Similarly, for MapViewOfFile the return value is NULL if the function fails.

Multiple views of the same file-mapping object can coexist and overlap each other as shown in Figure 5.

Figure 5. Memory-mapped file objects permit multiple, overlapped views of the file from one or more processes at the same time.

Notice that multiple views of a memory-mapped file can overlap, regardless of what process maps them. In a single process with overlapping views, you simply end up with two or more virtual addresses in a process that refer to the same location in physical memory. So, it's possible to have several PTEs referencing the same page frame. Remember, each page of a shared memory-mapped file is represented by only one physical page of memory. To view that page of memory, a process needs a page directory entry and page-table entry to reference the page frame.

There are two ways in which needing only one physical page of memory for a shared page benefits applications in the system. First, there is an obvious savings of resources because both processes share both the physical page of memory and the page of hard disk storage used to back the memory-mapped file. Second, there is only one set of data, so all views are always coherent with one another. This means that changes made to a page in the memory-mapped file via one process's view are automatically reflected in a common view of the memory-mapped file in another process. Essentially, Windows NT is not required to do any special bookkeeping to ensure the integrity of data to both applications.

Unmapping a View of a Memory-Mapped File

Once a view of the memory-mapped file has been mapped, the view can be unmapped at any time by calling the UnmapViewOfFile function. As you can see below, there is nothing tricky about this function. Simply supply the one parameter that indicates the base address, where the view of the file begins in your process

/* Load tokens for APIS section. */
LoadString (hDLL, IDS_PORTAPIS, szSection, MAX_PATH);
if (!LoadSection (szIniFilePath, 
                  szSection, 
                  PT_APIS, 
                  &nOffset, 
                  lpMMFile))
        {
        /* Clean up memory-mapped file. */
        UnmapViewOfFile (lpMMFile);
        CloseHandle (hMMFile);
        return FALSE;
        }

As mentioned above, you can have multiple views of the same memory-mapped file, and they can overlap. But what about mapping two identical views of the same memory-mapped file? After learning how to unmap a view of a file, you could come to the conclusion that it would not be possible to have two identical views in a single process because their base address would be the same, and you wouldn't be able to distinguish between them. This is not true. Remember that the base address returned by either the MapViewOfFile or the MapViewOfFileEx function is not the base address of the file view. Rather, it is the base address in your process where the view begins. So mapping two identical views of the same memory-mapped file will produce two views having different base addresses, but nonetheless identical views of the same portion of the memory-mapped file.

The point of this little exercise is to emphasize that every view of a single memory-mapped file object is always mapped to a unique range of addresses in the process. The base address will be different for each view. For that reason the base address of a mapped view is all that is required to unmap the view.

Flushing Views of Files

An important feature for memory-mapped files is the ability to write any changes to disk immediately if necessary. This feature is provided through the FlushViewOfFile function. Changes made to a memory-mapped file through a view of the file, other than the system pagefile, are automatically written to disk when the view is unmapped or when the file-mapping object is deleted. Yet, if an application needs to force the changes to be written immediately, FlushViewOfFile can be used for that purpose.

  /* Force changes to disk immediately. */
FlushViewOfFile (lpMMFile, nMMFileSize);

The example listed above flushes an entire file view to disk. In doing so, the system only writes the dirty pages to disk. Since the Windows NT virtual-memory manager automatically tracks changes made to pages, it is a simple matter for it to enumerate all dirty pages in a range of addresses, writing them to disk. The range of addresses is formed by taking the base address of the file view supplied by the first parameter to the FlushViewOfFile function as the starting point and extending to the size supplied by the second parameter, cbFlush. The only requirement is that the range be within the bounds of a single file view.

Releasing a Memory-Mapped File

Like most other objects in the Win32 subsystem, a memory-mapped file object is closed by calling the CloseHandle function. It is not necessary to unmap all views of the memory-mapped file before closing the object. As mentioned above, dirty pages are written to disk before the object is freed. To close a memory-mapped file, call the CloseHandle function, which supplies the memory-mapped file object handle for the function parameter.

/* Close memory-mapped file. */
CloseHandle (hMMFile);

It is worth noting that closing a memory-mapped file does nothing more than free the object. If the memory-mapped file represents a file on disk, the file must still be closed using standard file I/O functions. Also, if you create a temporary file explicitly for use as a memory-mapped file as in the initial ProcessWalker example, you are responsible for removing the temporary file yourself. To illustrate what the entire cleanup process may look like, consider the following example from the ProcessWalker sample application.

case IDM_MMFFREE:
case IDM_MMFFREENEW:
case IDM_MMFFREEEXIST:
    {
    HCURSOR    hOldCursor;
    OFSTRUCT   of;

    /* Put hourglass cursor up. */
    hOldCursor = (HCURSOR)SetClassLong (hWnd, GCL_HCURSOR, 0);
    SetCursor (LoadCursor (0, IDC_WAIT));

    /* Release memory-mapped file and associated file if any. */
    CloseHandle (MMFiles[wParam-IDM_MMFFREE].hMMFile);
    MMFiles[wParam-IDM_MMFFREE].hMMFile = NULL;

    if (MMFiles[wParam-IDM_MMFFREE].hFile)
        {
        CloseHandle (MMFiles[wParam-IDM_MMFFREE].hFile);
        MMFiles[wParam-IDM_MMFFREE].hFile = NULL;
        }

    /* If temporary file, delete here. */
    if (wParam == IDM_MMFFREENEW)
        {
        OpenFile (MMFiles[wParam-IDM_MMFFREE].szMMFile, 
                  &of, 
                  OF_DELETE);
        *(MMFiles[wParam-IDM_MMFFREE].szMMFile) = 0;
        }

    /* Replace wait cursor with old cursor. */
    SetClassLong (hWnd, GCL_HCURSOR, (LONG)hOldCursor);
    SetCursor (hOldCursor);
    }
    break;

In this example, the memory-mapped file can be one of three types: the system pagefile, a temporary file, or an existing file on disk. If the file is the system pagefile, the memory-mapped file object is simply closed, and no additional cleanup is necessary. If the memory-mapped file is mapped from an existing file, that file is closed right after closing the memory-mapped file. If the memory-mapped file is a mapping of a temporary file, it is no longer needed and is deleted using standard file I/O immediately after closing the temporary file handle, which cannot occur until after closing the memory-mapped file object handle.

Conclusion

Memory-mapped files provide unique methods for managing memory in the Win32 application programming interface. They permit an application to map its virtual address space directly to a file on disk. Once a file has been memory-mapped, accessing its content is reduced to dereferencing a pointer.

A memory-mapped file can also be mapped by more than one application simultaneously. This represents the only mechanism for two or more processes to directly share data in Windows NT. With memory-mapped files, processes can map a common file or portion of a file to unique locations in their own address space. This technique preserves the integrity of private address spaces for all processes in Windows NT.

Memory-mapped files are also useful for manipulating large files. Since creating a memory mapping file consumes few physical resources, extremely large files can be opened by a process and have little impact on the system. Then, smaller portions of the file called "views" can be mapped into the process's address space just before performing I/O.

There are many techniques for managing memory in applications for Win32. Whether you need the benefits of memory sharing or simply wish to manage virtual memory backed by a file on disk, memory-mapped file functions offer the support you need.

반응형

DLL Win32


[In-Process 방식]
내가 System part에 속하지 않아서 Process model을 설계하는 것에 대해 그다지 관심을 두지 않고 있지만,
지금 System 팀에서 아직 Process model의 architecture 를 설계 방향을 잡지 못하고 있다.
음, 아무래도 DLL 과 COM 의 구현 등에 대해서 래퍼런스가 될만한 프로세스 모델을 찾고,
좀더 정확하게 DLL의 동작및 메모리 구조 등에 대해서 더 공부를 해봐야 할듯하다.


- DLL Win32 의 경우 해당 DLL이 로드된 물리적인 메모리 영역이 해당 DLL을 로드한 어플리케이션의 프로세스 주소 영역게 메핑된다. 즉, Win32 DLL 은 자신을 로드한 어플리케이션의 일부가 된다.
따라서 DLL 함수의 매개변수에 포인터가 넘어올때 DLL함수가 이 포인터를 통하여 접근할 수 있는 주소 영역을 완전히 유효하다.


http://msdn.microsoft.com/en-us/library/ms686958(VS.85).aspx
Using Shared Memory in a Dynamic-Link Library

The following example demonstrates how the DLL entry-point function can use a file-mapping object to set up memory that can be shared by processes that load the DLL. The shared DLL memory persists only as long as the DLL is loaded. Applications can use the SetSharedMem and GetSharedMem functions to access the shared memory.

DLL that Implements the Shared Memory

The example uses file mapping to map a block of named shared memory into the virtual address space of each process that loads the DLL. To do this, the entry-point function must:

  1. Call the CreateFileMapping function to get a handle to a file-mapping object. The first process that loads the DLL creates the file-mapping object. Subsequent processes open a handle to the existing object. For more information, see Creating a File-Mapping Object.
  2. Call the MapViewOfFile function to map a view into the virtual address space. This enables the process to access the shared memory. For more information, see Creating a File View.

Note that while you can specify default security attributes by passing in a NULL value for the lpAttributes parameter of CreateFileMapping, you may choose to use a SECURITY_ATTRIBUTES structure to provide additional security.


[MSDN 참고 내용]

Visual C++
DLL의 데이터를 응용 프로그램 또는 다른 DLL과 공유하려면 어떻게 합니까?

Win32 DLL은 호출 프로세스의 주소 공간에 매핑됩니다. 기본적으로 DLL을 사용하는 각 프로세스에는 모든 DLL의 전역 및 정적 변수에 대한 자체 인스턴스가 있습니다. DLL이 다른 응용 프로그램이 로드한 DLL의 다른 인스턴스와 데이터를 공유해야 할 경우 다음 방법 중 하나를 사용할 수 있습니다.

  • data_seg pragma를 사용하여 명명된 데이터 섹션을 만듭니다.

  • 메모리 매핑 파일을 사용합니다. 메모리 매핑 파일에 대한 Win32 설명서를 참조하십시오.

다음은 data_seg pragma 사용 예입니다.

#pragma data_seg (".myseg")
   int i = 0; 
   char a[32]n = "hello world";
#pragma data_seg()

data_seg를 사용하여 새로운 명명된 섹션(이 예제에서는 .myseg)을 만들 수 있습니다. 일반적인 사용 방법은 명확하게 데이터 세그먼트 .shared를 호출하는 것입니다. /SECTION:.MYSEC,RWS 링커 옵션을 사용하거나 .def 파일의 새로운 명명된 데이터 섹션에 대해 올바른 공유 특성을 지정해야 합니다.

공유 데이터 세그먼트를 사용하기 전에 몇 가지 제한 사항을 고려해야 합니다.

  • 공유 데이터 세그먼트의 변수는 정적으로 초기화되어야 합니다. 위의 예제에서 i는 0으로 초기화되며 a는 hello world로 초기화된 32문자입니다.

  • 모든 공유 변수는 지정된 데이터 세그먼트의 컴파일된 DLL에 있어야 합니다. 배열이 크면 DLL도 커집니다. 초기화된 모든 전역 변수에 대해 해당됩니다.

  • 프로세스 관련 정보를 공유 데이터 세그먼트에 저장하지 마십시오. 대부분의 Win32 데이터 구조나 값(예: HANDLE)은 단일 프로세스의 컨텍스트 내에서만 유효합니다.

  • 각 프로세스는 자체 주소 공간을 얻습니다. 공유 데이터 세그먼트에 포함된 변수에는 포인터가 저장되지 않는다는 점에 주목할 필요가 있습니다. 포인터는 한 응용 프로그램에서는 절대적으로 유효하지만 다른 응용 프로그램에서는 그렇지 않습니다.

  • 각 프로세스의 가상 주소 공간 내의 다른 주소로 DLL 자체가 로드될 수 있습니다. DLL이나 다른 공유 변수에서 함수에 대한 포인터를 사용하는 것은 안전하지 않습니다.

뒤의 세 가지 항목은 메모리 매핑된 파일 및 공유 데이터 세그먼트에 적용됩니다.

메모리 매핑된 파일의 시작 위치를 알 수 없으므로 메모리 매핑된 파일이 공유 데이터 섹션보다 유용합니다. 개발자는 공유 메모리 내부에 있는 모든 데이터에서 "공유 메모리 섹션의 시작 위치를 기준으로 하는 오프셋"을 사용하여 포인터와 유사한 동작을 구현할 수 있습니다. 빠르고도 간편한 작업을 위해 __based 포인터가 강력히 권장됩니다. 그러나 기준(또는 메모리 매핑된 파일의 시작 위치)은 각 프로세스마다 달라질 수 있으므로 __based 포인터의 기준을 저장하는 변수 자체는 공유 메모리에 있을 수 없습니다.

이러한 제한이 C++ 클래스에서는 중요한 의미를 갖습니다.

  • 가상 함수를 사용하는 클래스에는 항상 함수 포인터가 있습니다. 가상 함수를 사용하는 함수는 공유 데이터 세그먼트에 저장될 수 없으며 메모리 매핑된 파일에도 저장될 수 없습니다. 이 점은 MFC 클래스나 MFC에서 상속된 클래스에게 중요합니다.

  • 통계 데이터 멤버는 전역 변수와 동등한 변수로 구현됩니다. 따라서 각 프로세스에는 해당 클래스의 정적 데이터 멤버에 대한 자체 복사본이 있습니다. 정적 데이터 멤버를 사용하는 클래스는 공유될 수 없습니다.

  • 공유 데이터 세그먼트의 초기화 요구 사항으로 인해 C++ 클래스에서 특별한 문제가 제기됩니다. 공유 데이터 세그먼트에 CTest Counter(0); 같은 문이 있으면 Counter 개체는 DLL을 로드하는 각 프로세스에서 초기화되어 매번 개체 데이터가 0으로 됩니다. 링커가 DLL을 만들 때 초기화하는 내장 데이터 형식과는 다릅니다.

이러한 제한 때문에 프로세스 간에 C++ 개체를 공유하는 것은 바람직하지 않습니다. 일반적으로 C++를 사용하여 프로세스 간에 데이터를 공유하려면, 데이터를 공유하기 위해 내부적으로 메모리 매핑된 파일을 사용하는 클래스를 작성하되 클래스 인스턴스는 공유하지 마십시오. 이런 클래스를 개발할 때는 신중해야 하지만 이런 방식으로 응용 프로그램 개발자는 데이터 공유에 의한 부작용을 완벽하게 제어할 수 있습니다.

명명된 데이터 섹션을 만드는 방법에 대한 자세한 내용은 http://support.microsoft.com의 기술 자료 문서를 참조하십시오.

  • "How to Share Data Between Different Mappings of a DLL"(Q125677)

  • "Specifying Shared and Nonshared Data in a DLL"(Q100634)

  • "Sharing All Data in a DLL"(Q109619)

  • "Memory in Shared Code Sections Is Not Shared Across Terminal Server Sessions"(Q251045)


반응형
BOOL DoModal()
{
       :
       :
     while(!SomethingFinished()) // message loop
    {
        if(GetMessage(&msg,NULL,0,0)) // wait message
        {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
         }

         else
        {
              WM_QUIT 처리 루틴
               CancelSomething();
               PostQuitMessage(msg,wParam);  // WM_QUIT 을 다시 message loop 에 넣는 구문.
               fReturn = FALSE;
               break;
        }
     }
   return fReturn;
}
반응형

폰 바꾸려고 생각중인데..눈에 확 들어오는 모델이 있어서 조사 착수중입니다. ㅎㅎ
물론 옆에서 추천도 있었고 해서!!!

아래 리뷰


 

전 skt m480을 사용하고 있는 유저입니다.

먼저 미라지에서는 이메일 기능이 가장 쓰기 쉽고 편한 것은 사실입니다. 아웃룩을 비롯한 오피스프로그램들을 기본 인스톨되어 있기 때문에 별다른 설정없이도 사용가능하구요. 그 담으로 유용한 기능이라고 하면 역시 아웃룩을 이용한 일정관리,주소록관리 입니다. 특히 밖에서 활동이 많으신 분들에게는 아주 도움이 되죠.

 

그 담으로는 모바일뱅킹이나 HTS(주식거래) 기능이 가능하구요(일부 은행, 증권에 해당)

mp3 플레이어나 동영상플레이어로도 사용하기 어렵지 않습니다. (외장메모리8기가 지원)

메신져도 쓸수 있구요(qwerty자판이라 더 편하죠)

블루투스 헤드셋이나 컴퓨터와 연동해서 스피커폰처럼 사용할수도 있습니다.

그리고 GPS가 내장되어 있는지라 맵과 연동해서 위치정보등을 확인할 수 있구요,

 

아무래도 스마트폰인 미라지가 다른 폰들과 다른 점은 직접 설정하고 튜닝하는 재미가 아닐까 싶네요.

기본적으로 윈도우 모바일이 운영체제이기 때문에 PC와 자료교환이 쉽다는 것이 실질적으로 가장 큰 메리트이구요.

 

다음 서비스분야 신지식 답변

다음 서포터즈(http://cafe.daum.net/search-changeup)

---------------------------------------------------

흠!!! 오피스가 된다는 예기군!!!<-- 메모 메모!!


p.s '미라지'가요 skt 랑 ktf 로 통신사가 나눠어 있는데요 ktf가 늦게 출시해서 사용자들의 불만사항을 다 반영했어요.

그러니 ktf 걸로 사는 것이 좋을 것 같아요.! 

ㅋㅋ 결국 내가 사려는 것은 다 되어있다는 거네... KTF살꺼니까. ㅎㅎ<---




정신을 차리고 보니 어느새 내 손엔 M4800이 쥐어져있었다. =_=

PDA폰이니까 늘 잘 쓰고 있던 G300같은 PDA랑 같겠지 생각했지만 해상도가 변태(!) 해상도다 보니 일반적인 PDA 프로그램 설치하려고 해도 제약이 있고 뭐 이래저래 복잡하네. realVGA는 뭐고 언락은 뭐야.

무엇보다 듣던 소문대로 배터리가 너무 빨리 소모되는 느낌이다. 음-

내일 MicroSDHC가 오니까 장착하면 하나 두개씩 필요한 프로그램 깔아봐야지.

아.. 정말 금년엔 너무 지른다. 옴니아폰 나왔으면 옴니아폰을 질렀을지도 몰라;

정말 정말 이젠 끝. 끝. 끝. 끝. 용돈 개털이다.

흨.............. 안좋은 예기도 있군요 ㅠ_ㅠ



반응형

1. Modal / Transient , 의 개념

어플리케이션의  코드 실행 중간에, 사용자의 피드백(interaction)을 유도하는 대화상자를 띄워 사용자가 선택을 하고 대화상자가 닫힐 때까지, 코드 흐름이 멈춰 있도록 할 수 있는 기능을 말합니다.

이런 기능은 특정 상황에서 매우 유용하고, 편리한 코드 작성을 할 수 있도록 도와줍니다.

예를 들면아래와 같은 플로우(flow) 작성하고 싶다고 가정 합니다.

1. 프로그램 실행
2. copy button 선택
3. copy 할 것인지 묻는 message box 를 띄움  (Do you wanna start copy?)
4. 사용자가 OK를 누르면, copy를 실행함.

Platform을 개발하면서 Windows 에서 제공하는 Modal 과 같은 기능을 어떤 방식으로 지원할까에 대해서 여러차례 고민 해봤습니다.

실제로 기능 자체의 구현은 어려운 것이 아니나, 예외 상황에 대한 처리가 항상 문제가 되더군요.


가장 기본적인 예외 상황은 UI 상 modal 과 Code상으로 Modal 상태에서 parent window가 종료된다거나, modal을 실행한 주체가 종료된다면, messagebox를 띄워서 기다리고 있는 코드의 다음 라인들은 어떻게 처리되어야 하는 가 입니다.

이 외에도 여러가지 예외상황을 만들때가 많습니다.이는 마치, multi-thread programming 과 비슷한 양상을 나타냅니다.

그래서 아래 코드의 pWind->DoModal()의 앞 과 뒤에서 내부 변수나 외부 변수들 그리고 객체의 상태가 달라질 수 있다는 의미 입니다.

즉 MyTestWindow 가 삭제되었거나 그 내부변수들의 정보가 바뀌어있을 수도 있다는 의미죠.

 

void MyTestWindow::KeyPressed() {
  HFile hDBCopyLog = CreateFiile("Log.txt");
  pWind = CreateModalMessageBox("Do you wanna start copy?");
  returnValue = pWind->DoModal(); //<-- -3. Modal window가 뜨고 코드상으로 아래로 진행되지 않고 멈춰 있는 부분 
           if (returnValue == OK) {
    CopyFile(pathDest, pathSrc);
    sprintft(testBuf, "handle=%d", this->m_hWin); //< --B.this와 this->m_hWin 이 정상적이다라는 보장이 없음 
    WriteFile( hDBCopyLog, testBuf);
    CloseFile(hDBCopyLog);
    return TRUE;
  }
 

만약 위와 같은 코드가 있다고 한다면, DoModal() 내부에서 분명 event 처리를 할수 있는 event loop 이 구현 되어있을 것입니다.
이 이벤트 loop에서 pWind를 가지고 있는 application 이나 window를 종료 했다면 User 입력을 기대하고 작성했던 OK일때 처리 부분이 수행이 안되게 될 수도 있습니다.
또 더 심각하게 본다면 this 나 m_hWin 도 정상적으로 살아있을것이라는 보장도 못하게 됩니다.


2. Modal 기능 구현

DoModal 기능은 의외로 간단한데요.

아래와 같이 platform에서 제공하는 event를 처리loop 코드를 참조하여 event queue 처리 로직을 loop로 제공하면 됩니다.

 

result MessageBox::DoModal() {
  while (!bEixt) {
    ReceiveMessage(&msg);
    ProcessMsg(&msg);
  }
}

WIN32 기준으로 봤을때...
Window들을 모두 종료 시키고 어플리케이션을 빠져 나가야 하는 경우라고 가정한다면,

 

WM_QUIT 메세지를 받게 되면, WM_QUIT에 의해 modal window가 삭제 될것 입니다. 그러나 parent window는 삭제가 되지 않기 때문에, WM_QUIT을 다시 한번 event queue에 넣어야 합니다.

이렇게 하면 Modal loop을 빠져 나가고,WM_QUIT 메세지를 재 생성해서(PostQuitMessage) 바깥에 있는 main loop에서 정리할 수 있도록 해줘야 합니다.

result MessageBox::DoModal() {
  while (!bEixt) {
    ReceiveMessage(&msg);
    if (msg.type == WM_QUIT) {
      bExit = TRUE;
      PostMessageFront(&msg);
      bException = TRUE;
    }
    if (!bException)
      ProcessMsg(&msg);
  }
}


platform 을 개발하는 입장에서   Modal을 구현하여 제공 해야 한다면 위와 비슷한 방법으로 application 종료 event를 받으면 우선 이 이벤트를 부모 window부터 처리하는 것이 아니라 modal window에서 먼저 처리를 하고 , modal window를 종료 시키고, 같은 event를 다시 event queue에 넣어서 modal event loop을 빠져 나간 다음에 main loop(또는 상위 event loop) 에서 처리할 수 있도록 합니다.

modal이 한개가 아니라 중첩, 3중첩 되어 있을 가능 성도 있기 때문입니다.

 

 

1. 프로그램 실행
2. delete button 선택
3. delete 할 것인지 묻는 message box 를 띄움  (Do you wanna delete this file?)
4. 진짜로 지울 것인지 묻는 message box 를 띄움  (리얼리?)
5 파일을 지움

 

 

반응형

[레이몬드 첸의 윈도우 개발 282 스토리 ]
the old new thing: Practical Development Throughout the Evolution of Windows


Inhouse 방식의 Platform을 Open platform으로 변경하는 프로젝트를 진행하려면, 여러가지 고려해야 할 사항들이 많겠죠?

이런 저런 자료들을 찾다 우연찮게 "윈도우 개발 282 스토리" 라는 책을 보게 되었습니다.

순전히 우연히 서점가서 발견한거임 ㅡㅡ;; 참 운도 좋지!!

안그래도 요즘 "아!!! 도대체 상용 플랫폼을 개발한 사람들은 뭘 어떻게 개발 했을까?" 하는 고민 중이었는데 이런 책을 구하게 되어 내용을 살펴보면서 "그렇지!!!","그래 맞아" 요런 공감대 어린 감탄사들을 연발하고 있습니다.


어떤 내용이 주인지 궁금하다구요???  "사서 보세요~"
그래도 이렇게 오늘의 블로그 페이지를 마무리 하면 아쉬우니까 몇가지 간단한 소개를 해 드리죠!!


컴퓨터를 끄기 위해 왜 시작 버튼을 눌러야만 하는가?  --> 시작 버튼의 유래와 유용성 설명
    초창기 Window95가 나왔을때는 '시작' 버튼이 없고 system 이라는 이름의 메뉴 버튼으로 이뤄 졌었는데, 사용자들은 테스트 결과 부팅후에 뭘해야 할지 모르는 사용자들이 대부분 이었다는 것입니다.
그때 누군가가 System 메뉴 버튼에 "시작" 이라는 이름을 붙이자라고 제안하했고, 이 '시작'이라는 문구가 유용성 테스트 결과를 극적으로 개선한 케이스가 되었다고 합니다.
모든 대화상자에서 기본 응답은 '취소' 이다.  --> 사용자는 결국 취소를 선택한다는 내용
대화상자의 목적은 S/W가 판단하기 힘든 경우 사용자에게 정확한 답변을 받기 위해서 사용하였는데 ,
문제는 사용자에게 어려운 질문이나 전문적인 용어의 dialog화면을 보여주면 사용자는 잠시 그 화면을 쳐다보다가 원래 그화면이 뜨기 전으로 돌려놓으려고 한다는 것을 희화했다.
GlobalAlloc 함수의 이력
UI 모달성과 코드 모달성   --> Modal 구현과 관련된 히스토리와 기술 설명
WHQL 드라이버 인증 절차 속이기 --> driver 개발자들의 만행(?)에 대한 에피소드
과거 호환성
유니코드
다중모니터 지원--> 다중 모니터를 지원하게 된 계기



반응형


SPH-B2300 USB driver






폰 카메라에 있던 내용들을 PC로 backup 하려고 Anycall 에서 download 받은 내용.
반응형


블로그를 어디에 개설해서 사용할까 고민을 하다 네이버에서 시작을 했었는데...
(회사에도 블로그가 있어서 거기도 써봤는데.... 별루 재미가 없음..)

올린 글도 몇개 안되고.. 워낙 또 내가 관심있는 부분만 글로 쓰니 .. 써놓고도 재미없고...

한달쯤 넘었을까..

옆에 "이승원 책임"이 TiStory를 알려줘서 이쪽으로 이사하게되었죠!!

근데 블로그 이사.. 이거 장난아니군요...

이사를 해놓고 나니 글들이 다 오늘날짜!!! ㅡㅡ;;;

역시 블로그 이사는 해놓고 나니 이것저것 후회되는게 많다는걸 새삼 느끼게 되네요.

티 스토리에서는 오랜시간 보낼수 있도록 관리를 잘해야 겠다는 각오도 다시 하게 되었답니다.!!!




+ Recent posts