Win32 Relative Shortcut Stub

From qtnode

Jump to: navigation, search

So, you are able to compile .exe files. The next thing that you will likely wish to do is share or sell your fantastic Qt application. Qt applications rely on a minimum of two .dll files (QtCore4.dll and QtGui4.dll) to co-exist along with your .exe. These are necessary for your application to be able to run on a Win32 machine that does not have Qt installed. Additional .dll's may need to be included, such as mingwm10.dll (if you used MinGW to compile your software), or .dll's for database support, and so on. Additionally, there is the option of using a fancy Windows installer to manage necessary .dll's, automatically copying them into the appropriate directories of the application path when installing on the client machine. As an alternative to utilizing separate software to perform this installation, we provide an outline of a process that will allow you to distribute a simple .zip file that when unzipped, shows a single .exe and subdirectory containing all necessary libraries (.dll's), configuration, graphics and sound and files:

1.) First, copy all files and directory structures that are necessary to run your executable into a single directory. Qt's .dll's need to be in the same directory as your .exe file in order for your executable to be able to find them without user intervention. This can be confusing for simple users. A user might be unsure, left pondering to him/herself, "Which file do I click to start the application?"

2.) Now that we have a working program in a single directory, we need to make another .exe file that knows how to move into that directory and run your Qt application, setting the working directory to where all the support .dll's are located. Provided is a code sample to accomplish this. It is a modified, default main.cpp file generated from Dev-C++ using MinGW as the compiler. This code discovers its own current name (as an .exe file), and looks for a subdirectory of the same name. If one is found, it changes the working directory to that subdirectory, then looks for another .exe file (in that subdirectory) that has the same name as itself (your actual Qt application), and initiates the program. This way, you can re-use the same .exe file without re-compiling for all of your distributions.

For example: You have a Qt application by the name of MyApp.exe. You would create a directory named MyApp and place MyApp.exe and all required support files (dll's, graphics, sounds, conf, etc.) inside that directory. Then you take this 'WinStub' .exe and place it at the same level as the MyApp dir, and rename it to MyApp.exe. Now you have a single MyApp.exe and a single MyApp directory. When the user runs the 'WinStub' MyApp.exe, it sees the MyApp directory, changes the working directory into it, and runs your actual Qt MyApp.exe that is inside the directory along with all of your other necessary files - nice and clean. This way, the user is presented with a single .exe file that can locate the Qt program and run it regardless of whether it is on a mapped drive letter, local drive, or a UNC network path. You can use a nice custom icon for Windows Explorer to display when browsing the directory that contains it. You can be assured that your program will be run from its own directory, and will start the way that you expect, regardless of where the actual files are located.

#include <windows.h>
#include <stdio.h>
#include <shellapi.h>
#include <string.h>
#include <direct.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "WindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nFunsterStil)

{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    // examine the command line
    int i, appStart, appEnd;
    char appPath[255];
    char appName[255];
    char cmd[255];

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default color as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           "Windows App",       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           544,                 /* The programs width */
           375,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    //ShowWindow (hwnd, nFunsterStil);
    // Don't want window to show up, just want to look for program to start and 
    // start it if there and exit

   sprintf(appPath, "%s", GetCommandLine());
   // parse
   //sprintf(cmd, "pathLen = %d", strlen(appPath));
   //MessageBox(hwnd, cmd, "Debug", MB_OK);
   for(i = strlen(appPath) - 1; i > 0; i--)
   {
      if(appPath[i] == '.')
      {
           appEnd = i;              
           //sprintf(cmd, "appEnd = %d", appEnd);
           //MessageBox(hwnd, cmd, "Debug", MB_OK);
      } else if(appPath[i] == '\\') {
           appStart = i   1;
           //sprintf(cmd, "appStart = %d", appStart);
           //MessageBox(hwnd, cmd, "Debug", MB_OK);
           break;
      }
   }
   // now build name
   for(i = appStart; i < appEnd; i  )
   {
         appName[i - appStart] = appPath[i];
   }
   appName[i - appStart] = '\0';
   appPath[appStart] = '\0';
   //MessageBox(hwnd, appPath, "App Path", MB_OK);
   
   // change working dir
   sprintf(cmd, "%s%s", appPath, appName);
   if(_chdir(cmd)){
       MessageBox(hwnd, cmd, "Dir Not Found!", MB_OK);
       return 0;
   }                    
   
   // run program
   sprintf(cmd, "%s%s\\%s.exe", appPath, appName, appName);
   if(WinExec(cmd, SW_SHOW) > 31){
      // sucess
   } else {
      MessageBox(hwnd, cmd, "Exe not Found!", MB_OK);
   }
   
    /* Run the message loop. It will run until GetMessage() returns 0 */
    //while (GetMessage (&messages, NULL, 0, 0))
    //{
        /* Translate virtual-key messages into character messages */
    //    TranslateMessage(&messages);
        /* Send message to WindowProcedure */
    //    DispatchMessage(&messages);
    //}

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    //return messages.wParam;
    return 0;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

   return 0;
}

3.) Now that we have a nice clean structure, simply zip up the entire thing, so that it extracts the 'winstub' .exe and the AppDir in the same place, and you are ready to go. There are no worries about versions, or dependencies, both software and human.

Enjoy!

Personal tools