Select language
English : Russian
Desktop multi language  
Aston Shell Desktop manager and Shell replacement AltDesk virtual desktops aston desktop
Top main menuMainDownloadsRegistrationSupport 
Products
Aston2
  • Product details
  • Themes
  • Live desktop
  • Skinning Manual
  • Skinning Tutorials
  • Aston 1.x
  • Basic setup tips
  • Tips and tricks
  • FAQ
  • Utilities
  • What`s new
  • Plug-ins SDK
  • Skinning Tutorial
  • Plug-ins Tutorial
  • Aston misc
  • Themes
  • Plug-ins


  • Aston2 Menu
  • Product details
  • Skins
  • Aston Secure Desktop
  • Product details
  • AltDesk
  • Product details
  • FAQ
  • Skinning Tutorial
  • Skins
  • StarBlaze
  • Product details
  • FAQ
  • StarBlaze 2
  • Product details

  • Services
  • Upload center
  • Press
  • Links


    MrBiotech's site

    adni18 site

    Skinning.net site
    Freeware
  • Fox Hunting
  • c2pas32

  • Search

    The Basics of Writing an Aston Plugin

     
     

    by Veratil Eladamri


    Part One - Introduction (Not of me, of course!)


    To begin with, I will assume that you have a working knowledge of at least the basics of the Win32 API and of C++*. Also, I assume that you are using Microsoft Visual C++ 6 (7 I don't have, so I can't teach that right now). Also, I do not think I'm "the best" at making plugins, I've only been making them for about 1 1/2 months now, but I believe the basics are behind me and I think that other people trying should at least have something to go by. Now then, let's understand pretty much what Aston plugins really are... Aston plugins are simply Windows DLLs renamed with a PLG or TBP extension. The PLG extension is for plugins that reside on the Desktop, Taskbar, or System tray. The TBP extension is for the left and right toolbars. In this tutorial, I will teach about the PLG type plugin, as the TBP type is a little different.
    *Note: As of now, I am doing only C++ examples and code. Later on I will try my best to work in a Delphi version.

    Let's start with a complete and totally basic plugin that will place itself on the desktop. Start up Microsoft Visual C++ and start a new project. Select the Win32 Dynamic-Link Library project. Type in your project name, and make sure the Win32 checkbox is checked under the Platforms list. Click OK. Select An empty DLL project. and click Finish. Click OK to the dialog that pops up. What we have to do now, is tell the compiler how we want the project to compile. Follow these steps carefully as if you mess up, chances are it might be something small and you won't know what to do.

    1. Click on Project, then select Settings.... Click on the Link tab. Change the Output file name to have the PLG extension, i.e. replace "dll" with "plg".
    2. Under the Object/library modules section, add to the end: starter.lib
    3. Under the Project Options you will need to go to the very end of the text, and add this to it: /libpath:"../". This is if you're under a folder in the SDK. The libpath should really point to starter.lib.

    Now then, we've just created the project workspace to compile correctly for an Aston plugin. Now we need to add files to it. Click on the New Text File toolbar button (if you have that toolbar up), otherwise click on File, go to New, select the Files tab, and select C/C++ Source File. Make sure the Add to Project checkbox is checked, and the project it's going to add to is the correct one. If you want, you can specify a filename, or you can do that later. For now, let's call it DeskWnd.c.

    I'm going to give you a simple, yet complete plugin source. This is a modified version of the DeskWnd plugin found in the Aston Plugin SDK. I have removed comments as I'm going to explain everything afterwards.

    File: DeskWnd.c
    
    #pragma comment(linker,"/DEF:DeskWnd.def")
    #pragma comment(linker,"/entry:DllMain")
    /* Note for VC7 users:
    You should use project options "Module Definition file" and "Entry point", 
    instead of this "#pragma" directives.
    Thanks to Oleg for telling me this. */
    #include <windows.h>
    
    #include "../rtlmem.h"
    
    #define NODEFAULTLIB
    
    #include "../astonsdk.h"
    #include "../st_api.h"
    
    HMODULE hInstance;
    
    BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID 
    lpReserved)
    {
        if (ul_reason_for_call == DLL_PROCESS_ATTACH)
        {
          DisableThreadLibraryCalls(hInstance = hModule);
        }
        return TRUE;
    }
    
    #define szClassName "DeskWnd"
    
    TAstonData AData;
    HWND ChildWindow = 0;
    RECT ClientRC;
    
    LONG WINAPI WindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM 
    LParam)
    {
      HDC DC;
      PAINTSTRUCT ps;
      RECT r;
      RECT r2;
      UINT uFlags;
    
      switch (Message)
      {
        case WM_PAINT:
        {
          DC = BeginPaint(Window, &ps);
          GetClientRect(Window, &ClientRC);
          GetWindowRect(Window, &r);
          SetRect(&r2, 0, 0, GetSystemMetrics(SM_CXSCREEN), 
    GetSystemMetrics(SM_CYSCREEN));
          uFlags = DPEx_CORRECTBRUSH;
          if (AData.WallPaperInfo.TileWallpaper != 0)
            uFlags = uFlags | DPEx_TILED;
          if (AData.WallPaperInfo.WallpaperStyle == 0)
            uFlags = uFlags | DPEx_CENTERED;
          DrawSkin(DC, DC, &ClientRC, &r, &r2, AData.WallPaperInfo.WallPaper, 
    AData.WallPaperInfo.DesktopBrush, uFlags);
          DrawEdge(DC, &ClientRC, BDR_SUNKENOUTER, BF_RECT);
          EndPaint(Window, &ps);
          return FALSE;
        }
        break;
        case WM_ERASEBKGND:
        {
          return TRUE;
        }
        break;
        case WM_ENTERSIZEMOVE:
        {
          SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) 
    & ~WS_EX_TRANSPARENT);
        }
        break;
        case WM_MOVING:
        {
          InvalidateRect(Window, NULL, FALSE);
        }
        break;
        case WM_EXITSIZEMOVE:
        {
          SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) 
    | WS_EX_TRANSPARENT);
          InvalidateRect(Window, NULL, FALSE);
        }
        break;
        case WM_NCHITTEST:
        {
          DefWindowProc(Window, Message, WParam, LParam);
          return HTCAPTION;
        }
      }
      return DefWindowProc(Window, Message, WParam, LParam);
    }
    
    void WINAPI InitGlobalModule(const PAstonData AstonData)
    {
      WNDCLASS WindowClass;
    
      if (AstonData->cbsize < sizeof(TAstonData))
        return;
      RtlMoveMemory(&AData, AstonData, sizeof(TAstonData));
    
      RtlZeroMemory(&WindowClass, sizeof(WNDCLASS));
      WindowClass.style = CS_HREDRAW | CS_VREDRAW;
      WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
      WindowClass.hInstance = hInstance;
      WindowClass.lpfnWndProc = WindowProc;
      WindowClass.lpszClassName = szClassName;
      if (RegisterClass(&WindowClass) == 0)
        return;
    
      ChildWindow = CreateWindowEx(WS_EX_TRANSPARENT, szClassName,
        NULL,
        WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
        0,
        0, 64,
        48, AData.Desktop,
        0,
        hInstance,
        NULL);
    
      AData.Node->Wnd = ChildWindow;
      GetClientRect(ChildWindow, &ClientRC);
    }
    
    void WINAPI DoneModule(void)
    {
      if (ChildWindow != 0)
      {
        DestroyWindow(ChildWindow);
        ChildWindow = 0;
      }
      UnregisterClass(szClassName, hInstance);
    }
    


    File: DeskWnd.def
    
    LIBRARY DeskWnd.plg
    EXPORTS
    	InitGlobalModule=_InitGlobalModule@4
    	DoneModule=_DoneModule@0
    


    Now, if you want to be brave, you can try compiling the plugin now. You will notice that everything will go perfect as long as everything is set correctly in the compiler, which is a given if you think about it! :)


    Part Two - The Explanation of Everything (well not everything)


    What shall we start with, eh? The short DEF file or the long C file... Let's go with the long C file. ;)

    Starting at the top:
    
    #pragma 
    comment(linker,"/DEF:DeskWnd.def")
    #pragma comment(linker,"/entry:DllMain")
    

    These two lines are simple. The pragma preprocessor directive tells the compiler before it starts to compile to do... well whatever you tell it to. These two lines add two sections to the linker. The first tells the linker to use a DEF file, which just gives some extra information on how to do things. The second tells the linker where to find the entry point, or rather what function you tell is the entry point. My suggestion is to keep these there. I haven't tried to compile and build without them, so I don't know what would happen if you did not. An idea I have if you leave out the DEF file, that Aston will not be able to properly read your plugin. As for the other, I think that the plugin itself would not initialize correctly, but I may be mistaken.

    Next section:
    
    #include <windows.h>
    
    #include "../rtlmem.h"
    
    #define NODEFAULTLIB
    
    #include "../astonsdk.h"
    #include "../st_api.h"
    

    These are the basic includes you will have for all plugins that you make. The first line is the Windows header file, which will include all information on function and constants and all that good stuff that makes your DLL about to work with Windows. The next include, the rtlmem.h file is just redefinitions of Windows functions. RtlZeroMemory, RtlFillMemory, and RtlMoveMemory are the only functions redefined. The next line is a define preprocessor directive. I will explain this after the next include. The next include is astonsdk.h. This is all the structures and messages and other goodies that make your plugin work with Aston. Now for the NODEFAULTLIB line. NODEFAULTLIB combined with the /entry: option, will reduce the size of your plugin, but will prohibit the use of libc.lib and CRT functions. Remove or comment both #pragma comment(linker,"/entry:DllMain") and #define NODEFAULTLIB if you need CRT functions. In the astonsdk.h file, there is a section that checks to see if there is a define named NODEFAULTLIB. If it is defined, it will include another pragma statement, which is just this: #pragma comment(linker,"/nodefaultlib"). Don't really worry about this much, if you really want to know more about it, either look it up at MSDN, or ask someone else. :P The next header, st_api.h, is just a bunch of function prototypes that enabled you to access things that Aston let's you.

    DllMain, the beginning of your plugin:
    
    HMODULE hInstance;
    
    BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID 
    lpReserved)
    {
        if (ul_reason_for_call == DLL_PROCESS_ATTACH)
        {
          DisableThreadLibraryCalls(hInstance = hModule);
        }
        return TRUE;
    }
    

    First off, hInstance is a global variable that will allow you to access it from wherever easily. It's an important variable, and besides, it's needed to create your plugin. :) I don't think I should really explain this. Just leave it how it is, and all will be fine.

    Variables:
    
    #define szClassName "DeskWnd"
    
    TAstonData AData;
    HWND ChildWindow = 0;
    RECT ClientRC;
    

    These are just the basic variables you'll have in a plugin. szClassName is the classname that will do many things. It will be the classname for your program's window. Aston keeps track of information for it via the classname. Plugin settings are usually kept under the classname heading. AData is a structure that Aston passes to the InitGlobalModule function later on. This gives your plugin data about Aston, and the ability to access things within the Aston settings. ChildWindow is the handle to the window that you will create for your plugin. ClientRC is a structure that keeps the location information of your window. That's all there is to it!

    The Window Procedure:
    
    LONG WINAPI WindowProc(HWND 
    Window, UINT Message, WPARAM WParam, LPARAM LParam)
    {
      HDC DC;
      PAINTSTRUCT ps;
      RECT r;
      RECT r2;
      UINT uFlags;
    
      switch (Message)
      {
        case WM_PAINT:
        {
          DC = BeginPaint(Window, &ps);
          GetClientRect(Window, &ClientRC);
          GetWindowRect(Window, &r);
          SetRect(&r2, 0, 0, GetSystemMetrics(SM_CXSCREEN), 
    GetSystemMetrics(SM_CYSCREEN));
          uFlags = DPEx_CORRECTBRUSH;
          if (AData.WallPaperInfo.TileWallpaper != 0)
            uFlags = uFlags | DPEx_TILED;
          if (AData.WallPaperInfo.WallpaperStyle == 0)
            uFlags = uFlags | DPEx_CENTERED;
          DrawSkin(DC, DC, &ClientRC, &r, &r2, AData.WallPaperInfo.WallPaper, 
    AData.WallPaperInfo.DesktopBrush, uFlags);
          DrawEdge(DC, &ClientRC, BDR_SUNKENOUTER, BF_RECT);
          EndPaint(Window, &ps);
          return FALSE;
        }
        break;
        case WM_ERASEBKGND:
        {
          return TRUE;
        }
        break;
        case WM_ENTERSIZEMOVE:
        {
          SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) 
    & ~WS_EX_TRANSPARENT);
        }
        break;
        case WM_MOVING:
        {
          InvalidateRect(Window, NULL, FALSE);
        }
        break;
        case WM_EXITSIZEMOVE:
        {
          SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) 
    | WS_EX_TRANSPARENT);
          InvalidateRect(Window, NULL, FALSE);
        }
        break;
        case WM_NCHITTEST:
        {
          DefWindowProc(Window, Message, WParam, LParam);
          return HTCAPTION;
        }
      }
      return DefWindowProc(Window, Message, WParam, LParam);
    }
    

    I'm not really going to explain much of this. This is just where all the messages that are sent to your plugin are handled. WM_PAINT is probably the most sent message to your window. It's where you will draw your plugin. You can look up the rest of the messages at the MSDN site. Remember I assume that you have a working knowledge of Win32 programming.

    InitGlobalModule, where Aston actually tells your plugin to make itself seen:
    
    void WINAPI 
    InitGlobalModule(const PAstonData AstonData)
    {
      WNDCLASS WindowClass;
    
      if (AstonData->cbsize < sizeof(TAstonData))
        return;
      RtlMoveMemory(&AData, AstonData, sizeof(TAstonData));
    
      RtlZeroMemory(&WindowClass, sizeof(WNDCLASS));
      WindowClass.style = CS_HREDRAW | CS_VREDRAW;
      WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
      WindowClass.hInstance = hInstance;
      WindowClass.lpfnWndProc = WindowProc;
      WindowClass.lpszClassName = szClassName;
      if (RegisterClass(&WindowClass) == 0)
        return;
    
      ChildWindow = CreateWindowEx(WS_EX_TRANSPARENT, szClassName,
        NULL,
        WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
        0,
        0, 64,
        48, AData.Desktop,
        0,
        hInstance,
        NULL);
    
      AData.Node->Wnd = ChildWindow;
      GetClientRect(ChildWindow, &ClientRC);
    }
    

    This... is where it all starts...... again. This is what Aston calls in your plugin to tell your plugin to start itself up. Let's go in sections to explain it. First, the function definition itself, don't mess with it. Leave it like it is. It makes things simple. Second, the variable(s). WNDCLASS WindowClass is the only variable here, there can be more depending on what you want to do. This will be your window class though, it's how your classname will be known throughout the system. As you might know there are already classes that are predefined, like a button, or a textbox (edit).
    Next is this section:
    
      if (AstonData->cbsize < 
    sizeof(TAstonData))
        return;
      RtlMoveMemory(&AData, AstonData, sizeof(TAstonData));
    

    This gets the Aston data that was sent along with the InitGlobalModule function. This allows you to access Aston's data... pretty self-explanatory, eh? :)
    Next is the window class stuff:
    
      RtlZeroMemory(&WindowClass, 
    sizeof(WNDCLASS));
      WindowClass.style = CS_HREDRAW | CS_VREDRAW;
      WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
      WindowClass.hInstance = hInstance;
      WindowClass.lpfnWndProc = WindowProc;
      WindowClass.lpszClassName = szClassName;
      if (RegisterClass(&WindowClass) == 0)
        return;
    

    This, in order from top to bottom, does this.

    1. Clears all the memory of the structure WindowClass
    2. Sets the style to CS_HREDRAW and CS_VREDRAW
    3. Sets the cursor to default arrow
    4. Sets the window class' instance to the instance of your plugin
    5. Sets the function that will be called to process messages sent to the window
    6. Sets the classname. I told you the szClassName variable is used for many things. ;)
    7. Trys to register the class. If it fails, the function will return, and nothing will be processed after that, meaning your plugin will not work if this happens.
    Well now that we've gotten that out of the way, let's go on to the next section. This section is where it all happens. You create your plugin window!
    
      ChildWindow = CreateWindowEx(WS_EX_TRANSPARENT, szClassName,
        NULL,
        WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
        0,
        0, 64,
        48, AData.Desktop,
        0,
        hInstance,
        NULL);
    
      AData.Node->Wnd = ChildWindow;
      GetClientRect(ChildWindow, &ClientRC);

    Aren't you happy now?! You've created your window! Instead of explaining this, I'll just give you a link to MSDN's website telling what the CreateWindowEx() function does. Once again, I said before I'd assume you had experience in Win32 programming. :) Here's the link: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/
    windowsuserinterface/windowing/windows/windowreference/windowfunctions/
    createwindowex.asp
    . Now for the next line of code. AData.Node->Wnd = ChildWindow; As I did not know what this did, I asked about it! The answer I received from Oleg himself, was this: "This instruction (AData.Node->Wnd = ChildWindow;) needs(means) that [the] plugin window got notification messages from Aston." aka. Aston knows your plugin is there, hence it will work. This line is REQUIRED. Always remember to have it there. The next line isn't really required, but it's good to have just in case. Feel free to remove it if you want.

    The DoneModule function:
    
    void WINAPI DoneModule(void)
    {
      if (ChildWindow != 0)
      {
        DestroyWindow(ChildWindow);
        ChildWindow = 0;
      }
      UnregisterClass(szClassName, hInstance);
    }

    This basically just removes your window from Aston, and then unregisters your class so no other plugin that for some unlikely reason has the same classname as yours and conflicts and can't create itself.

    Now for the DeskWnd.def file. This is really simple.
    
    LIBRARY DeskWnd.plg
    EXPORTS
    	InitGlobalModule=_InitGlobalModule@4
    	DoneModule=_DoneModule@0

    This is two sections. LIBRARY and EXPORTS. LIBRARY tells the linker what to name the outputted DLL. Keep this and the Output file name in the Project settings the same, otherwise the linker will give you a warning. EXPORTS tells what functions to export, meaning what functions are able to be called if the DLL is to be used. In this case, two functions are exported. They are very important to the success of your plugin. Aston calls both these functions to create and destroy your plugin.


    Part Three - The End of All Things (No seriously, this is the end, dude)


    Well this is the end. You've created your own desktop plugin for Aston. Feel free to do whatever you want, this is what you're suppose to do anyway!

    Now, I'm sure many of you are sitting there going, "But wait! This can't be the end! What about plugin settings?!?!" Making plugin settings, although simple by most means, is a little hard to start with. My next article will explain about adding plugin settings. After that I will explain about system tray plugins. Then I will explain about taskbar plugins. System tray and taskbar plugins are generally the same, but only where they are placed and how the code works is how they are different.

    Download the VC6 source: VC6 Source
    Download the VC7 source: VC7 Source


    Part Four - Thanks and other stuff (And you thought part three was the end? ;))


    First off, I'd like to thank Gladiators Software for creating such a great shell. Without Aston, we'd probably be in some other shell, and it's dark, and scary, and and... ahem. I'd also like to thank ~mycroes as he has helped me with some Win32 before. A HUGE thanks to Oleg for explaining a lot of mistakes and errors in this tutorial, without him, I don't know what we'd do. :) And finally thanks to The_PC_Mechanic for reviewing this initially!

    I don't think that anyone would really take this and put it somewhere else on the Net, but if someone for some crazy reason decides to, please e-mail me first: veratil_(at)hotmail(dot)com

     
         
    Copyright © 1999-2024 Gladiators Software