Comparing a string to an array of i8


#1

I’m writing a small utility to get the PID of a process based upon its name. I’ve started the implementation by writing the Windows version which is fairly simple and makes use of the winapi crate. The code looks as follows so far (yes, the return type will be Result<>, rather than Option<>, it’s just easier to test optionals whilst I’m working out the logic):

extern crate winapi;
extern crate kernel32;

use kernel32::{CreateToolhelp32Snapshot, Process32First, Process32Next, CloseHandle};
use winapi::tlhelp32::{TH32CS_SNAPPROCESS, PROCESSENTRY32};
use winapi::shlobj::INVALID_HANDLE_VALUE;
use winapi::minwindef::MAX_PATH;
use std::mem;

#[cfg(target_os="windows")]
pub fn find_pid_by_name(name: &str) -> Option<u32> {
    // Create a snapshot of the current processes
    let processes_snap_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        return None
    }

    // Initialise a process entry. In order to use `Process32First`, you need to set `dwSize`.
    let mut process_entry : PROCESSENTRY32 = PROCESSENTRY32 {
        dwSize: mem::size_of::<PROCESSENTRY32>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };

    // Get the first process from the snapshot.
    match unsafe { Process32First(processes_snap_handle, &mut process_entry) } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success : i32 = 1;

            // Loop through all processes until we find one hwere `szExeFile` == `name`.
            while process_success == 1 {
                // TODO: Compare `szExeFile` to `name`.

                process_success = unsafe { Process32Next(processes_snap_handle, &mut process_entry) };
            }

            unsafe { CloseHandle(processes_snap_handle) };

            Some(0)
        },
        0|_ => {
            unsafe { CloseHandle(processes_snap_handle) };
            None
        }
    }
}

Now, the problem is that winapi::tlhelp32::PROCESSENTRY32::szExeFile is defined as [CHAR; ::MAX_PATH], so basically a [i8; 260]. As far as I can see from the documentation, there is no standard way to convert this into a string in order to make the comparison with the process being searched for. I had a few ideas of possible solutions:

  • Compare the byte values of name to the values in the array szExeFile one by one to ensure they’re all found in sequence somewhere in the array. The problem here is that I’d be comparing u8 to i8, which feels nasty.
  • Convert szExeFile to a string for each process and make sure name exists as a substring. The problem here is working out the best way to get to a string value from szExeFile.

In C I’d use the A2T macro from atlstr.h: https://msdn.microsoft.com/en-GB/library/87zae4a3(v=vs.110).aspx

I feel like I’m missing something, so any thoughts would be appreciated greatly.


#2

Maybe this will work for your case https://doc.rust-lang.org/std/ffi/struct.CString.html#method.from_raw

CString can be converted to String if wanted.


#3

Yep, that looks like it should help, managed to miss that whilst looking - thanks!


#4

I believe you need to use the wide version of the struct (PROCESSENTRY32W) and convert the field to OsString via FromWide.

If I remember correctly, converting an OsString you got from Windows to String never fails. (No, I think it’s false or else why would there be Wtf8.)

The encoding used in the non-wide struct is not UTF-8 so converting the CStr to String, which requires UTF-8, would be pointless.


#5

Spot on that seems to work. For future reference, the code I’m now using is:

extern crate winapi;
extern crate kernel32;
extern crate wio;

use kernel32::{CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, CloseHandle};
use winapi::tlhelp32::{TH32CS_SNAPPROCESS, PROCESSENTRY32W};
use winapi::shlobj::INVALID_HANDLE_VALUE;
use winapi::minwindef::MAX_PATH;
use wio::wide::FromWide;
use std::mem;
use std::ffi::OsString;

#[cfg(target_os="windows")]
pub fn find_pid_by_name(name: &str) -> Option<u32> {
    // Create a snapshot of the current processes
    let processes_snap_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        return None
    }

    // Initialise a process entry. In order to use `Process32First`, you need to set `dwSize`.
    let mut process_entry : PROCESSENTRY32W = PROCESSENTRY32W {
        dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };

    // Get the first process from the snapshot.
    match unsafe { Process32FirstW(processes_snap_handle, &mut process_entry) } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success : i32 = 1;

            // Loop through all processes until we find one hwere `szExeFile` == `name`.
            while process_success == 1 {
                // TODO: Compare `szExeFile` to `name`.
                let process_name = OsString::from_wide(&process_entry.szExeFile);

                match process_name.into_string() {
                    Ok(s) => {
                        println!("Found process with id {}: {}", process_entry.th32ProcessID, s);
                    },
                    Err(_) => {
                        println!("Error converting process name for PID {}", process_entry.th32ProcessID);
                    }
                }

                process_success = unsafe { Process32NextW(processes_snap_handle, &mut process_entry) };
            }

            unsafe { CloseHandle(processes_snap_handle) };

            Some(6812)
        },
        0|_ => {
            unsafe { CloseHandle(processes_snap_handle) };
            None
        }
    }
}

This appears to work perfectly on my system. Thanks a bunch!


#6

Note that &str can be compared to OsString directly so you might not need to covert the latter to String.


#7

Thanks, the problem is that the string returned seems to have a newline on the end, so I need to trim that off first to do the comparison.


#8

You finally do the compare with the desired process name?
I’m trying it but i can’t do it.

extern crate kernel32;
extern crate winapi;
extern crate wio;
use std::mem;
use std::ffi::OsString;
use wio::wide::FromWide;

fn main() {
    unsafe{get_proc_id();}
}

unsafe fn get_proc_id() -> u32 {
    
    let mut process: winapi::PROCESSENTRY32W = mem::uninitialized(); 
    process.dwSize = mem::size_of::<winapi::PROCESSENTRY32W>() as u32;

    //Make a Snanshot of all the current proccess.
    let snapshot = kernel32::CreateToolhelp32Snapshot(winapi::TH32CS_SNAPPROCESS, 0);
        
    //We get the fir proccess and store it in proccess variable.
    if kernel32::Process32FirstW(snapshot, &mut process) != 0{
        
        //Take the next procces if posible.
        while kernel32::Process32NextW(snapshot, &mut process) != 0 {    
            
            let process_name = OsString::from_wide(&process.szExeFile);
                 
            match process_name.into_string() {
                Ok(s) => {
                    if s == "cargo.exe\n" {
                        println!("Found process with id {}: {}", process.th32ProcessID, s);
                        return process.th32ProcessID;
                        break;
                    }
                },
                Err(_) => {
                    println!("Error converting process name for PID {}", process.th32ProcessID);
                }          
            }
            
        }
    }

    return 0;
    
}

I think the problems its because the string have some extra data at the end, I use compare and works fine :slight_smile:


#9

Yes, the name has a new line at the end. You either need to remove that, or just use contains() (strcmp in C).


#10

The next I want to do its read and write memory from the process.
You have done it? I already have some workarounds but It would be really usefull to me, thanks.