CSDN博客

img tiaohh

如何获取DLL,EXE中的图标

发表于2004/9/28 12:10:00  1671人阅读



http://www.kennyandkarin.com/Kenny/CodeCorner/Tools/IconBrowser/

 

Icon Browser: An Exercise in Resource Management

By Kenny Kerr, November 2001

Summary Info

Prerequisites

This article assumes you’re familiar with C++, C#, and .NET programming.

Article Summary

The System.Runtime.InteropServices namespace contains many useful classes, structures and attributes that you can use to access your legacy libraries and resources. The .NET Framework as a whole also provides many services for migrating existing code and resources to managed code. This article explains how to use these services to extract resources from Win32 resource files as well as how to write icon resources to .ICO files. It also covers many of the issues surrounding resource management and interoperating with unmanaged code, memory and binary formats.

Resources

.NET Framework System.Runtime.InteropServices Namespace
http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemRuntimeInteropServices.asp

Platform SDK Documentation for Resource Functions
http://msdn.microsoft.com/library/en-us/winui/resource_0jsj.asp

Icons in Win32
http://msdn.microsoft.com/library/en-us/dnwui/html/msdn_icons.asp

Downloads

Kerr.IconBrowser.exe - The executable for the Icon Browser application. You need the .NET Framework SDK Release Candidate installed to run it.

Source Code - The full source code for the application. Ideally you would open this in Visual Studio .NET Release Candidate, but you can also build this using just the SDK (with a bit more effort).

Introduction

Every so often I find myself needing to extract an icon from an executable or dynamic-link library (DLL). And every time I end up bending over backwards to get this done, especially if the library contains hundreds of icons and I have to sift through all of them until I find the one I want. So I decided to write a little program to make the process of extracting an icon from a library much easier. Well it started out simple. I wrote the Icon Browser application in C# using the .NET Framework and in about 45 minutes I had a really cool icon viewer. Then I attempted to implement the Export feature that would allow the user to choose any icon in the library and save it to an external .ICO file. I hit a brick wall. It turns out there’s no simple way to save an icon to a file.

In this article I present the Icon Browser application. It is a small desktop application that allows you to browse through the icons embedded in executables and DLLs. It also allows you to save any of the icons to a .ICO file. I will use this application as a vehicle for exploring programming tasks such as reading and writing unmanaged resources and other issues related to porting or coexisting with legacy code and binaries. In addition, I’ll look at taking C++ classes employing traditional deterministic finalization and show you how to implement them as C# classes, ensuring that they behave well in the .NET environment.

Icon Browser

Before we get into the programming details let me quickly introduce the Icon Browser and show you how you can use it. Even if you’re not too interested in resource management and interoperating with unmanaged code from .NET, you might find this application useful. Icon Browser is very easy to use (see Figure 1). You can type a file name in the edit box and click the Go button and Icon Browser will search the given file and display any icons it finds in the list view. If you’re not sure about the name or location of the file you can click the Browse button and search for a file using the familiar ‘Choose a File’ dialog box. Once the icons have been loaded into Icon Browser you can choose any of the icons and click the Export Icon button to save the icon to a .ICO file. You will be presented with a ‘Save Icon As’ dialog box so that you can indicate where to save the new .ICO file. I’ve also added a handy context menu to the list view so that you can just right click on an icon and select the Export Icon command.

Figure 1 - The Icon Browser Application

If you are following along with me on your own computer you will notice that there is a default file name in the Save Icon As dialog box and it’s probably a number. You might be wondering what this number is. The name or number is what is known as the resource name for the icon in the library. It’s just a unique way to identify a resource in a library. The resource name is also used as the title for each of the icons that are displayed in Icon Browser (see Figure 1).

And that’s the Icon Browser application, simple but effective! The application as well as its source code is available with the download for this article.

What Are Icons Anyway?

Most people think of icons as the little images that represent documents and other files in the Windows shell. Technically, an icon is a group of icon images. An icon image resembles that of a device-independent bitmap. So an icon can be represented by a number of different images tailored for different display configurations. In fact, when designing icons for Windows XP you are encouraged to include 9 different images. Figure 2 shows the images for Microsoft Notepad in GIF Movie Gear, which is a tool you can use to assemble icon images and save to a .ICO file.

Figure 2 - Notepad Icon Images

