Lựa chọn

 Danh mục

 Chi tiết tài nguyên
Enumerate and Host Control Panel Applets using C#.
   Đăng bởi: host | Ngày : 01:43 05/12/09 | Xem: 93 |        
Enumerate and Host Control Panel Applets using C#.
A quick peak at the Applet Viewer Window displaying the applets in large icon mode.
Now viewing in details view. Notice the name and description displayed for each applet.
And finally notice an applet that has just been launched by double clicking its icon. This demonstrates the applets are really being hosted by the applet engine.
This article assumes a basic understanding of writing unmanaged C/C++ dynamic link libraries and exporting functions from those libraries. Also basic understanding of using P/Invoke to access unmanaged libraries will benefit the reader, but I will attempt to explain as much as possible.
The purpose of this article is to discuss several problems a developer might face when attempting to mix unmanaged and managed code. A problem that is all too often encountered when attempting to interact with the current Windows API from a managed language such as C# or VB.NET. In this article, I will discuss the problems I faced and how I solved the problems using a combination of managed and unmanaged code, C# and VC++ respectively. Here is a brief overview of what you can learn by reading this article:
  • Calling unmanaged function pointers
  • Dynamically loading unmanaged libraries at runtime
  • Acquiring function pointers to unmanaged functions
  • Translating C/C++ structures and data types to CLR compliant code
  • Finding alternatives to unsafe code for the purposes of using sizeof
  • Allocating/deallocating memory on the stack
  • Understanding how applets can be manipulated programmatically
  • Extracting strings and icons from embedded resources located in external unmanaged libraries.
So let’s begin by discussing why I decided to write this article and code. Being the curious coder I am, I am always intrigued by the underlying implementation of features in the Windows operating system. Control panel applets have always been a somewhat uncovered topic for some reason, yeah there is documentation on them in the MSDN library, but rarely any good working examples. Let along how they actually work. Before I set out to write this article, I already had a pretty good understanding of how applets are written, having written several as a professional developer in the past. However, it wasn’t until I stepped into shell development that I became completely curious just how Windows pulled off these wonderfully useful creatures.
As days went by developing my shell, I came across many methods for actually launching control panel applets from code. Most of the implementations involved hard coding references to rundll32.exe to call the Control_RunDLL function with various arguments to launch off control panel applets. This always bothered me, because this was far from dynamic, you had to know about the applets ahead of time, at least somewhat to figure out how to launch them. I decided I wanted a means to enumerate and host the applets just like Windows.
So having said the why, let’s discuss what an applet actually is. By the way, all of the information presented here is my personal dissection of the documentation found in the MSDN library. Control panel applets are nothing special, simply .dlls built with a special extension, .cpl, and placed into the Windows system directory. If you were to attempt to write one, you would have to choose a language that creates unmanaged DLLs and allows for exporting of unmanaged functions. Something that C# and VB just don’t do, so look to C++ or Delphi to pull this off. It’s not all that hard, but it’s beyond the scope of the article.
Now that we know what an applet is, an unmanaged DLL compiled with a .cpl extension, let's look at how they work. Digging into the documentation, you will discover the CplApplet function. It is the sole function an applet must export from its library to interface with Windows. The function looks like this:
LONG CPlApplet(HWND hWnd, UINT uMsg, LPARAM lParam1, LPARAM lParam2);
The function is very similar to the WndProc functions behind all windows. All communication to applets occurs through this function. You do not need to understand anything more about this function for now. If you are interested, do a search for CPlApplet in the MSDN library and you will have the pleasure of translating this wonderful function just as the rest of us were forced to do.
Ok, so let’s review what we know about applets. This will be the foundation of how we discover and communicate with all applets:
  • An unmanaged Windows dynamic link library
  • Uses the .cpl extension instead of the standard .dll extension
  • Exports a single function called CPlApplet
  • All applets should be located in the Windows System directory
