Java JNA:PROCESSENTRY32.szExeFile returns "????..." 转换为 Java 字符串时

Java JNA: PROCESSENTRY32.szExeFile returns "????..." when converted to a Java string

我是 JNA 的新手,在尝试我的第一个程序列出 Windows 上的所有进程时,我 运行 遇到了一些麻烦。出于某种原因,我得到以下输出:

[pid = 0, name = ???????? ]
[pid = 4, name = ???????? ]
[pid = 364, name = ???????? ]
[pid = 516, name = ????e??? ]
[pid = 648, name = ?????e?? ]
[pid = 668, name = ????ee?? ]
[pid = 708, name = ???????? ]
[pid = 732, name = ????e??? ]
[pid = 740, name = ???ee??? ]
[pid = 796, name = ???????? ]
[pid = 880, name = ?????e?? ]
...

进程标识符是有效的,并且在快照期间当前 运行 在我的系统上,但由于某种原因,字符串已损坏。 Whosebug 上的其他几个类似示例给了我相同的结果。我是否需要在最新版本的 JNA 中指定一些新的东西才能让这样的程序工作?

    public class Processes 
    {
        private static final Kernel32 kernel = ( Kernel32 )Native.loadLibrary( Kernel32.class );

        public static ArrayList<Process> getSnapshot( ) throws LastErrorException
        {
            ArrayList<Process> processes = new ArrayList<Process>( );
            HANDLE snapshot = null;

            try
            {
                snapshot = kernel.CreateToolhelp32Snapshot( Tlhelp32.TH32CS_SNAPPROCESS, new DWORD( 0 ) );
                PROCESSENTRY32 entry = new PROCESSENTRY32( );
                kernel.Process32First( snapshot, entry );

                do
                {
                    processes.add( new Process( Native.toString( entry.szExeFile ), entry.th32ProcessID.intValue() ) );
                }
                while( kernel.Process32Next( snapshot, entry ) );
            }
            finally
            {
                kernel.CloseHandle( snapshot );
            }

            return processes;
        }
    }

我的代码主要基于 here.

中的 MSDN 示例

JNA 使用 Process32First\Next which is the ANSI version but you need to use the Unicode or UTF-16LE version which is Process32FirstW\NextW. 这可能是 JNA 中的一个错误,因为它使用 PROCESSENTRY32 的 Unicode 版本,期望 TCHAR 对于 szExeFile 是 UTF-16LE

您可以这样扩展 Kernel32:

Kernel32.java:

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Tlhelp32;

public interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {
    Kernel32 INSTANCE = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class, com.sun.jna.win32.W32APIOptions.DEFAULT_OPTIONS);

    boolean Process32FirstW(HANDLE hSnapshot, Tlhelp32.PROCESSENTRY32 lppe);
    boolean Process32NextW(HANDLE hSnapshot, Tlhelp32.PROCESSENTRY32 lppe);

}

将Processes.java改成这样:

try
{
    snapshot = kernel.CreateToolhelp32Snapshot( Tlhelp32.TH32CS_SNAPPROCESS, new DWORD( 0 ) );
    PROCESSENTRY32 entry = new PROCESSENTRY32( );
    kernel.Process32FirstW( snapshot, entry );

    do
    {
        processes.add( new Process( Native.toString(entry.szExeFile ), entry.th32ProcessID.intValue() ) );
    }
    while( kernel.Process32NextW( snapshot, entry ) );
}
finally
{
    kernel.CloseHandle( snapshot );
}


带有可悲的废弃和过时 ANSI 的原始答案:
尝试

import java.util.ArrayList;

import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Tlhelp32;
import com.sun.jna.platform.win32.Tlhelp32.PROCESSENTRY32;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinNT.HANDLE;



public class Processes 
    {
        private static final Kernel32 kernel = ( Kernel32 )Native.loadLibrary( Kernel32.class );

        static class Process{
            public String pName;
            public int pID;
            Process(String pName,int pID){
                this.pName = pName;
                this.pID = pID;
            }
        }

        public static ArrayList<Process> getSnapshot( ) throws LastErrorException
        {
            ArrayList<Process> processes = new ArrayList<Process>( );
            HANDLE snapshot = null;

            try
            {
                snapshot = kernel.CreateToolhelp32Snapshot( Tlhelp32.TH32CS_SNAPPROCESS, new DWORD( 0 ) );
                PROCESSENTRY32 entry = new PROCESSENTRY32( );
                kernel.Process32First( snapshot, entry );

                do
                {
                    byte[] bytes = new byte[entry.szExeFile.length*2];
                    for(int i=0;i<entry.szExeFile.length;i++) {
                       bytes[i*2+1] = (byte) (entry.szExeFile[i] >> 8);
                       bytes[i*2] = (byte) entry.szExeFile[i];
                    }
                    processes.add( new Process( Native.toString( bytes, "ANSI" ), entry.th32ProcessID.intValue() ) );
                }
                while( kernel.Process32Next( snapshot, entry ) );
            }
            finally
            {
                kernel.CloseHandle( snapshot );
            }

            return processes;
        }
    }

真正唯一的变化是将 char[] 转换为 byte[],因此可以指定 "ANSI"。

byte[] bytes = new byte[entry.szExeFile.length*2];
for(int i=0;i<entry.szExeFile.length;i++) {
   bytes[i*2+1] = (byte) (entry.szExeFile[i] >> 8);
   bytes[i*2] = (byte) entry.szExeFile[i];
}
processes.add( new Process( Native.toString( bytes, "ANSI" ), entry.th32ProcessID.intValue() ) );

在主要 class 以上:

public static void main(String[] args) {

    ArrayList<Processes.Process> curProcesses = Processes.getSnapshot();
    for(Processes.Process curP : curProcesses){
        System.out.println(curP.pName + ":" + curP.pID);
    }
}

我得到:

[系统进程]:0
System:4
smss.exe:248
csrss.exe:444
csrss.exe:532
wininit.exe:540
services.exe:588
lsass.exe:596
...等等

您缺少 Native.loadLibrary 选项来告诉 JNA 自动映射到 Process32FirstWW32APIOptions.DEFAULT_OPTIONS 会为您完成)。查看 JNA 本身如何加载 kernel32 库。

JNA 的 platform.jar 中包含的 Process32First 的定义实际上 由于定义而与 unicode (-W) 版本一起使用PROCESSENTRY32 结构的一部分,它使用 Java char 作为文件名。你变得垃圾的原因是 "ANSI" 版本的编码字节数组已被读入 Java char 数组。 Native.toString() 正在尝试从该数组中读取数据,但没有意识到数据最初是编码字节。