When using an icon you will typically only deal with a handle to the icon and not the actual icon resource. In C or C++ you will use the HICON, which is simply an abstract handle to the icon and pass it to the various Win32 API calls. This is the traditional way that resources are managed in C. In the managed world of .NET you will use the Icon class.

Loading Icons, So Many Choices!

There are many different ways to load an icon. Two of the most common functions you can use are ExtractIcon and LoadIcon. ExtractIcon will return an HICON given a file name and the zero-based index of the icon in the file. This is useful if you just want to enumerate all or some of the icons in a file. LoadIcon is a bit more useful in that you can identify an icon based on its resource name or identifier. In .NET you don’t generally load icons from unmanaged executables or DLLs. Instead you will normally use the ResourceManager class to load resources from resource files as defined by the .NET Framework.

So how do I read icons from unmanaged libraries? Read on.

Platform Invoke

Platform Invoke, which is more commonly referred to as P/Invoke, is the bridge between the managed world of .NET and unmanaged code like the Win32 APIs. P/Invoke is part of the .NET Framework’s extensive interoperability layer. To call an unmanaged function you need to use the DllImport attribute to identify the function and the DLL that implements it. Here is the declaration for the LoadIcon API.

// Original declared in: winuser.h
// HICON LoadIcon(HINSTANCE hInstance,
//                          PCTSTR pName);

