I'm trying to convert this function from C++ to Rust using the windows crate:
#include <iostream>
#include "windows.h"
#include <RestartManager.h>
#pragma comment(lib ,"Rstrtmgr.lib")
BOOL ReleaseFileLock(LPCTSTR pFilePath)
{
BOOL bResult = FALSE;
DWORD dwSession;
WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 };
DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
if (dwError == ERROR_SUCCESS)
{
dwError = RmRegisterResources(dwSession, 1, &pFilePath,
0, NULL, 0, NULL);
if (dwError == ERROR_SUCCESS)
{
UINT nProcInfoNeeded = 0;
UINT nProcInfo = 0;
RM_PROCESS_INFO rgpi[1];
DWORD dwReason;
dwError = RmGetList(dwSession, &nProcInfoNeeded,
&nProcInfo, rgpi, &dwReason);
if (dwError == ERROR_SUCCESS ||
dwError == ERROR_MORE_DATA)
{
if (nProcInfoNeeded > 0)
{
//If current process does not have enough privileges to close one of
//the "offending" processes, you'll get ERROR_FAIL_NOACTION_REBOOT
dwError = RmShutdown(dwSession, RmForceShutdown, NULL);
if (dwError == ERROR_SUCCESS)
{
bResult = TRUE;
}
}
else
bResult = TRUE;
}
}
}
RmEndSession(dwSession);
SetLastError(dwError);
return bResult;
}
I successfully imported the necessary functions for that, but as a starting point I tried simply to start the session and close it and I got error STATUS_ACCESS_VIOLATION:
use windows_sys::core::PWSTR;
use windows::Win32::System::RestartManager::{
RmStartSession,
RmEndSession,
RmRegisterResources,
RmGetList,
RmShutdown,
RmForceShutdown,
};
fn release_file_lock(path: &str) -> Result<(), Box<dyn std::error::Error>> {
let psessionhandle = std::ptr::null_mut();
let dwsessionflags: u32 = 0;
let strsessionkey = std::ptr::null_mut();
unsafe {
let result = RmStartSession(psessionhandle, dwsessionflags, windows::core::PWSTR(strsessionkey));
println!("{:?} {:?}", result, psessionhandle);
RmEndSession(*psessionhandle); // < --- STATUS_ACCESS_VIOLATION
};
Ok(())
}
I would like to know what the flow of converting the code should be.
Currently, I'm looking in docs.rs/windows and follow every function and I'm trying to fit the parameters, but it looks like I missed something important in that process.
I would argue there is no single right way to convert code from one language to another. If you are unfamiliar with the language you can go layer by layer, like you did in the provided rust snippet. This way you may have an easier experience learning the concepts of the target language during your conversion process.
In your case I would recommend to focus on one api call at a time and check if the results match with the results of your C++ source code. Let me share some of my opinions on the conversion topic:
Keep the functions as identical as reasonable in the first iteration. For example your C++ code has the function signature
BOOL ReleaseFileLock(LPCTSTR pFilePath)but Rust hasrelease_file_lock(path: &str) -> Result<...>. In unicode mode aLPCTSTRrefers to wide characters that represent UTF16 encoding in windows. Rust stores the strings in UTF8 but the api you later call expects UTF16. Keeping the signatures identical at first, ensures that your expectations of the functional behavior can also be identical.Do not care about the specifics of error handling during conversion. You already started with
Result<(), Box<dyn std::error::Error>>which implies some sort of basic error forwarding. I would generally keep it simple and skip the error handling part for the moment. If you encounter api functions returningResult<...>just dounwrap. Of course this may has the side effects of handle leaks due tounwrapbut it is just faster for prototyping and converting code in an unfamiliar terrain. Later in the finalization process of the conversion, introduce error handling and step through the termination to ensure correctness of resource management.Be extremely careful with types, type conversions and memory concepts! You should be aware of the differences of C++ and Rust and know what the api expects from the arguments you pass. Understand the intent and content of variables in the C++ code, then research how to achieve the same using Rust.
Lets walk through the conversion process and explore some mistakes you made during conversion. The following snippet shows a stripped down version of the C++ example for the baseline. I made some minor type modifications and moved
RmEndSessioninto the successful branch.Here would be the conversion to Rust:
As you can see the first iteration of the Rust solution is almost identical to the C++ solution. There are type differences like
DWORDisu32andWCHARisu16in Rust. There are additional type conversions required likeWIN32_ERROR(result)andPWSTR(session_key_buffer.as_mut_ptr())to convert the wide char slice to the type expected by the api call. But the underlying ideas and variables memory layouts are equivalent.During your conversion attempt a lot of the C++ code concepts were certainly unintentionally left out or modified in a way that changed the intentions of variables. In the Rust version you:
Did not check if the api call was successful before doing something with the handle. To avoid garbage data during your debugging, you should always check the return value for success.
Used pointers
let psessionhandle = std::ptr::null_mut()where originally valuesDWORD dwSessionwere declared. The confusion of values and pointers also caused the access violation due to a dereference of a null pointer. I many cases a mutable reference passed to a function in C++ like the&dwSessiontranslates in Rust to&mut dwSessionor for complex types to.as_mut_ptr(), so you just have to keep the original idea of the C++ code in mind.Did not allocate a
WCHAR szSessionKey[]as a buffer for the api but instead passed in astd::ptr::null_mut(). The api just wants a pointer to the memory where it can store this session key. In C++ this pointer came from the stack allocatedWCHAR szSessionKey[]and you did not have to reference it like so&szSessionKeyduring the function call because in C++ the variable to a stack allocated memory block is already its pointer. In Rust you had to declare the stack memory and then specifically pass its pointer with.as_mut_ptr()to thePWSTR(*mut u16)to achieve the equivalent concept.With this advice you should be able to convert the entire code. Keep in mind that after the first iteration you go back and introduce proper error handling and if desired you can also update the function arguments to more convenient types, like a string slice instead of windows specific types.