Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Sending a Filename to the Clipboard - Making a Miniature Tool Without Using CRT

4.33/5 (26 votes)
26 Oct 2009CPOL6 min read 41.6K   304  
Name2Clip sits in the Sent To menu of Windows Explorer and sends selected filenames to the system clipboard.

A shortcut to Name2Clip tool is placed in the "Send To" menu of Windows Explorer:

A shortcut to Name2Clip tool

Contents

Introduction

Everybody knows how tedious it can be to copy full paths to files and folders from Windows Explorer to the Clipboard. The primary goal of Name2Clip is to fix that problem. Name2Clip is a tiny utility that sends its command line arguments to the system clipboard. Name2Clip can be used with any number of strings as command line parameters. The utility doesn't care much about the content of the strings. It just copies strings to the system clipboard as text where each string occupies a new line. This article shows how to use "Send To" context menu of Windows Explorer together with Name2Clip in order to copy full paths of selected files and folders to the Clipboard.

In order to keep things as small and as light as possible, Name2Clip doesn't use any external framework or library. It doesn't even use C Run-time library (CRT). Name2Clip is implemented in C programming language and calls strictly Windows API functions. Thanks to this, Name2Clip.exe is lightning fast and its size is about mere 7 KB.

Usage

Name2Clip can run on any Windows system starting from Windows 2000 and above. No installation is required. Here's the usage summary:

Name2Clip [/c] [/s] [/n] params
   /c - Coding friendly format: '\' replaced by '\\'.
   /s - Paths are converted to MS-DOS 8.3 format.
   /n - Only names rather than full paths are copied.

When launched without parameters, Name2Clip shows its usage.

In addition to command line switches, Name2Clip checks the keyboard state when launched. So, if a certain key is pressed, Name2Clip detects it and behaves as if a corresponding command line switch was specified. Here is the list of supported keys:

  • Ctrl key is pressed (equivalent to the /c switch) - double backslashes are inserted. Example: C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe
  • Shift key is pressed (equivalent to the /s switch) - MS-DOS 8.3 name is placed into the Clipboard. Example: C:\PROGRA~1\WINDOW~2\ACCESS~1\wordpad.exe
  • Ctrl+Shift keys are pressed - both of the above. Example: C:\\PROGRA~1\\WINDOW~2\\ACCESS~1\\wordpad.exe

Thanks to Nick Carruthers and his article Copy Path Context Menu Extension for the idea about these keys.

How to Use Name2Clip with "Send To" Context Menu

The "Send To" menu of Windows Explorer is created from the contents of special SendTo folder. The folder's location is as follows:

  • Windows XP: %USERPROFILE%\SendTo, where %USERPROFILE% usually expands to: C:\Documents and Settings\{username}
  • Windows Vista and Windows 7: %APPDATA%\Microsoft\Windows\SendTo, where %APPDATA% is usually expands to: C:\Users\{username}\AppData\Roaming

When a link is placed in the SendTo folder, then link's name will be shown in the "Send To" context menu in the Windows Explorer. When user selects the link from "Send To" menu, then full paths to currently selected files and folders are passed as command line parameters to the program specified in the link. All we need to do is to place a link to Name2Clip to SendTo folder.

Here is the detailed description of how to add an item to the "Send To" menu: Add an item to the Send To menu. The article relates to Windows XP, however the process is essentially the same for any Windows platform. Only the location of the SendTo folder is different.

Background

So, why develop an application that doesn't use CRT? Here are a couple of reasons:

  • Independency. An application that doesn't use CRT is almost completely independent of development environment. Any decent C compiler can be utilized. Alternatively, to achieve a similar level of independency, a program could be linked statically with the CRT. However, it will increase the program's size.
  • Smallest possible size. By using exclusively Windows API functions, a program can achieve the smallest possible size on Windows platform. To make it even smaller, one probably would have been required to use assembler as a development tool. But let's be sane.
  • It's fun. Most likely, you figured out already that the above points are moot. Nowadays nobody in their sane mind would start serious development without modern tools. It includes CRT support, of course. However, Name2Clip is so tiny and simple that making it without CRT might be fun. This is the true reason of the exercise.

Sometimes, there are legitimate reasons to make a program without CRT. Usually it is a device driver, a code that runs in embedded environment and other close to hardware applications. Regular desktop applications shouldn't be developed that way. Actually, modern desktop applications shouldn't be developed in a native language at all.