[DllImport("User32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern IntPtr LoadIcon(IntPtr hInstance,
                                                   IntPtr pName);

“User32.dll” is the single parameter to the DllImportAttribute instance constructor. It identifies the DLL implementing the unmanaged function. SetLastError and CharSet are public fields for the DllImportAttribute class.

Setting the SetLastError field to true informs the Common Language Runtime (CLR) that the unmanaged function may call the SetLastError Win32 API function. In response the CLR will cache the value returned by the GetLastError Win32 API function so that you can retrieve it later. This is to avoid race conditions, as it is possible for another function used by the CLR to call SetLastError before you get around to retrieving the error code. The .NET Framework exposes the cached error code through the GetLastWin32Error static method on the Marshal class. So it is a good idea to set this field if you suspect your unmanaged functions may set the last error code and you are interested in retrieving it.

Setting the CharSet field to CharSet.Auto indicates that the unmanaged function has a Unicode and an Ansi version. Most Win32 API functions that deal in any way with strings qualify. For example, there doesn’t actually exist a function named LoadIcon. It is actually #define’d to LoadIconW if UNICODE is defined or LoadIconA if it is not. The –W, or Unicode, versions of the functions were introduced with Windows NT as Windows NT and it successors Windows 2000 and XP deal natively with Unicode strings. Windows 9x and its successor Windows ME mostly only support the –A, or Ansi, versions. Setting CharSet to CharSet.Auto instructs the CLR to pick the right version based on the operating system that the code is running on. If you don’t explicitly set the CharSet field it defaults to CharSet.Ansi, which won’t give you the best performance on Window XP as the operating system will have to convert any strings from Ansi to Unicode before it can use them.

The next thing you’ll notice is that I’ve changed the return type and the parameter types to the IntPtr class. You will normally use IntPtr for opaque unmanaged types that can’t be represented by corresponding managed types. Although pName is a PCTSTR, which is a constant pointer to a TCHAR (in other words a string), it is more commonly used to pass an integer identifier using the MAKEINTRESOURCE marcro. For this reason pName is also declared as an IntPtr.

LoadIcon, as with many of the resource management functions, requires an instance handle. If you’re programming in C++ then this is no problem. The operating system will assign your process an instance handle when it first starts up. And if you need to get resources from another DLL or executable it’s a simple matter of calling LoadLibraryEx and passing the LOAD_LIBRARY_AS_DATAFILE flag to indicate that you just want access to the resources within the library and don’t want to execute any code it may contain. Here’s an example of using the LoadLibraryEx function to load the Windows Explorer shell executable.

HMODULE hModule = LoadLibraryEx(_T(“C://Windows//Explorer.exe”),
                                                    0, // must be zero.
                                                    LOAD_LIBRARY_AS_DATAFILE);

Although LoadLibraryEx returns a module handle, it is generally interchangeable with an instance handle so you can freely pass it to functions that expect a HINSTANCE. In fact, a HMODULE is typedef’d to be a HINSTANCE in windef.h. In the world of .NET however, you don’t deal with instance handles but rather with assemblies. An assembly, which is usually packaged as an executable or DLL, can be loaded using the Assembly.LoadFrom method as shown here.

Assembly assembly = Assembly.LoadFrom(“C://Kerr.Resources.dll”);

The Assembly class contains a host of properties and methods for accessing the various types defined in an assembly and can be used with the classes in the System.Resources namespace to retrieve any resources.

Resource Management

Lets get back to the problem at hand. We want to retrieve resources from unmanaged libraries. To do this we need to declare a few of the Win32 API functions for use in .NET. I tend to put all my declarations in a single class that I call WindowsAPI. This way I can check at a glance what dependencies my project may have to ensure that they are available on any platforms that I support. You can download the WindowsAPI class along with the source code for this article. Now because resource management is so important (yes, even when using .NET), you should avoid using these functions directly as your code will end up getting quite messy as you try to free the various resources when you’re done using them. Remember that you must always write your code to be resilient to exceptions. The best way to do this is to wrap any resource management functions inside a class. If you are a C++ programmer your first pass at writing a C# class to wrap the library functions might look something like this, with error handling code omitted for brevity (see Figure 3).

Figure 3 - Unreliable Library Class

public class Library
{
    public Library(string strFileName,
                         ulong ulFlags)
    {
        m_hModule = WindowsAPI.LoadLibraryEx(strFileName,
                                                                     IntPtr.Zero,
                                                                     UlFlags);
    }

    ~Library()
    {
        WindowsAPI.FreeLibrary(m_hModule);
    }

    private IntPtr m_hModule;
}

This is perfectly acceptable from a C++ point of view and it will even compile and work in C#. There’s only one problem. The .NET Framework uses non-deterministic finalization. What this means is that the .NET Framework’s garbage collector will call your object’s destructor (called a finalizer in .NET speak) in a non-deterministic fashion, most likely when it notices you’re low on memory. So unlike in C++ where your destructor is guaranteed to be called when execution leaves the scope in which the object was created, in .NET you cannot rely on a destructor to free your resources in such a timely fashion. Fortunately .NET defines a pattern for resource management, which if followed consistently makes resource management a breeze. The .NET Framework defines the IDisposable interface, which has a single method called Dispose. When used in conjunction with the C# using statement, it provides precise control over when your class will be disposed. So lets rewrite the Library class to make use of the IDisposable interface, again with error handling omitted for brevity (see Figure 4).

Figure 4 - Revised Library Class

public class Library : IDisposable
{
    public Library(string strFileName,
                        ulong ulFlags)
    {
        m_hModule = WindowsAPI.LoadLibraryEx(strFileName,
                                                                     IntPtr.Zero,
                                                                     UlFlags);
    }

    ~Library()
    {
        Free();
    }

    public void Dispose()
    {
        Free();
    }

    public void Free()
    {
        if (IntPtr.Zero != m_hModule)
        {
            WindowsAPI.FreeLibrary(m_hModule);
        }

        m_hModule = IntPtr.Zero;
    }

    private IntPtr m_hModule;
}

You’ll notice that I still have a destructor. This is just a safeguard in case the user of my class forgets to employ the using statement. In this case the library will still get freed, eventually. My new Library class can now be used as follows.

using (Library library = new Library(@”C:/Windows/Explorer.exe”,
                                                    WindowsAPI.LOAD_LIBRARY_AS_DATAFILE))
{
    // use the library
}

Before execution leaves the using scope the library will be freed. And that’s the basics of resource management in C#. Next we’ll take a look at extracting the icon resources from the library.

So Where Are The Icons?

Now that we know how to load an unmanaged library we need to determine whether a library contains any icons. As I mentioned before, you can use the LoadIcon function to load an icon from a library as long as you have the name or identifier for the icon resource. If you don’t know the name of a particular icon you can enumerate all the icons until you find the one you want. This is done using the EnumResourceNames function, which is declared as follows.

BOOL EnumResourceNames(HMODULE hModule,
                                         PCTSTR pType,
                                         ENUMRESNAMEPROC pCallback,
                                         LONG_PTR lParam);

The hModule parameter is a module handle, which you can get by calling LoadLibraryEx, so I can use my Library class for this. The pType parameter identifies the type of resource. Although you can define your own resource types, you will generally use one of the standard resource types. Two resource types that are of interest in this discussion are RT_GROUP_ICON and RT_ICON. An RT_GROUP_ICON resource represents an icon. It is basically just a directory of icon images. Each directory entry refers to a RT_ICON resource, which contains the actual image data (see Figure 5).

Figure 5 - RT_GROUP_ICON and RT_ICON Resource Memory Layout

So to enumerate the icons in a library you pass RT_GROUP_ICON as the type of resource to enumerate. The next parameter is the callback function, which is the traditional way of implementing an enumerator in C. You simply pass the address of a function to EnumResourceNames and it will call that function for each matching resource. Simple. Well not really. The problem is that the .NET Framework has no notion of a function pointer. Fortunately for us the .NET Framework has a thing called a delegate, which you can use to implement a callback. A delegate can refer to a static or instance method. You can’t pass EnumResourceNames a pointer to a function, but you certainly can pass it a delegate, assuming you declared the EnumResourceNames function appropriately and declared the matching delegate. Here’s how you would do that.

[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern int EnumResourceNames(IntPtr hModule,
                                                                 IntPtr pType,
                                                                 EnumResNameProc callback
                                                                 IntPtr param);

public delegate bool EnumResNameProc(IntPtr hModule,
                                                          IntPtr pType,
                                                          IntPtr pName,
                                                          IntPtr param);

Since EnumResourceNames really operates on a library, I’ve added a method to my Library class for it. This saves us from having to pass it a module handle every time. And of course this is the more object-oriented way to go about it. Now to enumerate the icon resources in a library becomes very easy (see Figure 6).

Figure 6 – Using the EnumResourceNameProc Delegate

public void Main()
{
    using (Library library = new Library(@”C:/Windows/Explorer.exe”
                                                        WindowsAPI.LOAD_LIBRARY_AS_DATAFILE))
    {
        // Call the Callback method for each RT_GROUP_ICON resource that
        // is found.

        library.EnumResourceNames(WindowsAPI.RT_GROUP_ICON,
                                                   new WindowsAPI.EnumResNameProc(Callback),
                                                   IntPtr.Zero);
    }
}

public bool Callback(IntPtr hModule,
                              IntPtr pType,
                              IntPtr pName,
                              IntPtr param)
{
    // use the icon resource

    return true; // to keep enumerating
}

At this point we can call LoadIcon to get a handle to an icon tailored for the current display configuration. This can then be passed to the Icon.FromHandle method to get an Icon object, which in turn can be displayed in various ways in a .NET application. If you recall one of my objectives for the Icon Browser application is to be able to save an icon to an .ICO file. To do this we need to dig a little deeper and get at the actual resource data.

To get the resource data we need to use the FindResource and LoadResource functions to locate and load the resource. This is different from calling LoadIcon, which generates a displayable icon based on the icon resource. Once the resource is loaded we call the LockResource function to get a pointer to the actual resource bytes. This pointer is valid until such time as the library containing the resource is freed. I’ve declared two C# structures to represent the resource data (see Figure 7).

Figure 7 – C# Strucures for RT_GROUP_ICON

[StructLayout(LayoutKind.Sequential, Pack=2)]
public struct MEMICONDIR
{
    public ushort wReserved;
    public ushort wType;
    public ushort wCount;
    public MEMICONDIRENTRY arEntries; // inline array
}

[StructLayout(LayoutKind.Sequential, Pack=2)]
public struct MEMICONDIRENTRY
{
    public byte bWidth;
    public byte bHeight;
    public byte bColorCount;
    public byte bReserved;
    public ushort wPlanes;
    public ushort wBitCount;
    public uint dwBytesInRes;
    public ushort wId;
}

You won’t find these data structures defined in the Platform SDK documentation. What complicates matters when trying to use them from C# is that MEMICONDIR is actually a variable length structure. In C++ you would declare it as follows.

struct MEMICONDIR
{
    WORD wReserved;
    WORD wType;
    WORD wCount;
    MEMICONDIRENTRY arEntries[1];
};

The length of the arEntries array is defined by wCount. In other words, arEntries is an inline array as apposed to a pointer to the first element of an array. So to get to the various elements of the array you need to perform a bit of pointer arithmetic. Although this is no problem to a seasoned C or C++ developer, it poses a challenge as C# does not encourage the use of pointers. Fortunately it does not disallow it altogether. You do however need to explicitly mark code blocks as unsafe before you can make use of pointers. This can be done using the C# unsafe keyword. Please bear in mind that it is called unsafe for a reason. Misuse of pointers often leads to very buggy code. This is why even in C++ the use of pointers is discouraged and should usually be localized to C++ classes employing the ‘resource acquisition is initialization’ idiom. If you use any unsafe code in a .NET assembly the entire assembly is marked as unsafe. Figure 8 contains a simple console application that demonstrates how to load the icon resource for the Microsoft Notepad application and how to use pointer arithmetic to access the last entry in the arEntries array.

Figure 8 – Loading an Icon Resource

using System;
using Kerr.Resources;

class Console
{
    unsafe static void Main()
    {
        try
        {
            // For brevity we assume Notepad is located here. To run this 
            // code yourself adjust this value accordingly.

            string strFileName = @"D:/Windows/Notepad.exe";

            // Load the library.

            using (Library library = new Library(strFileName,
                                                                WindowsAPI.LOAD_LIBRARY_AS_DATAFILE))
            {
                // Notepad's icon resource identifier: 2

                IntPtr hGroupInfo = library.FindResource(new IntPtr(2),
                                                                            WindowsAPI.RT_GROUP_ICON);

                // Load the icon resource.

                IntPtr hGroupRes = library.LoadResource(hGroupInfo);

                // Get a pointer to the resource data.

                WindowsAPI.MEMICONDIR* pDirectory =
                    (WindowsAPI.MEMICONDIR*) Library.LockResource(hGroupRes);

                if (0 != pDirectory->wCount)
                {
                    // pEntry points to the first entry in the array.
                    WindowsAPI.MEMICONDIRENTRY* pEntry = &(pDirectory->arEntries);

                    // Adjust the pEntry to point to the last entry.
                    pEntry += pDirectory->wCount - 1;

                    // use the directory entry.
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Console application failed: " + e.Message);
        }
    }
}

Now that we know how to access these structures lets take a closer look at them. Both MEMICONDIR and MEMICONDIRENTRY have the StructLayout attribute applied to them. LayoutKind.Sequential is the single parameter to the StructLayoutAttribute instance constructor. It indicates that the members of the structure must be laid out sequentially in memory so as to be memory-compatible with unmanaged code. Otherwise, the CLR is free to move the struct members around in memory for efficiency. The Pack field indicates the alignment of the members in memory. The Pack field defaults to 8, which is convenient as this is the same as what Visual C++ 6 uses. And since most unmanaged structures you might encounter were probably declared and implemented using Visual C++ 6 it is a safe default. However the icon resource uses a Pack value of 2 and that is why we need to declare it explicitly. This results in a slightly smaller memory footprint, as most of the structure members are 2 byte words or smaller. To put this in perspective you would declare the MEMICONDIR structure in C++ like this.

#pragma pack(push)
#pragma pack(2)

struct MEMICONDIR
{
    WORD wReserved;
    WORD wType;
    WORD wCount;
    MEMICONDIRENTRY arEntries[1];
};

#pragma pack(pop)

The MEMICONDIRENTRY structure contains information about an individual icon image. The wID member is of particular interest to us as it contains the resource identifier for the RT_ICON resource within the library that contains the actual image bitmap information. You can use this identifier with the FindResource and LoadResource functions to locate and load the RT_ICON resource. And then you can use the LockResource function to get at the bitmap information. Finally we’ve found the images! The RT_ICON resource data is stored in memory in a very similar way to a device-independent bitmap (DIB). So much so that we can use the BITMAPINFO structure, which defines a DIB in memory to access the RT_ICON resource data. For more information about DIB’s and the BITMAPINFO structure please consult the Platform SDK documentation. Technically the icon image uses the same format as the CF_DIB clipboard format where the DIB bits follow the BITMAPINFO header and color table. You will need to know this if you want to render the image yourself using the icon resource. For the purpose of this discussion we can ignore this fact except to remember that the BITMAPINFO is yet another variable length structure and we must treat it accordingly. One thing to keep in mind is that icons deviate slightly from the BITMAPINFOHEADER semantics in that the biHeight member is double the actual height. Figure 5 illustrates how the RT_GROUP_ICON and RT_ICON resources are laid out in memory.

Next we’ll look at how we can wrap all this resource management code in a few well-defined classes to reduce this complexity.

Resource Management Revisited

I have written two C# classes namely IconResource and IconImage respectively. The IconResource class takes care of loading an RT_GROUP_ICON resource from a library and populates an array of IconImage objects. Each IconImage object represents an RT_ICON resource, which is a single image for the icon resource. As a user of the IconResource class is entitled to keep a reference to an IconResource object long after the library that it originated from has been freed, we need to be careful about the memory that gets returned by calls to LockResource. Although we don’t have to worry about releasing this memory, the operating system will ensure that it is freed once its parent library is unloaded from the process, we do need to concern ourselves with any pointers to this memory that we may hold on to. The IconImage class needs to hold on to its resource data so that it can be used to write it to a .ICO file. I will talk more about this later. To satisfy this requirement the IconImage class makes a local copy of the image bits. When the IconImage object is constructed it loads the RT_ICON resource, which is just the DIB bits for the image. It then copies these image bits into a managed byte array using the Copy method on the Marshal class. So now that the IconImage class contains its own copy of the resource data, the library where the resource was originally retrieved from can be freed without affecting the IconImage class.

Lets See What You’ve Got!

Icons aren’t much fun if you can’t see them. So lets take a look at the Icon Browser’s MainWindow class and how it employs the classes that we’ve discussed so far to implement icon browsing. Although I am generally weary of computer generated code, the Visual Studio Forms Designer does a remarkable job of generating user interface code. It is a huge boon to productivity if handled correctly. The trick is to be conscious of the code it generates and to maintain your coding and naming standards. For example if you drag three text boxes onto a form it will name then textBox1, textBox2, and textBox3. Be sure to give them meaningful names in the context of your application as well as following any naming conventions you may be employing for your project. Further, for any non-trivial user interface component you need to emply standard design practices to ensure separation of data and presentation. This can be done using design patterns such as the Document/View pattern employed by MFC or the Model/View/Controller (MVC) pattern.

As you can see in Figure 1, the Icon Browser is a simple forms-based application. Its made up of a resizable window that contains a list view for displaying the icons as well as a tool bar of sorts exposing the functionality supported by the Icon Browser application. The code of interest is in the OkEventHandler method, which is called in response to the Click event on the OK button. This brings everything we have learnt so far together. The OkEventHandler method uses the Library class to load the user-provided file. It then calls the Library class’ EnumResourceNames method to enumerate the RT_GROUP_ICON resources. AddIconToList is the delegate called by EnumResourceNames for each icon resource that is found. It uses the IconResource class to load the resource data and adds an IconItem to the ListView to represent the icon to the user. IconItem is a simple class that derives from ListViewItem, that is used to initialize the ListViewItem text and image index.

Exporting Icons

There’s one more thing left to do. At the beginning of this article I mentioned that the Icon Browser would allow the user to export any icon to a .ICO file. Unfortunately for us the .ICO file format is different to the format used to store icons in resource files. Figure 9 illustrates how an icon is stored in a .ICO file. As you can see it holds somewhat of a resemblance with the memory layout of of the RT_GROUP_ICON resource. So with a little bit of translation code we can convert the one to the other. The major difference with the way the .ICO file format is written is that the icon image directory entry for each image stores an byte offset to the location of the image, whereas the RT_GROUP_ICON resource stores the resource name which you use to load the image. For the purposes of writing a .ICO file I have added a Save method to the IconResource class. This method takes a single string parameter that indicates the file to write the icon data to. It uses the BinaryWriter class to write the .ICO file header information. It then delegates to the IconImage class to write the directory entry information as well as the image bits.

Figure 9 – Binary layout of .ICO file

Conclusion

Reading and writing Win32 resources is a non-trivial exercise in C++. Being able to pull it off in C# displays some of the power and flexibility of the .NET Framework. Resource management code should always be written carefully. Using the .NET Framework’s IDisposable class lends a helping hand by providing a common pattern for resource management. I hope that this exercise of walking through the development of the Icon Browser application was useful to you in helping to understand some of the services provided by the .NET Framework for writing code to interoperate with unmanaged code and resources.

 

阅读全文
0 0

相关文章推荐

img
取 消
img