Sometimes I can figure it out, but certain functions I just can't.
For example I'm trying to recreate this example of capturing an image from a window in c++
https://docs.microsoft.com/en-us/windows/win32/gdi/capturing-an-image
In the example on that page I'm trying to use the GetObject method
GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);
There doesn't seem to be a GetObject method in window-rs so I presume GetObjectW is the same thing? (not sure)
These are the method signatures in rust and C++ and they look similar enough.
pub unsafe fn GetObjectW<'a, T0__: IntoParam<'a, HANDLE>>(
h: T0__,
c: i32,
pv: *mut c_void
) -> i32
int GetObjectW(
HANDLE h,
int c,
LPVOID pv
);
Anyway the issue I have is in the C++ code
hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
BITMAP bmpScreen;
GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);
If I try that in rust...
let hbmScreen = unsafe { CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top) };
let bmpScreen: BITMAP;
unsafe { GetObjectW(hbmScreen, 32, &bmpScreen) };
It doesn't work and i get stuck for the following reasons.
1/ The rust compiler complains if I use a reference &BITMAP and I don't understand what to give it instead.
mismatched types
expected raw pointer `*mut c_void`
found reference `&BITMAP
If I try cast a &BITMAP as *mut c_void, it won't let me.
2/ it doesn't seem to accept the same parameters as the c++ method as using hbmScreen give the following error
the trait bound `HBITMAP: windows::traits::into_param::IntoParam<'_, bindings::Windows::Win32::SystemServices::HANDLE>` is not satisfied
the following implementations were found:
<HBITMAP as windows::traits::into_param::IntoParam<'a, HGDIOBJ>>rustcE0277
main.rs(1, 1): required by a bound in this
windows.rs(344, 23): required by this bound in `GetObjectW`
I don't understand why this doesn't work.
Hope this makes sense, I'm trying to switch to windows-rs but it's been a massive struggle dealing with issues like this and i'm not sure how much is just my lack of knowledge on win32 programming and rust and how much is just the documentation isn't clear enough.
This is mostly about differences between C/C++ and Rust. There were also some misleading Rust compiler errors I ran into while reproducing this issue. But at the end of the day, these Win32 APIs were designed for C++ developers so they don't necessarily work as naturally in any other language. The WinRT APIs tend to be a lot friendlier to different languages.
The "GetObject" function is actually defined as follows in the original source headers:
WINGDIAPI int WINAPI GetObjectA(_In_ HANDLE h, _In_ int c, _Out_writes_bytes_opt_(c) LPVOID pv);
WINGDIAPI int WINAPI GetObjectW(_In_ HANDLE h, _In_ int c, _Out_writes_bytes_opt_(c) LPVOID pv);
#ifdef UNICODE
#define GetObject GetObjectW
#else
#define GetObject GetObjectA
#endif // !UNICODE
So GetObject is really just a macro and the metadata for the Windows SDK does not include macros so we end up with either GetObjectA or GetObjectW. Take your pick. 馃槈
There is a bug in the Win32 metadata that means that the result of CreateCompatibleBitmap cannot be directly passed to the GetObjectA function. https://github.com/microsoft/win32metadata/issues/406
At that point, it's just a matter of translating C++ to Rust. Here's a simple C++ example using GetObjectA:
int main()
{
auto dc = GetDC(0);
auto handle = CreateCompatibleBitmap(dc, 100, 100);
auto bitmap = BITMAP{};
GetObjectA(handle, sizeof(BITMAP), &bitmap);
assert(bitmap.bmWidth == 100);
}
Rust requires more type safety. Here's the equivalent Rust example:
use bindings::{
Windows::Win32::Gdi::{CreateCompatibleBitmap, GetDC, GetObjectA, BITMAP},
Windows::Win32::SystemServices::HANDLE,
Windows::Win32::WindowsAndMessaging::HWND,
};
fn main() {
unsafe {
let dc = GetDC(HWND(0));
let handle = CreateCompatibleBitmap(dc, 100, 100);
let mut bitmap = BITMAP::default();
GetObjectA(
HANDLE(handle.0),
std::mem::size_of::<BITMAP>() as i32,
&mut bitmap as *mut _ as _,
);
assert!(bitmap.bmWidth == 100);
}
}
many thanks, i think a lot of the issue may stem from the fact that I'm new to both rust and win32 programming, but many thanks for taking the time it really helps, as the more examples i have, the more I can figure it out myself, the above has gotten me stuck in many areas, but your example definitely will help me going forward.
Pardon my ignorance on the topic but can I use WinRT API instead of Win32 in all instances? For example can i use the WinRT API to do the above for any windows application? Or does it only work for applications that were created using WinRT API?
It depends on what functionality you're after. You can freely mix Win32 and WinRT APIs inside any Windows application. But you may find that the particular thing you're trying to do is only available through an older Win32 API. In this case, there are actually WinRT capture APIs in the Windows.Graphics.Capture namespace. Those would likely be far easier to use. @robmikh may have an example to share.
Here's a sample that uses the Windows.Graphics.Capture API using the windows crate: https://github.com/robmikh/screenshot-rs
The idea is to get a GraphicsCaptureItem that represents the window you wish to capture. This can be done with the IGraphicsCaptureItemInterop interface. The sample attempts to enumerate all windows and find one based on the window title, but if you already have the handle you can just look at create_capture_item_for_window in the sample.
After that, you'll need to setup your Direct3D11CaptureFramePool. The sample waits for a single frame to come back and then closes the capture. It then maps the d3d texture and extracts the bytes and encodes it to a png. All of that can be found in the take_screenshot function.
Most helpful comment
Here's a sample that uses the Windows.Graphics.Capture API using the
windowscrate: https://github.com/robmikh/screenshot-rsThe idea is to get a
GraphicsCaptureItemthat represents the window you wish to capture. This can be done with theIGraphicsCaptureItemInteropinterface. The sample attempts to enumerate all windows and find one based on the window title, but if you already have the handle you can just look atcreate_capture_item_for_windowin the sample.After that, you'll need to setup your
Direct3D11CaptureFramePool. The sample waits for a single frame to come back and then closes the capture. It then maps the d3d texture and extracts the bytes and encodes it to a png. All of that can be found in thetake_screenshotfunction.