IEのキャッシュを利用する

この記事は最終更新日から 19 年以上が経過しています。

僕は修論でコミュニケーションログを扱う研究をしておりまして、その中で過去に見たブログなんかもコミュニケーションログとして扱いたいよね、ということになり、IEのキャッシュからHTMLファイルを取ってくることになりました。
今回はその方法を記します。

と言っても、そんなに難しくないです。

IEが溜めたキャッシュを利用するには、キャッシュフォルダから目的のキャッシュファイルを見付けてきて利用するのですが、FindFirstUrlCacheEntry と FindNextUrlCacheEntry という2つのWin32APIを使うと便利です。
この関数は、キャッシュフォルダを検索し、キャッシュファイルの情報を INTERNET_CACHE_ENTRY_INFOA構造体の形で渡してくれます。
この構造体には、キャッシュファイルの場所、取得元のURI、アクセス回数など様々な情報が含まれているので、その情報を見ながらキャッシュファイルを利用するといいでしょう。

C#から使うには、以下のようにします。
まずは構造体とWin32APIの宣言です。

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit, Size = 80)]
public struct INTERNET_CACHE_ENTRY_INFOA {
	[FieldOffset(0)]
	public uint dwStructSize;
	[FieldOffset(4)]
	public IntPtr lpszSourceUrlName;
	[FieldOffset(8)]
	public IntPtr lpszLocalFileName;
	[FieldOffset(12)]
	public uint CacheEntryType;
	[FieldOffset(16)]
	public uint dwUseCount;
	[FieldOffset(20)]
	public uint dwHitRate;
	[FieldOffset(24)]
	public uint dwSizeLow;
	[FieldOffset(28)]
	public uint dwSizeHigh;
	[FieldOffset(32)]
	public long LastModifiedTime;
	[FieldOffset(40)]
	public long ExpireTime;
	[FieldOffset(48)]
	public long LastAccessTime;
	[FieldOffset(56)]
	public long LastSyncTime;
	[FieldOffset(64)]
	public IntPtr lpHeaderInfo;
	[FieldOffset(68)]
	public uint dwHeaderInfoSize;
	[FieldOffset(72)]
	public IntPtr lpszFileExtension;
	[FieldOffset(76)]
	public uint dwReserved;
	[FieldOffset(76)]
	public uint dwExemptDelta;
}

[DllImport(@"wininet",
	SetLastError = true,
	CharSet = CharSet.Auto,
	EntryPoint = "FindFirstUrlCacheEntryA",
	CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr FindFirstUrlCacheEntry(
	[MarshalAs(UnmanagedType.LPTStr)] string lpszUrlSearchPattern,
	IntPtr lpFirstCacheEntryInfo,
	ref int lpdwFirstCacheEntryInfoBufferSize);

[DllImport(@"wininet",
	SetLastError = true,
	CharSet = CharSet.Auto,
	EntryPoint = "FindNextUrlCacheEntryA",
	CallingConvention = CallingConvention.StdCall)]
public static extern bool FindNextUrlCacheEntry(
	IntPtr hFind,
	IntPtr lpNextCacheEntryInfo,
	ref int lpdwNextCacheEntryInfoBufferSize);

public enum ERROR {
	FILE_NOT_FOUND = 0x2,
	NO_MORE_ITEMS = 259,
}

使い方はこんな感じです。

int cacheEntryInfoBufferSizeInitial = 0;
int cacheEntryInfoBufferSize = 0;
IntPtr cacheEntryInfoBuffer = IntPtr.Zero;
IntPtr enumHandle = IntPtr.Zero;

// キャッシュ情報のサイズを得るための仮取得
enumHandle = FindFirstUrlCacheEntry(null, IntPtr.Zero, ref cacheEntryInfoBufferSizeInitial);

// 取得に失敗した場合
if (enumHandle != IntPtr.Zero && (int)ERROR.NO_MORE_ITEMS == Marshal.GetLastWin32Error())
	return;

// キャッシュ情報を取得するためのメモリ領域を確保
cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize);
// キャッシュ情報を取得
enumHandle = FindFirstUrlCacheEntry(null, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);

while (true) {
	bool returnValue = false;

	// 取得した情報を構造体として扱えるようにする
	INTERNET_CACHE_ENTRY_INFOA internetCacheEntry = (INTERNET_CACHE_ENTRY_INFOA)Marshal.PtrToStructure(cacheEntryInfoBuffer, typeof(INTERNET_CACHE_ENTRY_INFOA));

	// キャッシュ情報を分析(string型にコピー)
	string uri = Marshal.PtrToStringAnsi(internetCacheEntry.lpszSourceUrlName);
	string path = Marshal.PtrToStringAnsi(internetCacheEntry.lpszLocalFileName);


	/* 取得した情報を配列に保存するなりリストに追加するなりして使う */


	// 次のキャッシュ情報を取得
	cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize;
	returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
	if (!returnValue && (int)ERROR.NO_MORE_ITEMS == Marshal.GetLastWin32Error()) {
		break;
	}
	// バッファのサイズが足りなかったら拡張
	if (!returnValue && cacheEntryInfoBufferSizeInitial > cacheEntryInfoBufferSize) {
		cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
		cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, (IntPtr)cacheEntryInfoBufferSize);
		returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
	}
}
// 利用したメモリ領域を開放
Marshal.FreeHGlobal(cacheEntryInfoBuffer);

大雑把に書きましたが、このようにして、例えばキャッシュファイルの中からmixi日記を探し出して利用するなんてことも出来ました。
コメントの説明がいい加減で申し訳ないです。