Using Windows COM Automation with Rust - windows

We're using a Brother label printer (QL-series) via its programming interface/SDK called 'bPac'. The tool, of which the label printing facility is part, is currently being rewritten from Ruby to Rust. During this process, I got stuck on the Win32/COM/OLE thing in Rust. A minimal working example in Ruby would be simple enough:
doc = WIN32OLE.new "bpac.Document"
doc.open 'some_label.lbx'
doc.SetPrinter "Brother QL-810W", true
print_opts = 0
doc.StartPrint("", print_opts)
doc.PrintOut(1, print_opts)
doc.EndPrint
I'd like to have a similar simple working example in Rust to start off with. As I'm not familiar with the Windows API the windows-rs crate is quite overwhelming. I figured, that I probably need the System::Com part from it. Here's what I started off with:
use windows::Win32::System::{Com, Ole};
use ::windows::core::Result;
pub fn print() {
unsafe { Com::CoInitializeEx(std::ptr::null(), Com::COINIT_APARTMENTTHREADED) }.unwrap();
let clsid = unsafe { Com::CLSIDFromProgID("bpac.Document") };
println!("We've got a CLSID: {:?}", clsid);
let obj: Result<Com::IDispatch> = unsafe { Com::CoCreateInstance(&clsid.unwrap(), None, Com::CLSCTX_ALL) };
println!("IDispatch: {:?}", obj);
}
This way I can acquire an IDispatch object, which I should be able to query for available methods and properties. Having trouble calling into this low-level (very close to the C-metal) API. I found win-idispatch crate, but that does not seem to play ball with windows-rs... :-/

I wanted to do something similar: COM Office/Excel Automation using Rust.
In a nutshell I've built a wrapper around IDispatch::GetIDsOfNames() and IDispatch::Invoke() and for arguments one would use VARIANT.
The following resources helped me build a solution:
https://stuncloud.wordpress.com/2021/08/17/rust_com/
https://qiita.com/benki/items/42099c58e07b16293609
https://learn.microsoft.com/en-us/previous-versions/office/troubleshoot/office-developer/automate-excel-from-c

Related

How to print out tracing message in Substrate runtime development

When working on Parity Substrate runtime development, how can I print out debug message for tracing and inspecting my variables?
Both of the above answers are correct in their own sense/time. Here's a more accurate overview:
runtime_io::print("..."); has been moved. You can now use the same function from sp-runtime::print(). These will be visible in a log target named runtime. So you'd have to do RUST_LOG=runtime=debug. You are still calling into sp_io under the hood though. Also, note that frame_support is re-exporting this for you. Most pallets need frame_support anyhow and this maeks the usage easier.
If you want to compile for wasm and native, and want prints only for native execution, use sp_std::if_std!{} macro.
Finally, you can use frame_support::debug module. This module provides wrappers around the above two to make the usage easier and more rust-like. Similar to a normal logger, you can use debug::native::warn!(...) etc.
A final useful tip is to: when possible, you can just bloat your code with println! and do SKIP_WASM_BUILD=1 cargo run [xxx]. This is helpful when you are developing and want quick debug prints without any of the setup explained above.
You can also use the if_std! macro included with sp-std:
https://github.com/paritytech/substrate/pull/2979
if_std! is a feature gate that should only be run when std feature is enabled.
Example
sp_std::if_std! {
// This code is only being compiled and executed when the `std` feature is enabled.
println!("Hello native world");
}
This is better because you can println variables and stuff rather than simply printing a string.
As a newcomer to Substrate development, the most direct way I found is with runtime_io::print().
Example:
use runtime_io::{ self };
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn deposit_event<T>() = default;
pub fn my_func(origin) -> Result {
runtime_io::print("Hello World");
Ok(());
}
}
}
The message will then appear in the console. Pay quick attention to it as it is constantly scrolling.
For a complete example, refer to the TCR tutorial example in github.
you can use the log crate, add it to your cargo.toml and use it like this:
log::info!("hello {}",substrate);
source : https://docs.substrate.io/test/debug/

Is it possible to use pyobjc with a privilved XPC helper tool and XPCInterface API?

I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:#protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(#"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(#"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a #selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom #protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'#'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v#:##?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface..
I've filed an issue for this in PyObjC's tracker: https://github.com/ronaldoussoren/pyobjc/issues/256.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://github.com/ronaldoussoren/pyobjc/blob/415d8a642a1af7f2bd7285335470098af4962dae/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol") to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
This solution is now documented in the official documentation, here: https://pyobjc.readthedocs.io/en/latest/notes/using-nsxpcinterface.html
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev#lists.sourceforge.net (PyObjC mailinglist).

