Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INetFwAuthorizedApplications fails to return _NewEnum and type returned appears incorrect #1317

Open
fgimian opened this issue Dec 30, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@fgimian
Copy link

fgimian commented Dec 30, 2024

Actual behavior

Calling the _NewEnum method on an instance of INetFwAuthorizedApplications fails with the following exception:

Unhandled exception. System.Runtime.InteropServices.InvalidOleVariantTypeException: Specified OLE variant is invalid.
   at System.StubHelpers.ObjectMarshaler.ConvertToManaged(IntPtr pSrcVariant)
   at Windows.Win32.NetworkManagement.WindowsFirewall.INetFwAuthorizedApplications.get__NewEnum()
   at Fires.Program.Main() in C:\Users\Fots\source\Firewallcs\Program.cs:line 12

The source generated also suggests it returns an object when I think it should either be IUnknown (which can be cast to IEnumVARIANT) or IEnumVARIANT directly.

This all works perfectly in windows-rs (see below for equivalent code).

Expected behavior

I expect to be able to retrieve the _NewEnum so I can iterate through the collection.

Repro steps

  1. NativeMethods.txt content:
INetFwMgr
NetFwMgr
IEnumVARIANT
NetFwAuthorizedApplication
  1. NativeMethods.json content (if present):

N/A

  1. Any of your own code that should be shared?
using Windows.Win32.NetworkManagement.WindowsFirewall;
using Windows.Win32.System.Ole;

namespace Firewallcs;

internal class Program
{
    static void Main()
    {
        var fwMgr = (INetFwMgr)new NetFwMgr();
        var authorizedApplications = fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications;
        var applicationsEnum = authorizedApplications._NewEnum;
    }
}

Here's the equivalent in Rust...

Cargo.toml

[package]
name = "firewall"
version = "0.1.0"
edition = "2021"

[dependencies]
windows-result = "0.2.0"

[dependencies.windows]
version = "0.58.0"
features = [
    "Win32_System_Com",
    "Win32_System_Ole",
    "Win32_NetworkManagement_WindowsFirewall",
]

main.rs

use std::iter;

use windows::{
    core::{Interface as _, VARIANT},
    Win32::{
        NetworkManagement::WindowsFirewall::{INetFwAuthorizedApplication, INetFwMgr, NetFwMgr},
        System::{
            Com::{CoCreateInstance, CoInitialize, IDispatch, CLSCTX_INPROC_SERVER},
            Ole::IEnumVARIANT,
        },
    },
};
use windows_result::Result;

fn main() -> Result<()> {
    unsafe {
        CoInitialize(None).ok()?;

        let firewall_manager: INetFwMgr = CoCreateInstance(&NetFwMgr, None, CLSCTX_INPROC_SERVER)?;
        let current_profile = firewall_manager.LocalPolicy()?.CurrentProfile()?;
        let authorized_applications = current_profile.AuthorizedApplications()?;

        let applications_enum = authorized_applications._NewEnum()?;
        let applications_enum = applications_enum.cast::<IEnumVARIANT>()?;
        applications_enum.Reset()?;

        let mut variants = iter::repeat(VARIANT::default())
            .take(authorized_applications.Count()? as usize)
            .collect::<Vec<VARIANT>>();
        let mut count = 0;
        applications_enum.Next(&mut variants, &mut count).ok()?;

        for variant in variants {
            let dispatch = IDispatch::try_from(&variant)?;
            let app = dispatch.cast::<INetFwAuthorizedApplication>()?;
            println!(
                "- Name: {}, Enabled: {}, RemoteAddresses: {}",
                app.Name()?,
                app.Enabled()?.as_bool(),
                app.RemoteAddresses()?
            );
            println!(
                "  Scope: {:?}, ProcessImageFileName: {}, IpVersion: {:?}",
                app.Scope()?,
                app.ProcessImageFileName()?,
                app.IpVersion()?
            );
        }
    }

    Ok(())
}

Context

  • CsWin32 version: [e.g. 0.4.422-beta]: 0.3.106
  • Win32Metadata version (if explicitly set by project): N/A
  • Target Framework: [e.g. netstandard2.0]: net8.0-windows
  • LangVersion (if explicitly set by project): [e.g. 9]: N/A

Thanks a lot in advance!
Fotis

@fgimian fgimian added the bug Something isn't working label Dec 30, 2024
@AArnott
Copy link
Member

AArnott commented Jan 22, 2025

This looks like a bug in how we emit code when we allow marshaling. But I don't know what that bug is yet.

As a possible workaround, you can set allowMarshaling: false in NativeMethods.json and then reach the loop like this:

unsafe
{
    var fwMgr = (INetFwMgr.Interface)new NetFwMgr();
    INetFwAuthorizedApplications* authorizedApplications = fwMgr.LocalPolicy->CurrentProfile->AuthorizedApplications;
    var pUnkApplicationsEnum = authorizedApplications->_NewEnum;
    var pEnum = (IEnumVARIANT.Interface)Marshal.GetObjectForIUnknown((IntPtr)pUnkApplicationsEnum);
    VARIANT v = default;
    uint fetched = 0;
    while (pEnum.Next(1, &v, &fetched).Succeeded)
    {
    }
}

Of course with marshaling disallowed, you have to own all COM reference counted pointers, etc. It's hard to get it right. But there's less for CsWin32 to get wrong as well, which is why this approach doesn't throw.

@fgimian
Copy link
Author

fgimian commented Jan 22, 2025

This looks like a bug in how we emit code when we allow marshaling. But I don't know what that bug is yet.

As a possible workaround, you can set allowMarshaling: false in NativeMethods.json and then reach the loop like this:

unsafe
{
var fwMgr = (INetFwMgr.Interface)new NetFwMgr();
INetFwAuthorizedApplications* authorizedApplications = fwMgr.LocalPolicy->CurrentProfile->AuthorizedApplications;
var pUnkApplicationsEnum = authorizedApplications->_NewEnum;
var pEnum = (IEnumVARIANT.Interface)Marshal.GetObjectForIUnknown((IntPtr)pUnkApplicationsEnum);
VARIANT v = default;
uint fetched = 0;
while (pEnum.Next(1, &v, &fetched).Succeeded)
{
}
}

Of course with marshaling disallowed, you have to own all COM reference counted pointers, etc. It's hard to get it right. But there's less for CsWin32 to get wrong as well, which is why this approach doesn't throw.

Thanks heaps, yeah I worry disabling marshalling will maybe make my other uses of the library a little less pretty. 😄

Cheers
Fotis

@AArnott
Copy link
Member

AArnott commented Jan 22, 2025

If you want to copy out the definition of the interface into your user code, CsWin32 will stop emitting it. You can then make changes to it to tinker till you find out how to make it work. If you get to that point, please share what you learn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants