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

时间:2017-12-04 21:15:49

标签: c# windows windows-services pinvoke marshalling

我试图在我的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服务"返回路径

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

相反,我在Windows 10上得到了这个

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

,这在Windows 8上

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

在XP上就是这个

"C??

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

1 个答案:

答案 0 :(得分:0)

这是因为您应该分配缓冲区,而不是传递固定的缓冲区大小。 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);