boost interprocess file_lock understanding/usage

I have been having issues using an anonymous mutex (boost::interprocess::interprocess_mutex) in a boost::interprocess::managed_shared_memory instance. Namely, issues arise if the software crashes; the mutex may remain locked (depending on its state at time of crash). It can make debugging interesting too :).
My understanding is that I can substitute the interprocess_mutex with boost::interprocess::file_lock (FL). #DaveF posted some questions that I would like to build upon. I'd like to have a good understanding what I'm getting myself into before I put FL into use.
Can I use an anonymous boost::interprocess::condition_variable (CV) with FL? Having looked through the code, it appears that it will work.
In using a CV, am I opening myself up to the same problems I have experienced when using mutex (ie. if the application unexpectedly ends without proper cleanup/finalisation)?
What is the best way to create a FL. I've thought about something similar to the following...
Note code may not compile:
namespace bi = boost::interprocess;
namespace bf = boost::filesystem;
const std::string strSharedMemName = std::string("cp_shdmem_") + std::to_string(nIdx);
const std::string strNamedMutexName = strSharedMemName + "_mtx";
// I'm working on Linux, but would like to Boost to create a temporary file path.
const bf::path pathTmpFile =
bf::temp_directory_path() / (strNamedMutexName + ".txt");
{
// 1. So can I just create the file? What happens if it exists? Boost docs say this
// about the file_lock constructor:
// "Throws interprocess_exception if the file does not exist
// or there are no operating system resources."
// 2. What happens if file already exists?
bf::ofstream f(pathTmpFile);
}
// Create.
bi::file_lock lockFile(pathTmpFile.string().c_str());
// Lock.
bi::scoped_lock<bi::file_lock> lockNamed(lockFile);
Platform specifics:
Ubuntu 17.10
Boost 1.63
GCC 7.2

How build a composable wrapper that wraps another composable unit with Binding.scala

I am experimenting making composable components using binding.scala. I would like to be able to have a component that can be used to wrap other components that are passed in. For example: a card component that wraps a styled box around any other arbitrary #dom function. I have attempted several approaches but have realized that because of the #dom macro, types seem to be more complicated than they appear.
Below, I have included an approach that doesn't work, but shows the intent. I want to be able to call wrapperMarkup and pass it contentMarkup.
I have found many examples where a data is passed into a #dom function and rendered, but no examples that show how to pass in another #dom function or the results from a #dom call.
Is there a good way to accomplish this?
type MarkupFun = ()=>Binding[Div]
#dom
def contentMarkup():Binding[Div] = {
<div>card Content</div>
}
#dom
def wrapperMarkup(f:MarkupFun):Binding[Div] = {
//<div>card wrapper {f.bind}</div> // What I want that doesn't work
<div>card wrapper {contentMarkup().bind}</div> // works but not what I want.
}
Soon after I posted the question, I found the obvious answer. I was failing to invoke the function before invoking bind.
#dom
def wrapperMarkup(f:MarkupFun):Binding[Div] = {
<div>card wrapper {f().bind}</div>
}
But, any other best practice suggestions would be great.

Calling checkCurrentDictionary() from addon crashes FF - why?

I'm tyring to call the method checkCurrentDictionary() of nsIEditorSpellCheck from within an add-on. The relevant code I use is:
var editorSpellCheck = Cc["#mozilla.org/editor/editorspellchecker;1"].createInstance(Ci.nsIEditorSpellCheck);
editorSpellCheck.checkCurrentDictionary();
This immediately crashes the Fx. What is going wrong here?
So this probably has something to do with the fact that nsIEditorSpellCheck is not a scriptable interface.
Basically, a scriptable interface is one that can be used from JavaScript.
If you want to access the spell check service you can do something like:
let editor = editableElement.editor;
if (!editor) {
let win = editableElement.ownerDocument.defaultView;
editor = win.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIEditingSession).
getEditorForWindow(win);
}
if (!editor)
throw new Error("Unable to find editor for element " + editableElement);
(The above is from http://dxr.mozilla.org/mozilla-central/source/editor/AsyncSpellCheckTestHelper.jsm which is MPL).
Then you can use the InlineSpellCheck.jsm to do some crazy stuff.
I'm not sure what you want to do though, so perhaps you should ask that more specific question as a new question.

Resources