为什么 PtrToStringUni 返回一些不正确的字符

Why is PtrToStringUni returning some incorrect characters

我正尝试在我的 C# 应用程序中通过 pinvoke 使用 advapi32.dll 来获取服务使用的可执行文件的路径。我正在指定 Unicode 并获取看起来像是路径的部分内容,但某些字符在翻译中显然会出现乱码。

private static string GetServicePath(IntPtr service)
{
    SERVICE_CONFIG status = new SERVICE_CONFIG();

    uint bytesNeeded = 1;
    var queryResult = QueryServiceConfig(service, status, 0, out bytesNeeded);
    if (queryResult == 0 && bytesNeeded == 0)
    {
        throw new ApplicationException("Failed to query service config.");
    }
    else
    {
        QueryServiceConfig(service, status, bytesNeeded, out bytesNeeded);
    }

    var servicePath = Marshal.PtrToStringUni(status.lpBinaryPathName);
    return servicePath;
}

^ 调用方法以使用服务句柄获取服务名称

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class SERVICE_CONFIG
{
    public int dwServiceType;
    public ServiceStartType dwStartType;
    public int dwErrorControl;
    public IntPtr lpBinaryPathName;
    public IntPtr lpLoadOrderGroup;
    public int dwTagID;
    public IntPtr lpDependencies;
    public IntPtr lpServiceStartName;
    public IntPtr lpDisplayName;
};

[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
private static extern int QueryServiceConfig(IntPtr hService, SERVICE_CONFIG queryConfig, UInt32 cbBufSize, out UInt32 pcbBytesNeeded);

^ 相关的 PInvoke 方法和结构

例如,我希望询问 "Bonjour Service" 到 return

的路径

"C:\Program Files\Bonjour\mDNSResponder.exe"

相反,我在 Windows 10

上得到了这个

"C:\??? , m Files\Bonjour\mDNSResponder.exe"

和这个 Windows 8

"C:\??? , "C:\??? , "C:\??? , "C:\??? , "C:\

在 XP 上只有这个

"C??

如果我尝试转换任何其他属性,如显示名称或开始名称,那么我会得到一个合法的字符串。为什么可执行文件路径不像其他字符串那样解析?

发生这种情况是因为您应该分配 缓冲区,而不是传递固定的缓冲区大小。 QUERY_SERVICE_CONFIG 中的所有字符串指针都将包含在该缓冲区中。

QueryServiceConfig 会告诉您缓冲区必须有多大。此处提供了本机示例:Querying a Service's Configuration

所以,我已经相应地修改了你的代码:

    private static string GetServicePath(IntPtr service)
    {
        QueryServiceConfig(service, IntPtr.Zero, 0, out int size);
        if (size == 0)
            throw new Win32Exception(Marshal.GetLastWin32Error());

        var ptr = Marshal.AllocHGlobal(size);
        try
        {
            if (!QueryServiceConfig(service, ptr, size, out size))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            var config = Marshal.PtrToStructure<QUERY_SERVICE_CONFIG>(ptr);
            return config.lpBinaryPathName;
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }

    // note that I use a struct, not a class, so I can call Marshal.PtrToStructure.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct QUERY_SERVICE_CONFIG
    {
        public int dwServiceType;
        public int dwStartType;
        public int dwErrorControl;
        public string lpBinaryPathName;
        public string lpLoadOrderGroup;
        public int dwTagID;
        public string lpDependencies;
        public string lpServiceStartName;
        public string lpDisplayName;
    };

    // return type is a bool, no need for an int here
    // user SetLastError when the doc says so
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool QueryServiceConfig(IntPtr hService, IntPtr lpServiceConfig, int cbBufSize, out int pcbBytesNeeded);