use core::ffi::c_void;
use core::ptr;
use core::slice;
use std::ffi::CStr;
use windows_sys::Win32::System::Com::CoTaskMemFree;
pub struct ComAnsiString(*mut i8);
impl ComAnsiString {
pub unsafe fn from_raw(ptr: *mut i8) -> Option<Self> {
if ptr.is_null() { None } else { Some(Self(ptr)) }
}
pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
unsafe { CStr::from_ptr(self.0) }.to_str()
}
pub fn to_string_lossy(&self) -> String {
unsafe { CStr::from_ptr(self.0) }
.to_string_lossy()
.into_owned()
}
}
impl Drop for ComAnsiString {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { CoTaskMemFree(self.0.cast()) };
self.0 = ptr::null_mut();
}
}
}
impl std::fmt::Debug for ComAnsiString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ComAnsiString")
.field(&self.to_string_lossy())
.finish()
}
}
pub struct ComWideString(*mut u16);
impl ComWideString {
pub unsafe fn from_raw(ptr: *mut u16) -> Option<Self> {
if ptr.is_null() { None } else { Some(Self(ptr)) }
}
pub fn to_string_lossy(&self) -> String {
unsafe {
let mut len = 0usize;
while *self.0.add(len) != 0 {
len += 1;
}
String::from_utf16_lossy(slice::from_raw_parts(self.0, len))
}
}
}
impl Drop for ComWideString {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { CoTaskMemFree(self.0.cast()) };
self.0 = ptr::null_mut();
}
}
}
impl std::fmt::Debug for ComWideString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ComWideString")
.field(&self.to_string_lossy())
.finish()
}
}
pub struct ComArray<T> {
ptr: *mut T,
len: usize,
}
unsafe impl<T: Send> Send for ComArray<T> {}
unsafe impl<T: Sync> Sync for ComArray<T> {}
impl<T> ComArray<T> {
pub unsafe fn from_raw(ptr: *mut T, len: usize) -> Option<Self> {
if ptr.is_null() {
Some(Self {
ptr: ptr::null_mut(),
len: 0,
})
} else {
Some(Self { ptr, len })
}
}
pub fn as_slice(&self) -> &[T] {
if self.ptr.is_null() {
return &[];
}
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
impl<T> Drop for ComArray<T> {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { CoTaskMemFree(self.ptr.cast::<c_void>()) };
self.ptr = ptr::null_mut();
self.len = 0;
}
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for ComArray<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ComArray").field(&self.as_slice()).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::size_of;
use core::ptr::copy_nonoverlapping;
use windows_sys::Win32::System::Com::CoTaskMemAlloc;
fn alloc_wide(text: &str) -> *mut u16 {
let units: Vec<u16> = text.encode_utf16().chain(std::iter::once(0)).collect();
let bytes = units.len() * size_of::<u16>();
let raw = unsafe { CoTaskMemAlloc(bytes) } as *mut u16;
assert!(!raw.is_null(), "测试前提:COM 堆分配应当成功");
unsafe { copy_nonoverlapping(units.as_ptr(), raw, units.len()) };
raw
}
#[test]
fn test_com_wide_string_takes_ownership_and_frees() {
let raw = alloc_wide("容器未找到");
let content;
{
let owned = unsafe { ComWideString::from_raw(raw) }.expect("非空指针应构造成功");
content = owned.to_string_lossy();
assert_eq!(content, "容器未找到");
}
assert_eq!(content, "容器未找到");
}
#[test]
fn test_null_raw_yields_none() {
assert!(unsafe { ComWideString::from_raw(ptr::null_mut()) }.is_none());
assert!(unsafe { ComAnsiString::from_raw(ptr::null_mut()) }.is_none());
let empty =
unsafe { ComArray::<u32>::from_raw(ptr::null_mut(), 0) }.expect("应构造为空数组");
assert!(empty.as_slice().is_empty());
}
#[test]
fn test_com_array_borrows_and_frees() {
let values = [1u32, 2, 3, 4, 5];
let bytes = values.len() * size_of::<u32>();
let raw = unsafe { CoTaskMemAlloc(bytes) } as *mut u32;
assert!(!raw.is_null(), "测试前提:COM 堆分配应当成功");
unsafe { copy_nonoverlapping(values.as_ptr(), raw, values.len()) };
{
let owned =
unsafe { ComArray::from_raw(raw, values.len()) }.expect("非空指针应构造成功");
assert_eq!(owned.as_slice(), &values);
}
}
#[test]
fn test_com_ansi_string_roundtrip() {
let text = b"mcr.microsoft.com\0";
let bytes = text.len();
let raw = unsafe { CoTaskMemAlloc(bytes) } as *mut i8;
assert!(!raw.is_null(), "测试前提:COM 堆分配应当成功");
unsafe { copy_nonoverlapping(text.as_ptr().cast::<i8>(), raw, bytes) };
{
let owned = unsafe { ComAnsiString::from_raw(raw) }.expect("非空指针应构造成功");
assert_eq!(
owned.as_str().expect("测试载荷为合法 UTF-8"),
"mcr.microsoft.com"
);
assert_eq!(owned.to_string_lossy(), "mcr.microsoft.com");
}
}
#[test]
fn test_com_ansi_string_reports_non_utf8_instead_of_silently_empty() {
let bytes: [u8; 4] = [b'{', 0xFF, b'}', 0x00];
let n = bytes.len();
let raw = unsafe { CoTaskMemAlloc(n) } as *mut i8;
assert!(!raw.is_null(), "测试前提:COM 堆分配应当成功");
unsafe { copy_nonoverlapping(bytes.as_ptr().cast::<i8>(), raw, n) };
let owned = unsafe { ComAnsiString::from_raw(raw) }.expect("非空指针应构造成功");
assert!(
owned.as_str().is_err(),
"非 UTF-8 内容应返回 Err,而不是静默变成空串"
);
let lossy = owned.to_string_lossy();
assert!(
lossy.starts_with('{') && lossy.ends_with('}'),
"有损转换应保留原有结构,实际为: {lossy:?}"
);
}
}