potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# SearchPath関数を使用して既定の順番でパスを検索する

概要

Windowsではファイル名を指定された場合のパスの検索順序がレジストリに保存されており、「ファイル名を指定して実行」やLoadLibrary関数等ではこの順序が使用されます。この検索機能はSearchPath関数で公開されており、C#でもP/Invokeにより使用することができます。以下ではSearchPath関数の使用例を紹介します。

SearchPath関数の使用例

C#11のコード

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

Console.WriteLine(Utility.SearchPath("user32.dll"));

public static class Utility
{
    private static class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
        public static extern uint SearchPathW(
            ref char lpPath,
            ref char lpFileName,
            ref char lpExtension,
            uint nBufferLength,
            ref char lpBuffer,
            IntPtr lpFilePart);
    }

    /// <summary>
    /// ファイルの場所を検索します。
    /// </summary>
    /// <param name="fileName">検索する名前を指定します。</param>
    /// <param name="path">検索する場所を指定します。nullの場合はシステムの既定の場所を検索します。nullと空白は区別されます。</param>
    /// <param name="extension">検索する拡張子を指定します。nullと空白は区別されます。</param>
    /// <returns></returns>
    public static string? SearchPath(
        ReadOnlySpan<char> fileName,
        ReadOnlySpan<char> path = default,
        ReadOnlySpan<char> extension = default)
    {
        var len = NativeMethods.SearchPathW(
            ref MemoryMarshal.GetReference(path),
            ref MemoryMarshal.GetReference(fileName),
            ref MemoryMarshal.GetReference(extension),
            0, ref Unsafe.NullRef<char>(), IntPtr.Zero);
        Span<char> buffer = stackalloc char[(int)len];
        var ret = NativeMethods.SearchPathW(
            ref MemoryMarshal.GetReference(path),
            ref MemoryMarshal.GetReference(fileName),
            ref MemoryMarshal.GetReference(extension),
            len, ref MemoryMarshal.GetReference(buffer), IntPtr.Zero);
        return ret == 0 ? null : buffer[..(int)ret].ToString();
    }
}

2019年版 注意:この記事はDLLハイジャッキング対策に関する情報が加筆前です。

コード

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var user32DllPath = Utility.SearchPath("user32.dll");
        }
    }

    public static class Utility
    {
        private static class NativeMethods
        {
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern uint SearchPath(
                [In]string lpPath,
                [In]string lpFileName,
                [In]string lpExtension,
                uint nBufferLength,
                StringBuilder lpBuffer,
                IntPtr lpFilePart);
        }

        /// <summary>
        /// ファイルの場所を検索します。
        /// </summary>
        /// <param name="fileName">検索する名前を指定します。</param>
        /// <param name="path">検索する場所を指定します。nullの場合はシステムの既定の場所を検索します。nullと空白は区別されます。</param>
        /// <param name="extension">検索する拡張子を指定します。nullと空白は区別されます。</param>
        /// <returns></returns>
        public static string SearchPath(
            string fileName,
            string path = null,
            string extension = null)
        {
            var len = NativeMethods.SearchPath(
                path, fileName, extension,
                0, null, IntPtr.Zero);
            var buffer = new StringBuilder((int)len);
            var ret = NativeMethods.SearchPath(
                path, fileName, extension,
                (uint)buffer.Capacity, buffer, IntPtr.Zero);
            if (ret == 0 && Marshal.GetLastWin32Error() != 0)
            {
                return null;
            }
            else
            {
                return buffer.ToString(0, (int)ret);
            }
        }
    }
}

出力例

user32DllPath: "C:\\WINDOWS\\SYSTEM32\\user32.dll"

参考