Skip to main content

aviutl2/
common.rs

1pub use anyhow::Result as AnyResult;
2use zerocopy::{Immutable, IntoBytes};
3
4pub use half::{self, f16};
5pub use num_rational::{self, Rational32};
6pub use raw_window_handle::{self, Win32WindowHandle};
7
8/// AviUtl2の情報。
9#[derive(Debug, Clone)]
10pub struct AviUtl2Info {
11    /// AviUtl2のバージョン。
12    pub version: AviUtl2Version,
13}
14
15/// 対応する最小のAviUtl2バージョン。
16pub const MINIMUM_AVIUTL2_VERSION: AviUtl2Version = AviUtl2Version(2010800);
17
18/// AviUtl2のバージョンがサポート範囲かを確認します。
19pub fn ensure_minimum_aviutl2_version(version: AviUtl2Version) -> AnyResult<()> {
20    anyhow::ensure!(
21        version >= MINIMUM_AVIUTL2_VERSION,
22        "AviUtl2 version {version} is not supported. {MINIMUM_AVIUTL2_VERSION} or higher is required.",
23    );
24    Ok(())
25}
26
27/// AviUtl2のバージョン。
28///
29/// # Note
30///
31/// バージョン番号の形式は公式に定義されていないため、暫定的に以下のように解釈しています。
32/// ```text
33/// 2001500
34/// || | |
35/// || | +-- ビルドバージョン
36/// || +---- ベータバージョン
37/// |+------ マイナーバージョン
38/// +------- メジャーバージョン
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub struct AviUtl2Version(pub u32);
42impl From<u32> for AviUtl2Version {
43    fn from(value: u32) -> Self {
44        Self(value)
45    }
46}
47impl From<AviUtl2Version> for u32 {
48    fn from(value: AviUtl2Version) -> Self {
49        value.0
50    }
51}
52impl AviUtl2Version {
53    /// 新しいバージョンを作成します。
54    ///
55    /// # Panics
56    ///
57    /// `minor`、`patch`、`build`がそれぞれ100以上の場合にパニックします。
58    pub fn new(major: u8, minor: u8, patch: u8, build: u8) -> Self {
59        assert!(minor < 100, "minor version must be less than 100");
60        assert!(patch < 100, "patch version must be less than 100");
61        assert!(build < 100, "build version must be less than 100");
62        Self(
63            (major as u32) * 1_000_000
64                + (minor as u32) * 10_000
65                + (patch as u32) * 100
66                + (build as u32),
67        )
68    }
69
70    /// メジャーバージョンを取得します。
71    pub fn major(self) -> u32 {
72        self.0 / 1000000
73    }
74
75    /// マイナーバージョンを取得します。
76    pub fn minor(self) -> u32 {
77        (self.0 / 10000) % 100
78    }
79
80    /// ベータバージョンを取得します。
81    pub fn patch(self) -> u32 {
82        (self.0 / 100) % 100
83    }
84
85    /// ビルドバージョンを取得します。
86    pub fn build(self) -> u32 {
87        self.0 % 100
88    }
89}
90impl std::fmt::Display for AviUtl2Version {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        if self.0 <= 2005301 {
93            write!(
94                f,
95                "{}.{:0>2}beta{}{}",
96                self.major(),
97                self.minor(),
98                self.patch(),
99                int_to_a_to_z(self.build() as u8).unwrap_or_default()
100            )
101        } else {
102            write!(
103                f,
104                "v{}.{}.{}{}",
105                self.major(),
106                self.minor(),
107                self.patch(),
108                int_to_a_to_z(self.build() as u8).unwrap_or_default()
109            )
110        }
111    }
112}
113
114// NOTE: ビルドバージョンが27以上になった場合はどうなるかはまだ不明なので、
115// 暫定的にExcelの列番号のように変換する。そもそも26以上のビルドバージョンが
116// リリースされることなんてないと思うけど...
117fn int_to_a_to_z(n: u8) -> Option<String> {
118    let mut n = n;
119    if n == 0 {
120        return None;
121    }
122
123    let mut result = String::new();
124    while n > 0 {
125        n -= 1;
126        result.insert(0, (b'a' + (n % 26)) as char);
127        n /= 26;
128    }
129    Some(result)
130}
131
132#[cfg(feature = "aviutl2-alias")]
133impl aviutl2_alias::FromTableValue for AviUtl2Version {
134    type Err = std::num::ParseIntError;
135
136    fn from_table_value(value: &str) -> Result<Self, Self::Err> {
137        let v: u32 = value.parse()?;
138        Ok(Self::from(v))
139    }
140}
141
142/// ファイル選択ダイアログのフィルタを表す構造体。
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct FileFilter {
145    /// フィルタの名前。
146    pub name: String,
147    /// フィルタが適用される拡張子のリスト。
148    pub extensions: Vec<String>,
149}
150
151/// [`Vec<FileFilter>`]を簡単に作成するためのマクロ。
152///
153/// # Example
154///
155/// ```rust
156/// let filters = aviutl2::file_filters! {
157///     "Image Files" => ["png", "jpg"],
158///     "All Files" => []
159/// };
160/// ```
161#[macro_export]
162macro_rules! file_filters {
163    ($($name:expr => [$($ext:expr),* $(,)?] ),* $(,)?) => {
164        vec![
165            $(
166                $crate::FileFilter {
167                    name: $name.to_string(),
168                    extensions: vec![$($ext.to_string()),*],
169                }
170            ),*
171        ]
172    };
173}
174
175/// YC48のピクセルフォーマットを表す構造体。
176///
177/// # See Also
178/// <https://makiuchi-d.github.io/mksoft/doc/aviutlyc.html>
179#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable)]
180#[repr(C)]
181pub struct Yc48 {
182    /// Y成分の値。
183    /// 0から4096までの値を取ります。
184    pub y: i16,
185    /// Cb成分の値。
186    /// -2048から2048までの値を取ります。
187    pub cb: i16,
188    /// Cr成分の値。
189    /// -2048から2048までの値を取ります。
190    pub cr: i16,
191}
192impl Yc48 {
193    /// YUV 4:2:2(YUY2)からYC48に変換します。
194    pub fn from_yuy2(yuy2: (u8, u8, u8, u8)) -> (Self, Self) {
195        let (y1, u, y2, v) = yuy2;
196        let ny1 = ((y1 as u16 * 1197) >> 6) - 299;
197        let ny2 = ((y2 as u16 * 1197) >> 6) - 299;
198        let ncb = ((u as u16 - 128) * 4681 + 164) >> 8;
199        let ncr = ((v as u16 - 128) * 4681 + 164) >> 8;
200        (
201            Self {
202                y: ny1 as i16,
203                cb: ncb as i16,
204                cr: ncr as i16,
205            },
206            Self {
207                y: ((ny1 + ny2) >> 1) as i16,
208                cb: ncb as i16,
209                cr: ncr as i16,
210            },
211        )
212    }
213
214    /// YC48からYUV 4:2:2(YUY2)に変換します。
215    pub fn to_yuy2(self, other: Yc48) -> (u8, u8, u8, u8) {
216        let y1 = ((self.y * 219 + 383) >> 12) + 16;
217        let y2 = ((other.y * 219 + 383) >> 12) + 16;
218        let u = (((self.cb + 2048) * 7 + 66) >> 7) + 16;
219        let v = (((self.cr + 2048) * 7 + 66) >> 7) + 16;
220        let y1 = y1.min(255) as u8;
221        let y2 = y2.min(255) as u8;
222        let u = u.min(255) as u8;
223        let v = v.min(255) as u8;
224        (y1, u, y2, v)
225    }
226
227    /// RGBからYC48に変換します。
228    pub fn from_rgb(self, rgb: (u8, u8, u8)) -> Self {
229        let (r, g, b) = rgb;
230        let r = i16::from(r);
231        let g = i16::from(g);
232        let b = i16::from(b);
233        let y = ((4918 * r + 354) >> 10) + ((9655 * g + 585) >> 10) + ((1875 * b + 523) >> 10);
234        let cb = ((-2775 * r + 240) >> 10) + ((-5449 * g + 515) >> 10) + ((8224 * b + 256) >> 10);
235        let cr = ((8224 * r + 256) >> 10) + ((-6887 * g + 110) >> 10) + ((-1337 * b + 646) >> 10);
236
237        Yc48 { y, cb, cr }
238    }
239
240    /// YC48からRGBに変換します。
241    pub fn to_rgb(self) -> (u8, u8, u8) {
242        let y = self.y as i32;
243        let cr = self.cr as i32;
244        let cb = self.cr as i32;
245        let r = (255 * y + ((((22881 * cr) >> 16) + 3) << 10)) >> 12;
246        let g = (255 * y + ((((-5616 * cb) >> 16) + ((-11655 * cr) >> 16) + 3) << 10)) >> 12;
247        let b = (255 * y + ((((28919 * cb) >> 16) + 3) << 10)) >> 12;
248
249        let r = r.min(255) as u8;
250        let g = g.min(255) as u8;
251        let b = b.min(255) as u8;
252        (r, g, b)
253    }
254}
255
256pub(crate) fn format_file_filters(file_filters: &[FileFilter]) -> String {
257    let mut file_filter = String::new();
258    for filter in file_filters {
259        let display = format!(
260            "{} ({})",
261            filter.name,
262            if filter.extensions.is_empty() {
263                "*".to_string()
264            } else {
265                filter
266                    .extensions
267                    .iter()
268                    .map(|ext| format!(".{ext}"))
269                    .collect::<Vec<_>>()
270                    .join(", ")
271            }
272        );
273        file_filter.push_str(&display);
274        file_filter.push('\x00');
275        if filter.extensions.is_empty() {
276            file_filter.push('*');
277        } else {
278            file_filter.push_str(
279                &filter
280                    .extensions
281                    .iter()
282                    .map(|ext| format!("*.{ext}"))
283                    .collect::<Vec<_>>()
284                    .join(";"),
285            );
286        }
287        file_filter.push('\x00');
288    }
289
290    file_filter
291}
292
293pub(crate) struct KillablePointer<T> {
294    kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
295    inner: *mut T,
296}
297unsafe impl<T> Send for KillablePointer<T> {}
298unsafe impl<T> Sync for KillablePointer<T> {}
299impl<T> Drop for KillablePointer<T> {
300    fn drop(&mut self) {
301        self.kill_switch
302            .store(true, std::sync::atomic::Ordering::SeqCst);
303    }
304}
305impl<T> KillablePointer<T> {
306    pub fn new(inner: T) -> Self {
307        Self {
308            kill_switch: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
309            inner: Box::into_raw(Box::new(inner)),
310        }
311    }
312
313    pub fn create_child(&self) -> ChildKillablePointer<T> {
314        ChildKillablePointer {
315            kill_switch: std::sync::Arc::clone(&self.kill_switch),
316            inner: self.inner,
317        }
318    }
319}
320
321pub(crate) struct ChildKillablePointer<T> {
322    kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
323    inner: *mut T,
324}
325unsafe impl<T> Send for ChildKillablePointer<T> {}
326unsafe impl<T> Sync for ChildKillablePointer<T> {}
327impl<T> ChildKillablePointer<T> {
328    pub fn is_killed(&self) -> bool {
329        self.kill_switch.load(std::sync::atomic::Ordering::SeqCst)
330    }
331
332    pub unsafe fn as_mut(&mut self) -> &mut T {
333        if self.is_killed() {
334            panic!("parent KillablePointer has been dropped");
335        }
336        unsafe { &mut *self.inner }
337    }
338}
339
340pub(crate) enum LeakType {
341    WideString,
342    ValueVector { len: usize, name: String },
343    Null,
344    Other(String),
345}
346
347pub(crate) struct LeakManager {
348    ptrs: std::sync::Mutex<Vec<(LeakType, usize)>>,
349}
350
351pub(crate) trait IntoLeakedPtr {
352    fn into_leaked_ptr(self) -> (LeakType, usize);
353}
354pub(crate) trait LeakableValue {}
355
356impl LeakManager {
357    pub fn new() -> Self {
358        Self {
359            ptrs: std::sync::Mutex::new(Vec::new()),
360        }
361    }
362
363    pub fn leak<T: IntoLeakedPtr>(&self, value: T) -> *const T {
364        tracing::debug!("Leaking memory for type {}", std::any::type_name::<T>());
365        let mut ptrs = self.ptrs.lock().unwrap();
366        let leaked = value.into_leaked_ptr();
367        let ptr = leaked.1;
368        ptrs.push(leaked);
369        ptr as *const T
370    }
371
372    pub fn leak_as_wide_string(&self, s: &str) -> *const u16 {
373        tracing::debug!("Leaking wide string: {}", s);
374        let mut wide: Vec<u16> = s.encode_utf16().collect();
375        wide.push(0); // Null-terminate the string
376        let boxed = wide.into_boxed_slice();
377        let ptr = Box::into_raw(boxed) as *mut u16 as usize;
378        let mut ptrs = self.ptrs.lock().unwrap();
379        ptrs.push((LeakType::WideString, ptr));
380        ptr as *const u16
381    }
382
383    // pub fn leak_ptr_vec<T: IntoLeakedPtr>(&self, vec: Vec<T>) -> *const *const T {
384    //     tracing::debug!("Leaking vector of type {}", std::any::type_name::<T>());
385    //     let mut raw_ptrs = Vec::with_capacity(vec.len() + 1);
386    //     for item in vec {
387    //         let leaked = item.into_leaked_ptr();
388    //         let ptr = leaked.1;
389    //         raw_ptrs.push(ptr);
390    //         let mut ptrs = self.ptrs.lock().unwrap();
391    //         ptrs.push(leaked);
392    //     }
393    //     self.leak_value_vec(raw_ptrs) as _
394    // }
395
396    pub fn leak_value_vec<T: LeakableValue>(&self, vec: Vec<T>) -> *const T {
397        tracing::debug!(
398            "Leaking value vector of type {}",
399            std::any::type_name::<T>()
400        );
401        let len = vec.len();
402        let boxed = vec.into_boxed_slice();
403        let ptr = Box::into_raw(boxed) as *mut T as usize;
404        let mut ptrs = self.ptrs.lock().unwrap();
405        ptrs.push((
406            LeakType::ValueVector {
407                len,
408                name: std::any::type_name::<T>().to_string(),
409            },
410            ptr,
411        ));
412        ptr as *const T
413    }
414
415    pub fn free_leaked_memory(&self) {
416        let mut ptrs = self.ptrs.lock().unwrap();
417        while let Some((ptr_type, ptr)) = ptrs.pop() {
418            match ptr_type {
419                LeakType::WideString => unsafe {
420                    let _ = Box::from_raw(ptr as *mut u16);
421                },
422                LeakType::ValueVector { len, name } => {
423                    Self::free_leaked_memory_leakable_value(&name, ptr, len);
424                }
425                LeakType::Null => {
426                    assert!(ptr == 0);
427                }
428                LeakType::Other(ref type_name) => {
429                    Self::free_leaked_memory_other_ptr(type_name, ptr);
430                }
431            }
432        }
433    }
434}
435macro_rules! impl_leak_ptr {
436    ($($t:ty),* $(,)?) => {
437        $(
438            impl IntoLeakedPtr for $t {
439                fn into_leaked_ptr(self) -> (LeakType, usize) {
440                    let boxed = Box::new(self);
441                    let ptr = Box::into_raw(boxed) as usize;
442                    (LeakType::Other(std::any::type_name::<$t>().to_string()), ptr)
443                }
444            }
445        )*
446
447        impl LeakManager {
448            fn free_leaked_memory_other_ptr(ptr_type: &str, ptr: usize) {
449                unsafe {
450                    match ptr_type {
451                        $(
452                            t if t == std::any::type_name::<$t>() => {
453                                let _ = Box::from_raw(ptr as *mut $t);
454                            },
455                        )*
456                        _ => {
457                            unreachable!("Unknown leaked pointer type: {}", ptr_type);
458                        }
459                    }
460                }
461            }
462        }
463    };
464}
465macro_rules! impl_leakable_value {
466    ($($t:ty),* $(,)?) => {
467        $(
468            impl LeakableValue for $t {}
469        )*
470        impl LeakManager {
471            fn free_leaked_memory_leakable_value(type_name: &str, ptr: usize, len: usize) {
472                unsafe {
473                    match type_name {
474                        $(
475                            t if t == std::any::type_name::<$t>() => {
476                                let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut $t, len));
477                            },
478                        )*
479                        _ => {
480                            unreachable!("Unknown leaked value vector type: {}", type_name);
481                        }
482                    }
483                }
484            }
485        }
486    };
487}
488impl_leak_ptr!(
489    aviutl2_sys::input2::WAVEFORMATEX,
490    aviutl2_sys::output2::BITMAPINFOHEADER,
491    aviutl2_sys::filter2::FILTER_ITEM,
492);
493impl_leakable_value!(
494    aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM,
495    aviutl2_sys::module2::SCRIPT_MODULE_FUNCTION,
496    *const aviutl2_sys::filter2::FILTER_ITEM,
497    usize
498);
499
500impl<T: IntoLeakedPtr> IntoLeakedPtr for Option<T> {
501    fn into_leaked_ptr(self) -> (LeakType, usize) {
502        match self {
503            Some(value) => value.into_leaked_ptr(),
504            None => (LeakType::Null, 0),
505        }
506    }
507}
508
509impl Drop for LeakManager {
510    fn drop(&mut self) {
511        self.free_leaked_memory();
512    }
513}
514
515/// # Safety
516///
517/// - `ptr` は有効なLPCWSTRであること。
518/// - `ptr` はNull Terminatedなu16文字列を指していること。
519pub(crate) unsafe fn load_wide_string(ptr: *const u16) -> String {
520    if ptr.is_null() {
521        return String::new();
522    }
523
524    let mut len = 0;
525    while unsafe { *ptr.add(len) } != 0 {
526        len += 1;
527    }
528
529    unsafe { String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len)) }
530}
531
532#[doc(hidden)]
533#[expect(private_bounds)]
534pub fn __output_log_if_error<T: MenuCallbackReturn>(result: T) {
535    if let Some(err_msg) = result.into_optional_error() {
536        let _ = crate::logger::write_error_log(&err_msg);
537    }
538}
539
540#[doc(hidden)]
541#[expect(private_bounds)]
542pub fn __log_and_beep_if_error<T: MenuCallbackReturn>(result: T) {
543    if let Some(err_msg) = result.into_optional_error() {
544        let _ = crate::logger::write_error_log(&err_msg);
545
546        let _ = unsafe {
547            windows::Win32::System::Diagnostics::Debug::MessageBeep(
548                windows::Win32::UI::WindowsAndMessaging::MB_ICONEXCLAMATION,
549            )
550        };
551    }
552}
553
554trait MenuCallbackReturn {
555    fn into_optional_error(self) -> Option<String>;
556}
557impl<E> MenuCallbackReturn for Result<(), E>
558where
559    Box<dyn std::error::Error>: From<E>,
560{
561    fn into_optional_error(self) -> Option<String> {
562        match self {
563            Ok(_) => None,
564            Err(e) => {
565                let boxed: Box<dyn std::error::Error> = e.into();
566                Some(format!("{}", boxed))
567            }
568        }
569    }
570}
571impl MenuCallbackReturn for () {
572    fn into_optional_error(self) -> Option<String> {
573        None
574    }
575}
576
577/// ワイド文字列(LPCWSTR)を所有するRust文字列として扱うためのラッパー。
578#[derive(Debug, Clone, PartialEq, Eq)]
579pub(crate) struct CWString(Vec<u16>);
580
581/// ヌルバイトを含む文字列を作成しようとした際のエラー。
582#[derive(thiserror::Error, Debug, Clone)]
583#[error("String contains null byte")]
584pub struct NullByteError {
585    position: usize,
586    u16_seq: Vec<u16>,
587}
588impl NullByteError {
589    pub fn nul_position(&self) -> usize {
590        self.position
591    }
592
593    pub fn into_vec(self) -> Vec<u16> {
594        self.u16_seq
595    }
596}
597
598impl CWString {
599    /// 新しいCWStringを作成します。
600    ///
601    /// # Errors
602    ///
603    /// `string`にヌルバイトが含まれている場合、`NullByteError`を返します。
604    pub fn new(string: &str) -> Result<Self, NullByteError> {
605        let mut wide: Vec<u16> = string.encode_utf16().collect();
606        if let Some((pos, _)) = wide.iter().enumerate().find(|&(_, &c)| c == 0) {
607            return Err(NullByteError {
608                position: pos,
609                u16_seq: wide,
610            });
611        }
612        wide.push(0); // Null-terminate the string
613        Ok(Self(wide))
614    }
615
616    /// 内部のポインタを取得します。
617    ///
618    /// # Warning
619    ///
620    /// <div class="warning">
621    ///
622    /// この`CWString`がDropされた後にこのポインタを使用すると未定義動作を引き起こします。
623    ///
624    /// </div>
625    pub fn as_ptr(&self) -> *const u16 {
626        self.0.as_ptr()
627    }
628}
629impl std::fmt::Display for CWString {
630    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631        let s = String::from_utf16_lossy(&self.0[..self.0.len() - 1]);
632        write!(f, "{}", s)
633    }
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639
640    #[test]
641    fn test_format_file_filters() {
642        let filters = vec![
643            FileFilter {
644                name: "Image Files".to_string(),
645                extensions: vec!["png".to_string(), "jpg".to_string()],
646            },
647            FileFilter {
648                name: "All Files".to_string(),
649                extensions: vec![],
650            },
651        ];
652        let formatted = format_file_filters(&filters);
653        let expected = "Image Files (.png, .jpg)\x00*.png;*.jpg\x00All Files (*)\x00*\x00";
654        assert_eq!(formatted, expected);
655    }
656
657    #[test]
658    fn test_file_filters_macro() {
659        let filters = file_filters! {
660            "Image Files" => ["png", "jpg"],
661            "All Files" => []
662        };
663        assert_eq!(filters.len(), 2);
664        assert_eq!(filters[0].name, "Image Files");
665        assert_eq!(
666            filters[0].extensions,
667            vec!["png".to_string(), "jpg".to_string()]
668        );
669        assert_eq!(filters[1].name, "All Files");
670        assert_eq!(filters[1].extensions, Vec::<String>::new());
671    }
672
673    #[test]
674    fn test_aviutl2_version() {
675        let version = AviUtl2Version::new(2, 0, 15, 0);
676        assert_eq!(version.0, 2001500);
677        assert_eq!(version.major(), 2);
678        assert_eq!(version.minor(), 0);
679        assert_eq!(version.patch(), 15);
680        assert_eq!(version.build(), 0);
681
682        let version_from_u32: AviUtl2Version = 2001500u32.into();
683        assert_eq!(version_from_u32, version);
684
685        let u32_from_version: u32 = version.into();
686        assert_eq!(u32_from_version, 2001500);
687
688        assert_eq!(version.to_string(), "2.00beta15");
689
690        let version2 = AviUtl2Version::new(2, 0, 15, 1);
691        assert_eq!(version2.to_string(), "2.00beta15a");
692
693        let version3 = AviUtl2Version::new(2, 0, 54, 0);
694        assert_eq!(version3.to_string(), "v2.0.54");
695
696        let version4 = AviUtl2Version::new(2, 0, 54, 1);
697        assert_eq!(version4.to_string(), "v2.0.54a");
698
699        let version5 = AviUtl2Version::new(2, 0, 54, 26);
700        assert_eq!(version5.to_string(), "v2.0.54z");
701
702        let version6 = AviUtl2Version::new(2, 0, 54, 27);
703        assert_eq!(version6.to_string(), "v2.0.54aa");
704
705        let version7 = AviUtl2Version::new(2, 0, 54, 52);
706        assert_eq!(version7.to_string(), "v2.0.54az");
707    }
708
709    #[test]
710    fn test_cwstring_new() {
711        let s = "Hello, world!";
712        let cwstring = CWString::new(s).unwrap();
713        assert_eq!(unsafe { load_wide_string(cwstring.as_ptr()) }, s);
714
715        let s_with_nul = "Hello\0world!";
716        let err = CWString::new(s_with_nul).unwrap_err();
717        assert_eq!(err.nul_position(), 5);
718    }
719}