Problem #1: Finding and loading unmanaged DLLs dynamically
Just when life is looking easy, and you are saying how hard can this be, in walks the first problem. How do we call this function from unmanaged code? As you know, you can call unmanaged functions from DLLs using System.Runtime.InteropServices.DllImportAttribute, the problem lies in the fact that you have to know the name of the library while coding. So how do we load unmanaged DLLs on the fly and call that unmanaged function if we can’t define its entry point ahead of time.
The answer lies in several functions. LoadLibrary, FreeLibrary, and GetProcAddress. We will use LoadLibrary to load the applet’s .cpl file (a.k.a. nothing more than a .dll) by its filename, and use GetProcAddress to get an unmanaged function pointer to the CplApplet function. FreeLibrary will be used to release the DLL once we are finished using it. This is a standard practice, and any of you who have done dynamic function pointers can probably skip ahead just a bit. However, I can remember when this was a magic bag of voodoo magic, and needed a little explanation.
Let’s look at how we can do this. First we will need to search the Windows System directory for all files ending with the .cpl extension. This is very easy to do using the methods and classes in the System.IO namespace. Here is the method that will do the grunt work of discovering these on the fly. Let’s take a look. But first let me break down the classes that we will be working with, the classes I have created to make the magic happen. Very briefly they are…
  • AppletEngine
  • AppletLibrary
  • Applet
The AppletEngine class contains the following method that will allow us to find the applet libraries.
public FileInfo[] FindAppletLibraries(string path)
   DirectoryInfo di = new DirectoryInfo(path);
    if (di != null)
        return di.GetFiles("*.cpl");
    returnnew FileInfo[] {};
This will allow us to be returned an array of FileInfo objects that contain information about the files that fit our search. This is all pretty standard stuff and shouldn’t cause any questions as of yet. If it does, refer to the docs on MSDN or my source code and I’m sure the lights will come on quickly.
Now that we have discovered the files that end with .cpl, we will assume them to be all applet libraries. Let’s look at how we can use LoadLibrary and GetProcAddress to load them and get that function pointer so we can communicate with the applets. We simply need to loop through the FileInfo objects and call LoadLibrary on the filename to load the DLL, and assuming that succeeds, we can call GetProcAddress to return a function pointer to the CplApplet function. Here is a snippet from the AppletLibrary constructor that implements this algorithm.
public AppletLibrary(string path, IntPtr hWndCpl)
    _path = path;
    _hWndCpl = hWndCpl;
    _applets = new ArrayList();
    if (!System.IO.File.Exists(path))
      thrownew System.IO.FileNotFoundException
          ("No applet could be found in the specified path.", path);
    _library = LoadLibrary(path);
    if (base.IsNullPtr(_library))
      thrownew Exception("Failed to load the library '" + _path + "'");
    _appletProc = GetProcAddress(_library, "CPlApplet");
    if (base.IsNullPtr(_appletProc))
      thrownew Exception("Failed to load CPlApplet proc for the library '"
      + _path + "'");
Let’s discuss just what this code snippet is doing. First off, it will try and call LoadLibrary on the path to the file, this should be something like C:\Windows\System\SomeApplet.cpl. The method will return an IntPtr which is a handle to the library. Look at the MSDN docs for more info on this, I’d rather let the creators explain it. If the function succeeds, the IntPtr will be something other than IntPtr.Zero. Once we have a handle to the library, we can call GetProcAddress with the handle and the name of the function to get yet another IntPtr which is an unmanaged function pointer.
Problem #2: Calling unmanaged function pointers from managed code
Here now we are faced with a rather tricky problem. How do you call an unmanaged function pointer from managed code? At first glance the answer seems simple, we use delegates. However correct that solution may seem, I have not been able to uncover a means of creating a delegate in managed code to an unmanaged function pointer. Several methods in the Marshal class look promising, namely GetUnmanagedThunkForManagedMethodPtr. Here I will admit defeat because I cannot for the life of me figure out how to work this method. The docs are no help, and I simply got tired of racking my brain to figure it out. I am hoping that someone will read this article and come up with a solution for what I am about to do next. Part of this article, by the way, I am assigning to the rest of you interop gurus to help me figure that method out. I’m certain it can be done, it’s just not worth my time to waste any more time trying to figure it out. If someone does, please let me know!
Enter our own trickery. I decided the easiest way to do this would be to create a small unmanaged C++ DLL that could call it for us. Calling function pointers in C++ is as easy as declaring integers to the rest of the managed world. So breaking open a Win32 project and setting its project type to dynamic link library, I created a DLL to do this work for me. I called it AppletProxy.dll for lack of a better term, because it will act as a proxy between our managed code in C# and the unmanaged function exported by the applet. I am not going to cover how to create unmanaged DLLs here, that is also beyond the scope of the article. If you are really interested, the source code should provide you with a very simple example to learn from, and as always, I’m around for questioning if you get stuck. Here is what the unmanaged function looks like that will be the key to calling the unmanaged function pointers for us.
LONG APIENTRY ForwardCallToApplet(APPLET_PROC pAppletProc,
    HWND hwndCpl, UINT msg, LPARAM lParam1, LPARAM lParam2)
    if (pAppletProc != NULL)
        return pAppletProc(hwndCpl, msg, lParam1, lParam2);
    // call the unmanaged function pointer, this is the same
    // as calling a regular function, except we’re using
    // the variable instead of a function name
Ok, I know a lot of you are looking at this and thinking, what in the heck is this guy doing? I don’t understand the syntax of this stuff, and what’s with all the funky data types. I’m hoping that’s not the case, because if it is, you should really go open MSDN and look up the data types. They are all readily available in the docs.
This method will accept an unmanaged function pointer and call the method it points to and return us the result.
Now that we have our proxy function to call the unmanaged function pointer, you might be wondering how we call that from our managed code. We simple use P/Invoke and define the entry point just like any other API. Here is how to do just that.
publicstaticexternint ForwardCallToApplet(IntPtr appletProc,
    IntPtr hWndCpl, AppletMessages message, IntPtr lParam1, IntPtr lParam2);
One of the problems I encountered when I started trying to call API functions were the difference in data types. I had a real problem trying to figure out what an HWND or LPARAM translated to in managed code. Here is a quick reference for you newbies that will help you out when trying to translate functions from C/C++ to managed code.
  • HWND, HANDLE, HINSTANCE, HICON, any Pointer type maps to System.IntPtr
  • DWORD, LONG, BOOL map to System.Int32 or int in C#
  • LPSTR, LPTSTR, LPCTSTR map to System.String and System.Text.StringBuilder
  • Use System.String most times, but if the API requires a pre-initialized buffer of any length, then use a System.Text.StringBuilder