Here is the price Name2Clip paid to be free from CRT:

  • No checks for stack frame run-time errors:
    • Detection of overruns and underruns of local variables such as arrays.
    • Stack pointer verification, which detects stack pointer corruption.
  • No exception handling whatsoever. Any attempt to use __try, __except, __finally blocks will result in unresolved externals linker errors. Exception handling mechanisms are tightly coupled with CRT.
  • No optimizations for size. Many language constructs usually are replaced by the compiler with CRT routines. For example, this code:
    C++
    int arr[10] = { 0 };

    will fail to link since the compiler inserts a call to memset function behind the scenes to initialize the array with zeroes.

  • No CRT debug-time infrastructure: no diagnostics like ASSERTs, no memory corruption checks, no memory leaks checks, etc.
  • A lot of useful functions are unavailable. Everything must be reinvented and reimplemented from scratch.

So, the price is pretty high. Beware!

Using the Code

Since we don't use CRT, we need to specify our own entry point to the program. We can do it by using /ENTRY linker switch. However, MSDN is wrong about the signature of the entry point function. From the "/ENTRY (Entry-Point Symbol)" article:

The function must be defined with the __stdcall calling convention. The parameters and return value must be defined as documented in the Win32 API for WinMain (for a .exe file) or DllEntryPoint (for a DLL).

This is incorrect. The actual function signature of the program entry point must be: int __cdecl(*)(void) or int __stdcall(*)(void). Calling convention is not important, since the function doesn't receive any arguments, so there is no need to clean the stack.

Here is the entire code of Name2Clip:

C++
// Name2Clip.c : Defines the entry point for the application.
//

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#define STRICT
#define _WIN32_WINNT    0x0500      // Win2K

#include <Windows.h>
#include <ShellApi.h>
#include <WindowsX.h>

#include "resource.h"

/////////////////////////////////////////////////////////////////////////////
//
UINT CalcDataLength(
    LPCWSTR* ppArgv,
    int nArgc,
    UINT nFlags);

LPWSTR FormatCopyData(
    LPCWSTR* ppArgv,
    int nArgc,
    UINT nFlags,
    LPWSTR pDst);

UINT GetFlags(
    LPCWSTR* ppArgv,
    int nArgc);

UINT GetKeyboardFlags();

UINT GetCmdLineFlags(
    LPCWSTR* ppArgv,
    int nArgc);

LPCWSTR FindFilename(
    LPCWSTR pcwsz);

void ShowUsage(void);

/////////////////////////////////////////////////////////////////////////////
//
#define N2C_CODEFRIENDLY    0x01    // /C switch
#define N2C_SHORTPATHS      0x02    // /S switch
#define N2C_NAMESONLY       0x04    // /N switch

/////////////////////////////////////////////////////////////////////////////
//
int wN2ClipMain(void)
{
    int nArgc = 0;
    LPCWSTR* ppArgv = CommandLineToArgvW(GetCommandLineW(), &nArgc);

    if(!ppArgv) return -1;

    if(nArgc > 1)
    {
        UINT nFlags = GetFlags(ppArgv, nArgc);

        UINT nDataLen = CalcDataLength(ppArgv, nArgc, nFlags);

        LPWSTR pwszCopy = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT,
            nDataLen * sizeof(WCHAR));

        if(pwszCopy)
        {
            HWND hWndDesktop = GetDesktopWindow();

            FormatCopyData(ppArgv, nArgc, nFlags, pwszCopy);
            GlobalUnlockPtr(pwszCopy);

            if(OpenClipboard(hWndDesktop))
            {
                if(EmptyClipboard())
                    SetClipboardData(CF_UNICODETEXT, GlobalPtrHandle(pwszCopy));

                CloseClipboard();
            }
        }

        GlobalFreePtr(ppArgv);
    }
    else ShowUsage();

    return 0;
}

UINT CalcDataLength(
    LPCWSTR* ppArgv,
    int nArgc,
    UINT nFlags)
{
    UINT nLen = 0;
    int i;

    for(i = 1; i < nArgc; i++)
    {
        int nExtraLen = 0;
        int nPathLen = 0;
        LPCWSTR pPath = ppArgv[i];

        if(*pPath == L'/') continue;    // it's a switch

        if(nFlags & N2C_NAMESONLY)
        {
            pPath = FindFilename(pPath);
        }
        else if(nFlags & N2C_CODEFRIENDLY)
        {
            LPCWSTR pEos = pPath;

            while(*pEos)
            {
                if(*pEos == '\\') nExtraLen++;
                pEos++;
            }
        }

        if(nFlags & N2C_SHORTPATHS)
        {
            if(nFlags & N2C_NAMESONLY)
                nPathLen = 12;  // 8.3 name
            else
                nPathLen = GetShortPathNameW(pPath, NULL, 0);
        }
        else
        {
            nPathLen = lstrlenW(pPath);
        }

        nLen += nPathLen + nExtraLen + 2 /*CR+LF*/;
    }

    nLen = nLen + 1 /*NULL*/;

    return nLen;
}

