potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

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"

参考