I hope this helps, because I know for a while I was constantly heading off to the C header files to find the underlying definitions and then doing some research on MSDN to figure out what the data type was supposed to be declared as in managed code. The main thing to remember in my opinion is that if it starts with "H", it’s most likely a handle of some sort which maps nicely to System.IntPtr. The specific implementations of course may vary from time to time, but as a general guideline these have worked out just fine.
Problem #3: What does the applet expect?
Now that we have covered a few tricky concepts, let’s move back into some functionality discussions. At this point, we can load any applet we want, and call the CplApplet function to communicate with the applet. But what exactly are we going to pass to this function to get the results we want. There are several things that you’ll notice Windows presents for applets, by looking at the Windows Control Panel in Windows Explorer. There are icons, and text for all the applets. How does it get those? Answer: By calling the CplApplet function with specific messages and getting specific results back.
Applets are designed (or should be designed) to provide a name and description as well as an icon to be displayed for the user. Let’s look at how we can recreate this functionality and talk to our applets. A quick look at the docs on MSDN and we find a set of messages and several structures that are used to communicate with the applet. Another problem is coming, but it’s not nearly as rough as any previous ones, but if you didn’t know what to do, it could be really a world ending problem. Don’t worry I will show you how to deal with it, but let’s see what these messages and structures look like first.
To communicate with the applet we will send a message, and optionally a pointer to a structure to receive information back from the applet. The structures are where our last hurdle occurs. Here are the definitions in managed code:
/// <summary>
/// The standard Control Panel Applet Information structure
/// </summary>
publicstruct CPLINFO
    /// <summary>
    /// The resource Id of the icon the applet wishes to display
    /// </summary>
    publicint IconResourceId;
    /// <summary>
    /// The resource Id of the name the applet wishes to display
    /// </summary>
    publicint NameResourceId;    
    /// <summary>
    /// The resource Id of the information the
    /// applet wishes to display (aka. Description)
    /// </summary>
    publicint InformationResourceId;
    /// <summary>
    /// A pointer to applet defined data
    /// </summary>
    public IntPtr AppletDefinedData;   
    /// <summary>
    /// A simple override to display some debugging information
    /// about the resource ids returned from each applet
    /// </summary>
    /// <returns></returns>
    publicoverridestring ToString()
            "IconResourceId: {0},
             NameResourceId: {1},
             InformationResourceId: {2},
             AppletDefinedData: {3}",
/// <summary>
/// The advanced Control Panel Applet Information structure
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
publicstruct NEWCPLINFO
    /// <summary>
    /// The size of the NEWCPLINFO structure
    /// </summary>
    publicint Size;
    /// <summary>
    /// This field is unused
    /// </summary>
    publicint Flags;
    /// <summary>
    /// This field is unused
    /// </summary>
    publicint HelpContext;
    /// <summary>
    /// A pointer to applet defined data
    /// </summary>
    public IntPtr AppletDefinedData;
    /// <summary>
    /// A handle to an icon that the applet wishes to display
    /// </summary>
    public IntPtr hIcon;
    /// <summary>
    /// An array of chars that contains the name
    /// that the applet wishes to display
    /// </summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
    publicstring NameCharArray;
    /// <summary>
    /// An array of chars that contains the information
    /// that the applet wishes to display
    /// </summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
    publicstring InfoCharArray;
    /// <summary>
    /// An array of chars that contains the help file that
    /// the applet wishes to display for further help
    /// </summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
    publicstring HelpFileCharArray;
There are really two kickers here. The first is in the CPLINFO structure. Once returned to us, it contains the integer IDs of resources in the applet’s resource file that contain either the string for a name or description, or an icon. A quick glance at the docs, I realized we could extract this information using LoadString and LoadImage. However, it states clearly that you should use the MAKEINTRESOURCE macro on the resource ID before passing it to the LoadString or LoadImage functions. I dug through the header files and discovered a really nasty looking conversion. I won’t even bring it up, because I think the Windows developers did it just to screw with the rest of the world, a.k.a. anyone not programming in C/C++! It’s funknasty and I don’t know how to convert it to C#, believe me I tried. Here is what it looks like in the header file:
#define IS_INTRESOURCE(_r) (((ULONG_PTR)(_r) >> 16) == 0)
#ifdef UNICODE
#endif // !UNICODE
Now if any of you can translate that to C#, again please clue me in. I’d love to know. I consider myself pretty good at translating, but again I may have just been up too late or had too much Mountain Dew to operate at the level required to translate this. So like my previous solution to the function pointers, I went back to my C DLL and made another wrapper function so that I could just use the real deal and be done with it. Here is what the wrapper functions look like:
HICON APIENTRY LoadAppletIcon(HINSTANCE hInstance, int resId)
    return ::LoadIcon(hInstance, MAKEINTRESOURCE(resId));
                      int resId, int width, int height)
    return ::LoadImage(hInstance, MAKEINTRESOURCE(resId),
        IMAGE_ICON, width, height, LR_DEFAULTCOLOR);