LPWSTR FormatCopyData(
    LPCWSTR* ppArgv,
    int nArgc,
    UINT nFlags,
    LPWSTR pDst)
{
    LPWSTR pCopyData = pDst;
    int i;
    WCHAR wszPath[MAX_PATH + 1];

    for(i = 1; i < nArgc; i++)
    {
        LPCWSTR pSrc = ppArgv[i];

        if(*pSrc == L'/') continue; // it's a switch

        if(nFlags & N2C_SHORTPATHS)
        {
            DWORD dwLen = GetShortPathNameW(pSrc, wszPath, MAX_PATH);

            if(dwLen != ERROR_INVALID_PARAMETER)
                pSrc = wszPath;
            /* else */
            /* The volume doesn't support short filenames */
        }

        if(nFlags & N2C_NAMESONLY)
            pSrc = FindFilename(pSrc);

        while((*pDst = *pSrc) != 0)
        {
            if((nFlags & N2C_CODEFRIENDLY) && (*pSrc == L'\\'))
            {
                pDst++; *pDst = L'\\';
            }

            pDst++; pSrc++;
        }

        if(i < nArgc - 1)
        {
            *pDst++ = L'\r';
            *pDst++ = L'\n';
        }
    }

    return pCopyData;
}

UINT GetFlags(
    LPCWSTR* ppArgv,
    int nArgc)
{
    UINT nKbdFlags = GetKeyboardFlags();
    UINT nCmdFlags = GetCmdLineFlags(ppArgv, nArgc);

    return (nKbdFlags | nCmdFlags);
}

UINT GetKeyboardFlags()
{
    UINT nFlags = 0;

    if(GetAsyncKeyState(VK_CONTROL) & 0x8000)
        nFlags |= N2C_CODEFRIENDLY;

    if(GetAsyncKeyState(VK_SHIFT) & 0x8000)
        nFlags |= N2C_SHORTPATHS;

    return nFlags;
}

UINT GetCmdLineFlags(
    LPCWSTR* ppArgv,
    int nArgc)
{
    UINT nFlags = 0;
    int i, j;

    const struct Switches
    {
        LPCWSTR pcwszSwitch;
        UINT    nFlag;
    } switches[] = {
        { L"/C",  N2C_CODEFRIENDLY },
        { L"/S",  N2C_SHORTPATHS   },
        { L"/N",  N2C_NAMESONLY    }
    };

    for(i = 0; i < nArgc; ++i)
    {
        for(j = 0; j < ARRAYSIZE(switches); ++j)
        {
            if(lstrcmpiW(ppArgv[i], switches[j].pcwszSwitch) == 0)
            {
                nFlags |= switches[j].nFlag;
                break;
            }
        }
    }

    return nFlags;
}

LPCWSTR FindFilename(
    LPCWSTR pcwsz)
{
    LPCWSTR pcwszStart = pcwsz;

    /* find end of string */
    while(*pcwsz++);

    /* search towards front */
    while(--pcwsz != pcwszStart && *pcwsz != L'\\' && *pcwsz != L'/');

    /* char found ? */
    if (*pcwsz == L'\\' || *pcwsz == L'/')
        pcwsz++;

    return pcwsz;
}

void ShowUsage(void)
{
    MSGBOXPARAMS mbp = { 0 };
    mbp.cbSize       = sizeof(MSGBOXPARAMS);
    mbp.hwndOwner    = GetDesktopWindow();
    mbp.hInstance    = GetModuleHandle(NULL);
    mbp.lpszText     = MAKEINTRESOURCE(IDS_USAGE);
    mbp.lpszCaption  = MAKEINTRESOURCE(IDS_CAPTION);
    mbp.dwStyle      = MB_USERICON | MB_OK;
    mbp.lpszIcon     = MAKEINTRESOURCE(IDI_NAME2CLIP);
    mbp.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);

    MessageBoxIndirect(&mbp);
}

Known Limitations

According to MSDN, the maximum length of command line string for CreateProcess function is 32,768 characters. Name2Clip is started by Windows Explorer with selected filenames as command line parameters. So there is a limitation on the number of files that can be sent to the Clipboard using Name2Clip. Even though 32K of characters is not a small buffer, sometimes it can be exceeded, too. When there are too many files selected, the system shows the following dialog box:

Command line is too long

The workaround is to copy less filenames at a time.

History

  • 29th August, 2009: Initial post
  • 26th October, 2009: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)