2006 年 10 月 18 日 23 時 56 分

フォルダリンクを作成する


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


さて、フォルダリンクをプログラムから作成してみよう。
手作業でも作成できるのだが、少し手間がかかるので、
別途ユーティリティ化しておけば便利である。

まずは、ヘッダファイル。カテゴリは、FolderLink とし、
作成関数は FolderLinkCreate と命名することにする。

========== folderlink.h ==========

/* インクルードガード */
#ifndef folderlink_h_included
#define folderlink_h_included

#include <windows.h>
#include <ole2.h>

/* プロトタイプ定義 */
HRESULT FolderLinkCreate(const wchar_t *dirName,
        const wchar_t *target);

#endif /* !folderlink_h_included */

========== end of folderlink.h ==========

ヘッダは、必要最小限が原則だ。
単に HRESULT を使うがためだけに、
<ole2.h> を取り込むのは気が引けるが、仕方ないか。

続いて、実装ファイル

========== folderlink.c (1st half) ==========

#define UNICODE
#define STRICT
#define WIN32_LEAN_AND_MEAN

#include "folderlink.h"

/* ShortcutCreate */
#include "lnkfile.h"

/* wcslen */
#include <string.h>

/* swprintf */
#include <stdio.h>

/* 文字列定数 */
#define FLINK_CLSID      L"{0AFACED1-E828-11D1-9187-B532F1E9575D}"
#define FLINK_INI_SUFFIX L"\\desktop.ini"
#define FLINK_LNK_SUFFIX L"\\target.lnk"

========== end of folderlink.c (1st half) ==========

フォルダリンクは、内部にショートカットを持つので、
ShortcutCreate 関数を利用して作成する。
そのため、関数定義のヘッダファイルを取り込んでおく。

フォルダリンクでは、幾つかのファイルを扱うため、
ファイル名や、クラス ID などを定数化しておく。

今回は #define でマクロにしてみた。
static const wchar_t *const FLINK_XXX = L"~"; と
実体を持つ厳格型の定数値として定義しても構わない。
まあ、このあたりは好みの問題ではある。

では、関数の実装を作る。

========== folderlink.c (2nd half) ==========

HRESULT FolderLinkCreate(const wchar_t *dirName,
        const wchar_t *target) {

    wchar_t iniPath[MAX_PATH];
    size_t  iniPathLength;

    wchar_t lnkPath[MAX_PATH];
    size_t  lnkPathLength;

    HRESULT result = S_OK;

    /* desktop.ini と target.lnk のパス文字列長を計算 */
    iniPathLength = wcslen(dirName) + wcslen(FLINK_INI_SUFFIX);
    lnkPathLength = wcslen(dirName) + wcslen(FLINK_LNK_SUFFIX);

    /* パスがバッファに入らない場合は、未実装ということに */
    if (iniPathLength >= MAX_PATH) return E_NOTIMPL;
    if (lnkPathLength >= MAX_PATH) return E_NOTIMPL;

    /* desktop.ini と target.lnk の完全パスを作成 */
    swprintf(iniPath, L"%s%s", dirName, FLINK_INI_SUFFIX);
    swprintf(lnkPath, L"%s%s", dirName, FLINK_LNK_SUFFIX);

/* ここから本番 */

    /* ディレクトリを作成 */
    if (!CreateDirectory(dirName, NULL))
            return HRESULT_FROM_WIN32(GetLastError());

    /* target.lnk を作成 */
    result = ShortcutCreate(lnkPath, target);
    if (FAILED(result)) goto failure;

    /* desktop.ini を作成 */
    if (!WritePrivateProfileString(L".ShellClassInfo", L"CLSID",
            FLINK_CLSID, iniPath)) {
        result = HRESULT_FROM_WIN32(GetLastError());
        goto failure;
    }
           
    /* ディレクトリを読み取り専用に */
    if (!SetFileAttributes(dirName, FILE_ATTRIBUTE_READONLY)) {
        result = HRESULT_FROM_WIN32(GetLastError());
        goto failure;
    }

    /* OK */
    return S_OK;

/* 失敗 */
failure:

    /* desktop.ini を削除 */
    DeleteFile(iniPath);

    /* target.lnk を削除 */
    DeleteFile(lnkPath);

    /* ディレクトリを削除 */
    RemoveDirectory(dirName);

    return result;

}

========== end of folderlink.c (2nd half) ==========

長くなったが順に見ていこう。

最初に、関数が作成して特殊化するファイルフォルダ内に、
含まれる desktop.ini と target.lnk のパスを作る。

iniPath、lnkPath のバッファは C 言語特有だ。
C 言語には、高級言語のような文字列サポートがない。
基本は文字配列に終端記号 \0 を組み合わせて文字列とする。
単に + で連結することもできないので、
文字列の長さを注意深く計算してコピーする。

MAX_PATH (=260) は、Windows のパス文字列の最大長だ。
Windows では暗黙の了解となっている定数でもある。
ファイルシステムによっては、
これ以上の長さを扱うこともできるのだが、
その場合は E_NOTIMPL を返してごまかすことにしよう。
まあ MAX_PATH 以上を扱えるプログラムはあまりないしね。

パスが作成できたら、いよいよ作成に入る。
ディレクトリは、CreateDirectory で作成できる。

COM ではない Windows の API 関数は、
失敗すると 0、成功すると 0 以外を返す物が多く、
エラーコードは、GetLastError という別の関数で得る。

このエラーコードは、Windows 内部番号である。
COM の HRESULT とはまた異なる種類の値だ。
そのため、HRESULT_FROM_WIN32 マクロを使って、
エラーコードを HRESULT に変換してエラーを返す。

フォルダの中に作成する target.lnk は、
以前作った ShortcutCreate 関数を使うと一発だ。

そして、desktop.ini だが、これも簡単だ。
ini は、初期の Windows でレジストリの代用として、
設定情報を保存するために使われていた。
そのため、専用の API を持っているのだ。

WritePrivateProfileString 関数を使うと、
以下の desktop.ini を一発で書き込むことができる。

[.ShellClassInfo]
CLSID={0AFACED1-E828-11D1-9187-B532F1E9575D}

.ShellClassInfo がセクション名、CLSID がキー名、
{0AFACED1-E828-11D1-9187-B532F1E9575D} が値、
~\desktop.ini がファイル名となり、
それらを関数のパラメータに渡してやるだけで OK だ。

最後に、ディレクトリを読み取り専用にする。
これは、SetFileAttributes 関数で処理する。

関数は、できる限り原子性を持つように設計したほうが良い。
原子性というのは、1 か 0 かということで、
もし問題が生じて失敗した場合でも、中途半端な状態にせず、
まるで、何も行わなかったかのように、
元の状態に戻しておかなければならないということである。

これは、主にデータベースで言われる重要な概念だが、
たとえ単なる関数であったとしても実装することが望ましい。

最後にある、failure ラベル以降は、
関数が作成したものを削除するコードを書いている。

CreateDirectory 関数は原子的であり、
既にディレクトリが存在した場合はエラーとなる。
そのため、failure に飛ぶのは、
関数がディレクトリを作成した場合のみである。

ディレクトリがないと、中のファイルは存在できないので、
ディレクトリ内のファイルを削除しても問題はない。

本来は、入力されるディレクトリ名やリンク先名の、
NULL チェックや記号チェックなども必要だが、
そのあたりを書くと非常に長くなるので省略している。

program.c を書き換えてこの関数を呼び出してみると、
実際にフォルダリンクが作成されることが確認できた。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.