Ok, now that we can load the strings and icons from the resources of the applet, we hit the last of our hurdles. This probably caused me more trouble than any of them, but ended up being the easiest to solve once I understood how to tackle the problem. Like I stated before, the structures above will be used to pass information back to us when we call into the applet. The CPLINFO structure is really straight forward, but the NEWCPLINFO structure is kinda different. Some applets expose dynamic information, based on some changing resource for example. Something like wi-fi or some network or disk resource the description or icon might need to be changed. So we have to refresh the information each time using the NEWCPLINFO structure. As I started translating the structure to C# I discovered the following definition in the docs. A fixed length char[] of predefined length. As you may or may not know you cannot pre-initialize public fields in managed structures. And I didn’t understand how to get my structure to map to the real one because of this limitation.
typedef struct tagNEWCPLINFO {
    DWORD dwSize;
    DWORD dwFlags;
    DWORD dwHelpContext;
    LONG_PTR lpData;
    HICON hIcon;
    TCHAR szName[32];
    TCHAR szInfo[64];
    TCHAR szHelpFile[128];
Take the szName field for our example. How do we define a fixed length array of characters in our structure? The answer lies in the MarshalAs attribute. We can define our field as a string and let the P/Invoke services marshal the data as in a specific format. This is what we can do to get the desired marshalling to occur. We will define our field to be marshaled as an array of null terminated characters with a fixed array size.
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
publicstring NameCharArray;
A small side note is in the string conversions. Do we use ANSI or Unicode? For the most part Unicode is the data type of choice for strings, but because we don’t know ahead of time the real struct uses the TCHAR type which will map to the appropriate type when compiled based on the various #defines in the C/C++ header files. We don’t have that luxury, but we do have a solution. We will apply the StructLayout attribute to our structures to define sequential layout of the fields, which will keep the fields in the order we specify, (the CLR tends to optimize structure fields to what it feels is optimal, which can really hose us when dealing with pointers and unmanaged code, so we need to tell it that it needs to leave our fields in the order we define) and also the character set to use for any strings it encounters. This is accomplished by the following:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
publicstruct NEWCPLINFO
Having piddled around with the various character sets, I discovered the strings are displayed correctly using the ANSI charset on my Windows XP pro box on down to my 98 test machine. I don’t know if this is the right choice, hopefully someone will step up and put a final yes or no to this question. Again I’m completely open for suggestions as this was a learning experience for me as well. I hope to have all of my questions answered and sleep comfortable in my bed at some point in the next 30 years, but that’s only going to happen when all of my Windows API questions have been answered. And at the rate I find questions, I’ll either have to get a job at Microsoft or steal some of the source to get at the answers. I’m quite frankly open for both at this point. So many hours of my life have been wasted trying to understand the minds of the guys that made those APIs, but again I digress.
Ok, we’re nearing the completion of our translations and definitions, so let’s look at how to communicate with the applets. The applets expect a certain order of messages. The first call to CPlApplet should initialize the applet and let it know we are loading. The second call will tell us how many applets are actually contained in the .cpl file. This is a one to many relationship as one DLL could have an unlimited number of applets contained inside. The third and fourth calls will inquire the information from the applet. After getting the information we need, we can ask the applet to show its UI by sending it a double click message (don’t stress this is normal, and I’ve made an enumeration for the messages. I renamed them to fit my .NET likings but if you are interested, find the cpl.h header file and look at the CPL_INIT, CPL_GETCOUNT, CPL_INQUIRE, CPL_NEWINQUIRE, CPL_DBLCLK, CPL_CLOSE, CPL_STOP messages for details). Here is the message enumeration that I translated from the MSDN docs:
publicenum AppletMessages
    Initialize = 1,
    /* This message is sent to indicate CPlApplet() was found. */
    /* lParam1 and lParam2 are not defined. */
    /* Return TRUE or FALSE indicating whether
               the control panel should proceed. */
    GetCount = 2,
    /* This message is sent to determine the number
                    of applets to be displayed. */
    /* lParam1 and lParam2 are not defined. */
    /* Return the number of applets you wish to display in the control */
    /* panel window. */
    Inquire = 3,
    /* This message is sent for information about each applet. */
                   AND CPL_NEWINQUIRE MESSAGES. */
    /* The developer must not make any assumptions about the order */
    /* or dependance of CPL inquiries. */
    /* lParam1 is the applet number to register, a value from 0 to */
    /* (CPL_GETCOUNT - 1). lParam2 is a far ptr to a CPLINFO structure. */
    /* Fill in CPLINFO's IconResourceId, NameResourceId,
          InformationResourceId and AppletDefinedData fields with */
    /* the resource id for an icon to display,
             name and description string ids, */
    /* and a long data item associated with
             applet #lParam1. This information */
    /* may be cached by the caller at runtime and/or across sessions. */
    /* To prevent caching, see CPL_DYNAMIC_RES, above. */
    Select = 4,
    /* The CPL_SELECT message has been deleted. */
    DoubleClick = 5,
    /* This message is sent when the applet's icon has been */
    /* double-clicked upon.
        lParam1 is the applet number which was selected. */
    /* lParam2 is the applet's AppletDefinedData value. */
    /* This message should initiate the applet's dialog box. */
    Stop = 6,
    /* This message is sent for each applet when the
                            control panel is exiting. */
    /* lParam1 is the applet number. lParam2 is
             the applet's AppletDefinedData value. */
    /* Do applet specific cleaning up here. */
    Exit = 7,
    /* This message is sent just before the control panel
                                       calls FreeLibrary. */
    /* lParam1 and lParam2 are not defined. */
    /* Do non-applet specific cleaning up here. */
    NewInquire = 8
    /* Same as CPL_INQUIRE execpt lParam2 is a
                pointer to a NEWCPLINFO struct. */
    /* The developer must not make any assumptions about the */
    /* order or dependance of CPL inquiries. */
There is a lot of stuff I’m skipping because it’s kinda dull. I know you all are interested in seeing something running. But if you are curious, read through the comments in my code and the docs in MSDN to see the exact steps the applets are expecting.
So let’s begin by initializing an applet library and finding out how many applets are inside. The following snippet from the AppletLibrary class demonstrates how this is achieved:
publicvoid Initialize()
    if (this.CPlApplet(AppletMessages.Initialize,
        IntPtr.Zero, IntPtr.Zero) == (int)BOOL.TRUE)
        int count = this.CPlApplet(AppletMessages.GetCount,
                                     IntPtr.Zero, IntPtr.Zero);
//        System.Diagnostics.Trace.WriteLine
//          (string.Format("{0} applets found in '{1}'", count, _path));
        for(int i = 0; i < count; i++)
            Applet applet = new Applet(this, i);
Take note of the class hierarchy here. AppletLibrary contains Applets. One to many. The AppletLibrary contains a property that exposes an ArrayList of Applet objects. I purposefully used ArrayList as I do not want the reader getting confused by any custom collection classes. Believe me, if this were production code, that would be a strongly typed collection class either by implementing ICollection and others, or by inheriting from CollectionBase. That again is outside the scope of this article, so try and stay focused on the problems at hand, and not with my coding style or means for enumerating sub objects.
Now that we know how many applets are actually inside an applet library, we need to extract the information from the applet so we can display its name, a short description, and an icon for the applet. What good would this do us if we couldn’t show it to the users like Windows does, right? Now, take a look a the AppletLibrary class. This is where the remainder of our discussion lies. As I stated before, the applets will pass us information back in structures. If the applet is using static resources, we will have to extract them. So let’s see how this is accomplished by yet another snippet of code, and talk about the caveats. This is the fun stuff I think, pulling of some pointers and marshalling!
Here I will demonstrate a small scenario that uses an unsafe code block, and another similar safe means to achieve the same ends using P/Invoke that does not require marking the code as unsafe. Keep in mind, our goal is to stay in the managed world as much as possible, to allow us to reap the benefits of garbage collection and type safety. There are plenty of days left to track down bugs because it was cool to pin pointers using the fixed statement and cast byte* around like they were Mountain Dew cans. Yeah I can do it, but it kinda defeats the purpose of a clean type safe managed language, so I avoid it if at all possible. There are probably a lot of cocky guys around who want to do it just to be cool, but believe me I was one of those guys, till deadlines hit and CEOs are asking when my app is going to stop crashing at random times. Pointers are cool, not using pointers is cooler. Trust me when I say life in a managed world is good, very good.
The first block that uses unsafe code actually requires the unsafe statement simple to demonstrate an alternative to the sizeof() function we’re all so used to. The sizeof() is a pretty standard means for determining the size of a structure in bytes. Unfortunately, it must be used in unsafe code. Its alternative is Marshal.SizeOf which does not require unsafe code statements. Here, have a look for yourself. This code is going to call the applet and ask for its information, and then use the pointers returned to cast into our managed structures.
publicvoid Inquire()
        _info = new CPLINFO();
        _infoPtr = Marshal.AllocHGlobal(sizeof(CPLINFO));
        Marshal.StructureToPtr(_info, _infoPtr, true);
        if (!base.IsNullPtr(_infoPtr))
                new IntPtr(_appletIndex), _infoPtr);
            _info = (CPLINFO)Marshal.PtrToStructure(_infoPtr,
            if (!this.IsUsingDynamicResources)
publicvoid NewInquire()
//    unsafe
//    {
          _dynInfo = new NEWCPLINFO();
          _dynInfo.Size = Marshal.SizeOf(_dynInfo);
          _dynInfoPtr = Marshal.AllocHGlobal(_dynInfo.Size);
          Marshal.StructureToPtr(_dynInfo, _dynInfoPtr, true);
          if (!base.IsNullPtr(_dynInfoPtr))
                  new IntPtr(_appletIndex), _dynInfoPtr);
              _dynInfo = (NEWCPLINFO)Marshal.PtrToStructure(_dynInfoPtr,
              _smallImage = Bitmap.FromHicon(_dynInfo.hIcon);
              _largeImage = Bitmap.FromHicon(_dynInfo.hIcon);
              _name = _dynInfo.NameCharArray.ToString();
              _description = _dynInfo.InfoCharArray.ToString();
//   }
To get back the structure from the pointer returned by the applet, we first have to allocate memory on the stack for the structure. This can be achieved by calling Marshal.AllocHGlobal. Keep in mind that anytime we allocate memory on the stack, we have to free that memory back up otherwise we have yet another crappy app with a memory leak. That’s just no good for anyone because the stack is a finite resource shared by everyone. You run out of stack memory and well, better start thinking about rebooting and explaining why your programs run like a fat man in a marathon. They start out strong enough, but end up taking a taxi across the finish line. That’s just no way to be. Because of the memory allocation occurring, all of my classes inherit from DisposableObject. That is just my simple wrapper for implementing IDisposable and putting some wrappers around pointer checks. So look to the overrides in the AppletLibrary and Applet classes to see resources being disposed properly by overriding the abstract method in the DisposableObject class. Once we have the information back out of the applets, we’re free to display it and wait for the user to open an applet. You can programmatically open an applet by calling the Open method on the Applet class. Here is the code for that:
publicvoid Open()
    IntPtr userData = (this.IsUsingDynamicResources ?
        _info.AppletDefinedData : _dynInfo.AppletDefinedData);
    int result = _appletLibrary.CPlApplet(AppletMessages.DoubleClick,
        new IntPtr(_appletIndex), userData);
    if (result != 0)
        System.ComponentModel.Win32Exception e =
            new System.ComponentModel.Win32Exception();
Notice that we are passing a pointer back to the applet. Each applet can define data in a pointer that we must pass back to when we open and close the applet. If the result of sending the CPL_DBLCLK method returns 0, then everything went OK according to MSDN. However, this call blocks until the applet’s dialog closes, and I’ve seen cases where it fails, by result being non-zero, even after the applet shows its dialog. I am currently trying to figure this out, but the docs aren’t much help. I have noticed that certain applets always seem to fail according to the result of this call, even though they appear to work correctly. I’ve tried to catch and look at the exception, but most times it’s not much help. Try it out and see what your results are. Put a break point on the trace and launch an applet. I’m really quite curious to what the deal with my results are.
Again I’m hoping someone can pick up the ball and help me figure this out here. I’ve cleared a lot of pretty tricky stuff I think, I just hate to get stumped this far along with the project to give up and say we just got lucky. On a side note, the Display Properties applet quit working on my system. I don’t know why. I was working in this code base, but then I changed the window handle around. Look at the docs and source to see, if you don’t understand what I’m saying, then you probably can’t help me, so no worries k? LOL. Like I said, this is supposed to be a learning experience for all of us, I’m learning too. I have never seen any code to do what I’m trying to do before online, so feel free to tear me up if you want. Go find another example if you can, I’d love to see it! Seriously, this might have been easier to write having something to turn to other than the cryptic header files and MSDN docs to help me out.
Time to go download the code, and perhaps toss me a vote so I know how to improve for the next article
Well it’s time that you downloaded the source and demo projects if you haven’t done so already. Nothing feels quite as good as running a project and seeing some results. So go have fun playing with the project and stepping around through some interesting coding ideas. I hope this helps some of you overcome some similar hurdles. I had a lot of fun writing the code and almost as much fun trying to explain it in this article. This is my official first posting online, probably will have many more, just gotta find the time. I’ve held back for sooooo long on posting from lack of time, not lack of ideas or knowledge. Look for more posts in the future.
Also, this being my first article, if you don't mind, give me some feedback, and possibly a vote. Have some kindness on my writing abilities as an author of articles, because as you may know I write code, not books. So chances are high that the article reads poorly, but hopefully the code is solid! :)
What we learned
Let’s review a few interesting concepts that we’ve covered:
  • Calling unmanaged function pointers
  • Dynamically loading unmanaged libraries at runtime
  • Acquiring function pointers to unmanaged functions
  • Translating C/C++ structures and data types to CLR compliant code
  • Finding alternatives to unsafe code for the purposes of using sizeof
  • Allocating/deallocating memory on the stack
  • Understanding how applets can be manipulated programmatically
  • Extracting strings and icons from embedded resources located in external unmanaged libraries
Things left, and known issues
  • Thread calls to Open and Close, or make Asynchronous wrappers
  • Figure out why certain applets return false after they close
  • Find out where in the heck the Admin Tools Applets are located or how they are inserted into the control panel namespace (I think it’s a Shell Extension, just haven’t found it yet)
  • Figure out how to work Marshal.GetUnmanagedThunkForManagedMethodPtr
  • Figure out how to convert MAKEINTRESOURCE to C#
Version 1.0.0

About Mark (Code6) Belles

Senior Application Developer specializing in Windows desktop and network development.

Professional Experience
- B.S. of Computer Science (Graduated 2001 - PSU)
- Senior Application Developer (5+ yrs)
- Microsoft Certified Professional
- Techtarget.com C# Expert

Primary Interests
- C#, C++, HTML, Javascript
- XML, ASP.NET, Web Services, SOAP, UDDI
- Socket programming and anything network related
- Reflection, Serialization, and Plugin Frameworks
- Owner-drawn controls and GDI+ goodness

Bình luận

Thêm nhận xét