diff --git a/Cargo.toml b/Cargo.toml index fde2e2b..4f31998 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ documentation = "https://docs.rs/clroxide" readme = "README.md" license = "MIT" repository = "https://github.com/yamakadi/clroxide" -version = "1.0.9" +version = "1.1.0" exclude = ["/test"] [lib] diff --git a/examples/compose_clr.rs b/examples/compose_clr.rs index 3ffe114..5b5f711 100644 --- a/examples/compose_clr.rs +++ b/examples/compose_clr.rs @@ -5,7 +5,7 @@ use clroxide::{ use windows::Win32::System::Com::VARIANT; fn main() -> Result<(), String> { - let mut clr = Clr::context_only()?; + let mut clr = Clr::context_only(None)?; let mut context = clr.get_context()?; let app_domain = context.app_domain; let mscorlib = unsafe { (*app_domain).load_library("mscorlib")? }; diff --git a/examples/patch_exit.rs b/examples/patch_exit.rs index d661f7c..7b1cadb 100644 --- a/examples/patch_exit.rs +++ b/examples/patch_exit.rs @@ -18,7 +18,7 @@ fn main() -> Result<(), String> { } pub unsafe fn run(contents: Vec, args: Vec) -> Result { - let mut clr = Clr::context_only()?; + let mut clr = Clr::context_only(None)?; clr.redirect_output()?; diff --git a/examples/streaming_output.rs b/examples/streaming_output.rs index ac9d43a..971fa06 100644 --- a/examples/streaming_output.rs +++ b/examples/streaming_output.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), String> { let (path, args) = prepare_args(); let contents = fs::read(path).expect("Unable to read file"); - let mut clr = Clr::context_only()?; + let mut clr = Clr::context_only(None)?; let mut context = clr.get_context()?; let app_domain = context.app_domain; let mscorlib = unsafe { (*app_domain).load_library("mscorlib")? }; diff --git a/src/clr/mod.rs b/src/clr/mod.rs index 0181025..796205c 100644 --- a/src/clr/mod.rs +++ b/src/clr/mod.rs @@ -1,6 +1,6 @@ use crate::primitives::{ ICLRMetaHost, ICLRRuntimeInfo, ICorRuntimeHost, _AppDomain, _MethodInfo, empty_variant_array, - wrap_method_arguments, GUID, HRESULT, + wrap_method_arguments, RuntimeVersion, GUID, HRESULT, }; use std::ffi::c_void; use windows::Win32::System::Com::VARIANT; @@ -13,6 +13,7 @@ pub struct Clr { create_interface: isize, context: Option, output_context: Option, + version: RuntimeVersion, } pub struct ClrContext { @@ -44,11 +45,30 @@ impl Clr { create_interface, context: None, output_context: None, + version: RuntimeVersion::V4, }) } #[cfg(feature = "default-loader")] - pub fn context_only() -> Result { + pub fn new_with_runtime( + contents: Vec, + arguments: Vec, + version: RuntimeVersion, + ) -> Result { + let create_interface = load_function("mscoree.dll", "CreateInterface")?; + + Ok(Clr { + contents, + arguments, + create_interface, + context: None, + output_context: None, + version, + }) + } + + #[cfg(feature = "default-loader")] + pub fn context_only(version: Option) -> Result { let create_interface = load_function("mscoree.dll", "CreateInterface")?; Ok(Clr { @@ -57,6 +77,7 @@ impl Clr { create_interface, context: None, output_context: None, + version: version.unwrap_or(RuntimeVersion::V4), }) } @@ -74,11 +95,34 @@ impl Clr { create_interface, context: None, output_context: None, + version: RuntimeVersion::V4, }) } #[cfg(not(feature = "default-loader"))] - pub fn context_only(load_function: fn() -> Result) -> Result { + pub fn new_with_runtime( + contents: Vec, + arguments: Vec, + version: RuntimeVersion, + load_function: fn() -> Result, + ) -> Result { + let create_interface = load_function()?; + + Ok(Clr { + contents, + arguments, + create_interface, + context: None, + output_context: None, + version, + }) + } + + #[cfg(not(feature = "default-loader"))] + pub fn context_only( + load_function: fn() -> Result, + version: Option, + ) -> Result { let create_interface = load_function()?; Ok(Clr { @@ -87,6 +131,7 @@ impl Clr { create_interface, context: None, output_context: None, + version: version.unwrap_or(RuntimeVersion::V4), }) } @@ -213,10 +258,14 @@ impl Clr { } let host = self.get_clr_host()?; - let runtime_info = unsafe { (*host).get_first_available_runtime()? }; + let runtime_info = unsafe { (*host).get_first_available_runtime(Some(self.version))? }; let runtime_host = unsafe { (*runtime_info).get_runtime_host()? }; - unsafe { (*runtime_host).start()? }; + unsafe { + if (*runtime_info).can_be_loaded()? && !(*runtime_info).has_started()? { + (*runtime_host).start()?; + } + }; let app_domain = unsafe { (*runtime_host).get_default_domain()? }; diff --git a/src/primitives/iclrmetahost.rs b/src/primitives/iclrmetahost.rs index 9c476da..d6dcef7 100644 --- a/src/primitives/iclrmetahost.rs +++ b/src/primitives/iclrmetahost.rs @@ -1,7 +1,8 @@ use crate::primitives::{ - Class, ICLRRuntimeInfo, IEnumUnknown, IUnknown, IUnknownVtbl, Interface, GUID, HRESULT, + Class, ICLRRuntimeInfo, IEnumUnknown, IUnknown, IUnknownVtbl, Interface, RuntimeVersion, GUID, + HRESULT, }; -use std::{ffi::c_void, ops::Deref, ptr}; +use std::{collections::HashMap, ffi::c_void, ops::Deref, ptr}; #[repr(C)] pub struct ICLRMetaHostVtbl { @@ -53,22 +54,32 @@ impl ICLRMetaHost { return Ok(ppv); } - pub fn get_first_available_runtime(&self) -> Result<*mut ICLRRuntimeInfo, String> { + pub fn get_first_available_runtime( + &self, + prefer_version: Option, + ) -> Result<*mut ICLRRuntimeInfo, String> { let runtimes = self.get_installed_runtimes()?; - if runtimes.len() > 0 { - Ok(runtimes[0]) - } else { - Err("Could not find any runtimes".into()) + if let Some(prefer_version) = prefer_version { + if let Some(runtime) = runtimes.get(&prefer_version) { + return Ok(*runtime); + } } + + for runtime in runtimes.values() { + return Ok(*runtime); + } + + Err("Could not find any runtimes".into()) } - pub fn get_runtime(&self, version: *mut u16) -> Result<*mut ICLRRuntimeInfo, String> { + pub fn get_runtime(&self, version: RuntimeVersion) -> Result<*mut ICLRRuntimeInfo, String> { + let version_ptr = version.clone().to_bstr(); let mut ppv: *mut ICLRRuntimeInfo = ptr::null_mut(); let hr = unsafe { (*self).GetRuntime( - version, + version_ptr.into_raw() as *mut _, &ICLRRuntimeInfo::IID, &mut ppv as *mut *mut _ as *mut *mut c_void, ) @@ -76,17 +87,22 @@ impl ICLRMetaHost { return match hr.is_ok() { true => Ok(ppv), - false => Err(format!("{:?}", hr)), + false => Err(format!( + "Could not find a runtime for version `{}`: {:?}", + version, hr + )), }; } - pub fn get_installed_runtimes(&self) -> Result, String> { + pub fn get_installed_runtimes( + &self, + ) -> Result, String> { let mut ieu_ptr: *mut IEnumUnknown = ptr::null_mut(); let hr = unsafe { (*self).EnumerateInstalledRuntimes(&mut ieu_ptr) }; if hr.is_err() { - return Err(format!("{:?}", hr)); + return Err(format!("Could not enumerate installed runtimes: {:?}", hr)); } if ieu_ptr.is_null() { @@ -94,6 +110,7 @@ impl ICLRMetaHost { } let mut hmri: Vec<*mut ICLRRuntimeInfo> = vec![]; + let mut hmri: HashMap = HashMap::new(); loop { let mut iu_ptr: *mut IUnknown = ptr::null_mut(); @@ -118,7 +135,9 @@ impl ICLRMetaHost { break; } - hmri.push(ri_ptr); + let version = unsafe { (*ri_ptr).get_version()? }; + + hmri.insert(version, ri_ptr); } Ok(hmri) diff --git a/src/primitives/iclrruntimeinfo.rs b/src/primitives/iclrruntimeinfo.rs index 15e3ab0..b731cf4 100644 --- a/src/primitives/iclrruntimeinfo.rs +++ b/src/primitives/iclrruntimeinfo.rs @@ -1,7 +1,48 @@ use crate::primitives::{ Class, ICorRuntimeHost, IUnknown, IUnknownVtbl, Interface, BOOL, GUID, HANDLE, HRESULT, }; -use std::{ffi::c_void, ops::Deref, ptr}; +use std::{ffi::c_void, fmt::Display, ops::Deref, ptr}; +use windows::core::{BSTR, PWSTR}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum RuntimeVersion { + V2, + V3, + V4, + UNKNOWN, +} + +impl RuntimeVersion { + pub fn to_str(&self) -> &str { + match self { + RuntimeVersion::V2 => "v2.0.50727", + RuntimeVersion::V3 => "v3.0", + RuntimeVersion::V4 => "v4.0.30319", + RuntimeVersion::UNKNOWN => "UNKNOWN", + } + } + + pub fn to_bstr(&self) -> BSTR { + BSTR::from(self.to_str()) + } +} + +impl From for RuntimeVersion { + fn from(version: String) -> Self { + match version.as_str() { + "v2.0.50727" => RuntimeVersion::V2, + "v3.0" => RuntimeVersion::V3, + "v4.0.30319" => RuntimeVersion::V4, + _ => RuntimeVersion::UNKNOWN, + } + } +} + +impl Display for RuntimeVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_str()) + } +} #[repr(C)] pub struct ICLRRuntimeInfo { @@ -93,8 +134,49 @@ impl ICLRRuntimeInfo { return Ok(ppv); } - pub fn has_started() -> Result { - todo!() + pub fn get_version(&self) -> Result { + let mut dummy = ptr::null_mut(); + let mut length = 0; + + let _ = unsafe { (*self).GetVersionString(dummy, &mut length) }; + + let mut buffer: Vec = Vec::with_capacity(length as usize); + let mut version = PWSTR(buffer.as_mut_ptr()); + + let hr = unsafe { (*self).GetVersionString(version.as_ptr(), &mut length) }; + + if hr.is_err() { + return Err(format!("Failed while running `GetVersionString`: {:?}", hr)); + } + + Ok(RuntimeVersion::from(unsafe { + version.to_string().unwrap_or_default() + })) + } + + pub fn can_be_loaded(&self) -> Result { + let mut loadable = BOOL(0); + + let hr = unsafe { (*self).IsLoadable(&mut loadable) }; + + if hr.is_err() { + return Err(format!("Failed while running `IsLoadable`: {:?}", hr)); + } + + Ok(loadable.0 > 0) + } + + pub fn has_started(&self) -> Result { + let mut startup_flags = 0; + let mut started = BOOL(0); + + let hr = unsafe { (*self).IsStarted(&mut started, &mut startup_flags) }; + + if hr.is_err() { + return Err(format!("Failed while running `IsStarted`: {:?}", hr)); + } + + Ok(started.0 > 0) } #[inline]