Windows-rs & FFI ergonomic issues

I was writing some code using windows-rs to grab the system certs from the Windows API and encountered a bunch of things that didn't feel very pleasant, which made me wonder if there is a nicer way to do them:

  1. Wrapper structs for Drop, as per the norm for raw C API, you get a bunch of owned resources you need to free yourself sometimes, I found myself writing stuff like the following (e.g. For the CERT_CONTEXT from CertEnumCertificatesInStore):

    #[repr(transparent)]
    #[derive(Debug)]
    struct OwnedCertContext(*mut CERT_CONTEXT);
    
    impl Drop for OwnedCertContext {
        fn drop(&mut self) {
            unsafe {
                if !self.0.is_null() {
                    CertFreeCertificateContext(Some(self.0));
                }
            }
        }
    }
    

    But this then means the code is littered with cert_context.0 everywhere, which isn't the cleanest
    looking, is this the best way to do this? Anything better/more ergonomic?

  2. Many functions require you to first query for a buffer size, and then allocate the buffer yourself and call it again, e.g. CertGetEnhancedKeyUsage. But sometimes this buffer is also then used as a pointer to an array of structs, with additional spaces for strings after the array which the structs will point to. Such a buffer needs to be aligned to the struct size to be safe, and the allocation size will not necessarily be a multiple of the struct size. Basically, it assumes malloc semantics, which always allocates with the maximum alignment needed for anything. As such, I found myself resorting to raw std::alloc::alloc with Layout::from_size_align to be safe, and since I need this to also be dropped in case of an error, I also ended up writing this:

    impl Allocation {
        unsafe fn new(layout: Layout) -> Self {
            let ptr = alloc::alloc(layout);
            if ptr.is_null() {
                alloc::handle_alloc_error(layout);
            }
    
            return Self { ptr, layout };
        }
    }
    
    impl Drop for Allocation {
        fn drop(&mut self) {
            unsafe { alloc::dealloc(self.ptr, self.layout) };
        }
    }
    

    But this again, feels like something is missing, is there anything better, more ergonomic for this?

  3. I needed to create an owned rust Vec<u8> from a raw buffer returned from the API, so I followed what is outlined here: copy in std::ptr - Rust, but I'm wondering if there isn't any ready made function that does this instead of having to yet again copy that into my code.

  4. For passing strings, I used CString, though in Windows, you will often want to use a wide string for proper Unicode support, without assuming that the UTF-8 mode is enabled in the executable manifest, I wonder if there is anything similar for converting a Rust String to a wide C string.

This is not the norm for a C API. The norm is that you have the raw C API that just presents you the interface without adjustment. Then, on top of that, you have a high-level wrapper that adds additional features, safety, etc.

What I typically do for system calls I'm using a lot is build wrapper types for things like handles, then write wrappers around functions that have to take those to also accept my wrapper. The wrapper functions will then also do things like check for errors.

windows is a bit weird in that it doesn't really do this. It has both windows and windows-sys, so you'd expect higher-level bindings in windows, but they're a very thin veneer over the *-sys version. But then, it's also machine generated, and there's probably not enough information in the bindings to create those higher level bindings.

(On the other hand, COM and WinRT stuff does appear to get those higher-level bindings, again, probably because there's more useful type information available.)

Not that I'm aware of.

The problem is that you cannot mix allocators. If you take an allocation made by one allocator and pass it to a different one, that second allocator is not going to have any idea what to do with it, and you'll get a crash (if you're lucky). It's possible that Rust could be using the same underyling allocator, but that's not something I would ever, ever rely on.

If you get a buffer back from a Windows API call, you need to check the documentation and find out which allocator interface (and from what I recall, there are multiple such interfaces) you need to use to deallocate/manage that buffer, and then use it. Yes, that means more wrapper types.

One thing you might be able to do is use the Rust allocator interface to teach Vec how to talk to that specific allocator, and then you could just hand management of the buffer over to Vec. However, as far as I know, the necessary allocation stuff on the Rust side is not yet stable, so you can't really use it.

So more wrapper types, I'm afraid.

std::encode_utf16

1 Like

But when you implement the higher level wrapper you might still need some Drop wrappers like that, no? (Assuming the API doesn't map cleanly to Rust structs). I'm really just wondering if that's the best way to implement those.

Not mixing allocators in this case, but needing to supply an externally allocated buffer to a C API call, where I found myself needing to go directly to the Rust allocator to make sure the allocation is aligned as suitable for such an FFI scenario, and wondered if there isn't any existing wrapper for such an owned raw allocation from the Rust allocator.

encode_utf16 doesn't add a nul terminator though.

I'm not sure what you mean by this. You can just implement Drop on the wrappers to implement the clean-up logic. If such logic is not possible, I'd make Drop panic and require the wrapper values to be explicitly cleaned up.

Just a guess, here: I suspect it's because you just don't do this sort of thing when dealing with regular Rust code. Box and Vec will align things just fine. It comes up when dealing with FFI, but in that case there's a decent chance there are other special requirements involved (like using a specific allocator) and it's simpler to throw it in the "you sort it out" bucket.

The last time I dealt with anything in Win32 that involved allocation management, I also had to write all the allocation code by hand, but that was because it had to interact with specific allocators and structures that did not have a static size. I'm not sure how much value a stdlib abstraction over that would have provided.

But, hey, if you come up with a good, widely applicable interface for this, write it down and suggest it. It might get accepted and everyone who comes after you will have an easier time. :slight_smile:

str::encoode_utf16(s).chain(0)

(That's off the top of my head. I hate having to look up stdlib docs because all my links to it are local and I obviously can't use those in a forum post. :P)

1 Like