0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust で Excel オートメーション ver0.2.0

Posted at

動機

下記記事のコードがだいぶ古くなっていたし、リファクタリングついでに書き直しました。

環境

Windows 11 25H2
Microsoft® Excel® for Microsoft 365 MSO (バージョン 2511)
rustc 1.92.0
windows crate 0.62.2

解説

feature flag の変更

windows crate の[features]が変更されているので、それに対応します。

Cargo.toml
[dependencies.windows]
- version = "0.39"
+ version = "0.62"
features = [
  "Win32_System_Com",
  "Win32_System_Ole",
-  "Win32_Foundation",
+  "Win32_System_Variant",
]

COM の初期化

COM の初期化も構造体にまとめました。

src/main.rs
struct Com;

+ impl Com {
+     fn new() -> Result<Self> {
+         unsafe { CoInitialize(None).ok()? };
+         Ok(Self)
+     }
+ }

impl Drop for Com {
    fn drop(&mut self) {
        unsafe { CoUninitialize() };
    }
}

fn main() -> Result<()> {
-   unsafe { CoInitialize(ptr::null())?; }
    let _com = Com::new()?;
    // omit
    Ok(())
}

VARIANT 構造体

ヘルパー関数を用いて各タイプ(VT_I4VT_BSTR)に対応したVARIANT構造体の初期化を行っていましたが、windows crate でimpl From<i32> for VARIANTimpl From<&str> for VARIANTが実装されているのでそちらを使います。ヘルパー関数は削除しました。

src/main.rs
- unsafe fn variant_int(value: i32) -> Variant {
-    let mut variant = VARIANT::default();
-    (*variant.Anonymous.Anonymous).vt = VT_I4.0 as u16;
-    (*variant.Anonymous.Anonymous).Anonymous.lVal = value;
-    Variant(variant)
-}
-
-unsafe fn variant_bstr<S: AsRef<str>>(value: S) -> Variant {
-    let mut variant = VARIANT::default();
-    (*variant.Anonymous.Anonymous).vt = VT_BSTR.0 as u16;
-    (*variant.Anonymous.Anonymous).Anonymous.bstrVal = ManuallyDrop::new(value.as_ref().into());
-    Variant(variant)
-}
-
- let visible = variant_int(0);
- let param = variant_bstr("path_to_excel.xlsx");

+ let visible: VARIANT = 0.into();
+ let param: VARIANT = "path_to_excel.xlsx".into();

まとめ

以下、コピペ用サンプルコード

Cargo.toml
[package]
name = "excel_to_pdf"
version = "0.2.0"
edition = "2024"

[dependencies]
anyhow = "1.0"

[dependencies.windows]
version = "0.62"
features = [
  "Win32_System_Com",
  "Win32_System_Ole",
  "Win32_System_Variant",
]
main.rs
use anyhow::{Context, Result, ensure};
use std::path::Path;
use windows::{
    Win32::System::{
        Com::{
            CLSCTX_LOCAL_SERVER, CLSIDFromProgID, CoCreateInstance, CoInitialize, CoUninitialize,
            DISPATCH_FLAGS, DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT,
            DISPPARAMS, IDispatch,
        },
        Ole::DISPID_PROPERTYPUT,
        Variant::{VARIANT, VT_DISPATCH},
    },
    core::{GUID, HSTRING, PCWSTR, w},
};

struct Com;

impl Com {
    fn new() -> Result<Self> {
        unsafe { CoInitialize(None).ok()? };
        Ok(Self)
    }
}

impl Drop for Com {
    fn drop(&mut self) {
        unsafe { CoUninitialize() };
    }
}

struct Variant(VARIANT);

impl Variant {
    fn dispval(&self) -> Result<&IDispatch> {
        ensure!(self.0.vt().eq(&VT_DISPATCH));
        unsafe {
            (*self.0.Anonymous.Anonymous)
                .Anonymous
                .pdispVal
                .as_ref()
                .context("no disp val")
        }
    }
}

fn auto_wrap(
    auto_type: DISPATCH_FLAGS,
    pdisp: &IDispatch,
    ptname: &str,
    args: Vec<VARIANT>,
) -> Result<Variant> {
    // Get DISPID for name passed...
    let ptname: HSTRING = ptname.into();
    let mut disp_id = 0;
    unsafe {
        pdisp.GetIDsOfNames(
            &GUID::default(),
            &PCWSTR::from_raw(ptname.as_ptr()),
            1,
            0x0400, // LOCALE_USER_DEFAULT 1024u32
            &mut disp_id,
        )?
    };

    // Build DISPPARAMS
    let mut dp = DISPPARAMS {
        cArgs: args.len() as _,
        rgvarg: args.as_ptr() as _,
        ..Default::default()
    };

    // Handle special-case for property-puts!
    if (auto_type & DISPATCH_PROPERTYPUT).ne(&DISPATCH_FLAGS(0)) {
        dp.cNamedArgs = 1;
        dp.rgdispidNamedArgs = &DISPID_PROPERTYPUT as *const _ as _;
    }

    let mut vresult = VARIANT::default();

    // Make the call!
    unsafe {
        pdisp.Invoke(
            disp_id,
            &GUID::default(),
            0x0800,
            auto_type,
            &dp,
            Some(&mut vresult),
            None,
            None,
        )?
    };

    Ok(Variant(vresult))
}

fn excel_to_pdf(path: &str) -> Result<()> {
    // Initialize COM for this thread...
    let _com = Com::new()?;

    // Get CLSID for our server...
    let cls_id = unsafe { CLSIDFromProgID(w!("Excel.Application")) }?;

    // Start server and get IDispatch...
    let pxlapp: IDispatch = unsafe { CoCreateInstance(&cls_id, None, CLSCTX_LOCAL_SERVER) }?;

    // Make it visible (i.e. app.visible = 1)
    let visible: VARIANT = 0.into();
    auto_wrap(DISPATCH_PROPERTYPUT, &pxlapp, "Visible", vec![visible])?;

    // Suppress alerts
    let display_alerts: VARIANT = 0.into();
    auto_wrap(
        DISPATCH_PROPERTYPUT,
        &pxlapp,
        "DisplayAlerts",
        vec![display_alerts],
    )?;

    // Get Workbooks collection
    let result = auto_wrap(DISPATCH_PROPERTYGET, &pxlapp, "Workbooks", vec![])?;
    let pxlbooks = result.dispval()?;

    // Call Workbooks.Open() to get a new workbook...
    let param: VARIANT = path.into();
    let result = auto_wrap(DISPATCH_PROPERTYGET, pxlbooks, "Open", vec![param])?;
    let pxlbook = result.dispval()?;

    // Get ActiveSheet object
    let result = auto_wrap(DISPATCH_PROPERTYGET, pxlbook, "ActiveSheet", vec![])?;
    let pxlsheet = result.dispval()?;

    // Call wb.ActiveSheet.ExportAsFixedFormat(xlTypePDF, "path to pdf")
    let typ: VARIANT = 0.into();
    let path = Path::new(path);
    let parent = path.parent().context("no parent")?;
    let file_stem = path.file_stem().context("no file stem")?;
    let pdf_path = parent.join(format!("{}.pdf", file_stem.to_str().context("no str")?));
    let pdf_path: VARIANT = pdf_path.to_str().context("no pdf path")?.into();
    auto_wrap(
        DISPATCH_METHOD,
        pxlsheet,
        "ExportAsFixedFormat",
        vec![pdf_path, typ],
    )?;

    // wb.Close(False)
    let close: VARIANT = false.into();
    auto_wrap(DISPATCH_METHOD, pxlbook, "Close", vec![close])?;

    // Tell Excel to quit (i.e. App.Quit)
    auto_wrap(DISPATCH_METHOD, &pxlapp, "Quit", vec![])?;

    Ok(())
}

fn main() -> Result<()> {
    excel_to_pdf("C:\\path\\to\\Book1.xlsx")?;
    Ok(())
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?