Not sure how to use IDesktopWallpaper - winapi

In the documentation there is a struct 'IDesktopWallpaper' with a method named 'GetWallpaper'. The method takes a reference to 'self' but there is no constructor method for 'IDesktopWallpaper'.
use windows::{
core::*,
Win32::UI::Shell::IDesktopWallpaper,
};
fn main() -> Result<()> {
unsafe {
// ?????
IDesktopWallpaper::GetWallpaper(&self, monitorid);
}
Ok(())
}
What am I supposed to do in order to get my desired outcome?

COM generally uses factory methods to construct COM objects. The "standard" factory method for classic COM is CoCreateInstance. It needs a class ID that identifies the specific implementation (in case there are multiple) and an interface ID that names the requested interface.
The windows crate exposes class IDs differently from the Windows SDK: The latter frequently uses CLSID_-prefixes, wheres the former doesn't. DesktopWallpaper in the windows crate is the same GUID as CLSID_DesktopWallpaper in the Windows SDK.
Another difference is that CoCreateInstance in the windows crate is generic over its returned interface type (as opposed to taking the address of a type-erased void* in the Windows SDK). Clients will need to explicitly name the interface type they are requesting.
The following code initializes COM (required), instantiates a COM object, and returns an IDesktopWallpaper interface for further use:
use windows::{
core::Result,
Win32::{
System::Com::{CoCreateInstance, CoInitialize, CLSCTX_ALL},
UI::Shell::{DesktopWallpaper, IDesktopWallpaper},
},
};
fn main() -> Result<()> {
// Initialize COM
unsafe { CoInitialize(None) }?;
// Create a DesktkopWallpaper object and return its IDesktopWallpaper interface
let wallpaper: IDesktopWallpaper =
unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL) }?;
// Use the COM object
unsafe { wallpaper.GetWallpaper(...) };
Ok(())
}
You'll need to have the following in your Cargo.toml file:
[dependencies.windows]
version = "0.42.0"
features = [
"Win32_UI_Shell",
"Win32_System_Com",
]

Related

Implementing a Windows Credential Provider

I recently discovered the windows-rs framework and have been looking to build a Windows Credential Provider in Rust by implementing their ICredentialProvider COM interface.
I've been working on a proof-of-concept implementation using the information put together under one of the existing issues, but I'm not sure how to actually expose the compiled rust as a proper DLL to then register with the windows system.
use std::cell::RefCell;
use windows::{
core::implement,
Win32::UI::Shell::{ICredentialProvider, ICredentialProvider_Impl},
};
fn main() -> windows::core::Result<()> {
#[implement(ICredentialProvider)]
struct Provider {
mutable_state: RefCell<u32>,
}
impl Provider {
fn new() -> Self {
Self {
mutable_state: RefCell::new(0),
}
}
}
impl ICredentialProvider_Impl for Provider {
fn Advise(
&self,
pcpe: &core::option::Option<windows::Win32::UI::Shell::ICredentialProviderEvents>,
upadvisecontext: usize,
) -> windows::core::Result<()> {
*self.mutable_state.borrow_mut() = 42;
todo!();
}
fn GetCredentialAt(
&self,
dwindex: u32,
) -> windows::core::Result<windows::Win32::UI::Shell::ICredentialProviderCredential>
{
todo!();
}
fn GetCredentialCount(
&self,
pdwcount: *mut u32,
pdwdefault: *mut u32,
pbautologonwithdefault: *mut windows::Win32::Foundation::BOOL,
) -> windows::core::Result<()> {
todo!();
}
fn GetFieldDescriptorAt(
&self,
dwindex: u32,
) -> windows::core::Result<
*mut windows::Win32::UI::Shell::CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR,
> {
todo!();
}
fn GetFieldDescriptorCount(&self) -> windows::core::Result<u32> {
todo!();
}
fn SetSerialization(
&self,
pcpcs: *const windows::Win32::UI::Shell::CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION,
) -> windows::core::Result<()> {
todo!();
}
fn SetUsageScenario(
&self,
cpus: windows::Win32::UI::Shell::CREDENTIAL_PROVIDER_USAGE_SCENARIO,
dwflags: u32,
) -> windows::core::Result<()> {
todo!();
}
fn UnAdvise(&self) -> windows::core::Result<()> {
todo!();
}
}
Ok(())
}
I compiled a Sample Credential Provider written in C++, supplied by Windows in their SDK, and used a tool to view the exported functions available in the generated DLL
There have been similar efforts in the windows-rs community to expose rust for WinRT but this COM interface needed for the Credential Provider is different enough I don't really know where to begin.
Are there any rust tricks to generate a similar DLL that can expose my interface and make it available to windows? Any help is appreciated.
A credential provider needs to be implemented as a COM server. A COM server is a PE image (EXE or DLL) that provides the following exports:
DllGetClassObject
DllCanUnloadNow1
DllGetClassObject is where the magic happens: It checks to see if the requested class (identified by rclsid) is implemented by the module, and subsequently returns a pointer to the requested interface riid (commonly an IClassFactory or IClassFactory2 interface). Once a client (the operating system, in case of a credential provider) received a class factory, it can use it to instantiate objects that implement interfaces such as the ICredentialProvider interface.
The key point here is that those two exports are all that's required for a module to be a COM server, that can expose arbitrary interfaces2.
That covers the systemic invariants. Moving into Rust the following issues need to be addressed:
Problem statement
Create a crate that produces a DLL
Instruct the linker to export symbols by a given name
Implement the interfaces
Implement the exported functions
Solution
First up, let's create a library crate. The following will do:
cargo new --lib cp_demo
Add this to Cargo.toml (removing anything beyond the [package] table):
[lib]
crate-type = ["cdylib"]
That addresses the first issue by having the crate produce a DLL. Running cargo build will produce cp_demo.dll inside targets/debug (assuming default settings). Mind you, this isn't making forward progress just yet. It merely creates a DLL that doesn't export anything (evidenced by running dumpbin /EXPORTS targets\debug\cp_demo.dll). It merely removes the obligation to have a fn main().
Having an "empty" DLL in place, we'll want to expose functionality to the outside world. The mechanics are very similar to the referenced resources, requiring a combination of an extern "system" block as well as the no_mangle attribute, so that the exports can be discovered by the system by name.
The former designates the calling convention, the contract between callers (the system) and callees (the implementation), formalizing how arguments are passed and who is responsible for (stack) cleanup on return. The latter instructs the linker to keep the exported symbols undecorated, so that the system can discover them by calling GetProcAddress(hmod, "DllGetClassObject"), for example.
Dumping the following into src/lib.rs
use std::{ffi, ptr};
use windows::{
core::{GUID, HRESULT},
Win32::Foundation::{CLASS_E_CLASSNOTAVAILABLE, E_POINTER, S_OK},
};
#[no_mangle]
extern "system" fn DllGetClassObject(
_rclsid: *const GUID,
_riid: *const GUID,
ppv: *mut *mut ffi::c_void,
) -> HRESULT {
// Implement basic COM contract
if ppv.is_null() {
E_POINTER
} else {
unsafe { *ppv = ptr::null_mut() };
CLASS_E_CLASSNOTAVAILABLE
}
}
#[no_mangle]
extern "system" fn DllCanUnloadNow() -> HRESULT {
// It's always safe to unload this module
S_OK
}
and updating Cargo.toml to include
[dependencies.windows]
version = "0.44.0"
features = [
"Win32_Foundation",
]
complies into a credential provider skeleton DLL. Running dumpbin /EXPORTS targets\debug\cp_demo.dll again produces output that includes
ordinal hint RVA name
1 0 00001080 DllCanUnloadNow = DllCanUnloadNow
2 1 00001000 DllGetClassObject = DllGetClassObject
Sweet! Now we have a credential provider that doesn't provide anything (and certainly not credentials). But it looks like a potential credential provider to the system already, that can be registered using the following .reg script (make sure to use a fresh GUID; this one won't be "globally unique" anymore by the time you're reading this).
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{DED30376-B312-4168-B2D3-2D0B3EADE513}]
#="cp_demo"
[HKEY_CLASSES_ROOT\CLSID\{DED30376-B312-4168-B2D3-2D0B3EADE513}]
#="cp_demo"
[HKEY_CLASSES_ROOT\CLSID\{DED30376-B312-4168-B2D3-2D0B3EADE513}\InprocServer32]
#="cp_demo.dll"
"ThreadingModel"="Apartment"
The interface implementations follow the pattern you've already used in the question: Declare a custom type to hold local state (if any), apply the #[implement] attribute, and supply an implementation for the generated trait named <interface>_Impl.
For the ICredentialProvider interface3 this would roughly look like what is in the question. The only exception being that the following snippet replaces the todo!()'s with returning error codes, as it isn't legal for panics to cross the ABI4.
#[implement(ICredentialProvider)]
struct Provider {
_mutable_state: cell::RefCell<u32>,
}
impl Provider {
fn new() -> Self {
Self {
_mutable_state: cell::RefCell::new(0),
}
}
}
impl ICredentialProvider_Impl for Provider {
fn SetUsageScenario(
&self,
_cpus: CREDENTIAL_PROVIDER_USAGE_SCENARIO,
_dwflags: u32,
) -> Result<()> {
Err(E_NOTIMPL.into())
}
// ...
}
This isn't doing anything useful, other than failing gracefully in case the system manages to instantiate this implementation. To do this it needs an IClassFactory implementation that does:
#[implement(IClassFactory)]
struct ProviderFactory;
impl IClassFactory_Impl for ProviderFactory {
fn CreateInstance(
&self,
punkouter: &core::option::Option<windows::core::IUnknown>,
riid: *const windows::core::GUID,
ppvobject: *mut *mut core::ffi::c_void,
) -> windows::core::Result<()> {
// Validate arguments
if ppvobject.is_null() {
return Err(E_POINTER.into());
}
unsafe { *ppvobject = ptr::null_mut() };
if riid.is_null() {
return Err(E_INVALIDARG.into());
}
let riid = unsafe { *riid };
if punkouter.is_some() {
return Err(CLASS_E_NOAGGREGATION.into());
}
// We're only handling requests for `IID_ICredentialProvider`
if riid != ICredentialProvider::IID {
return Err(E_NOINTERFACE.into());
}
// Construct credential provider and return it as an `ICredentialProvider`
// interface
let provider: ICredentialProvider = Provider::new().into();
unsafe { *ppvobject = mem::transmute(provider) };
Ok(())
}
fn LockServer(&self, _flock: windows::Win32::Foundation::BOOL) -> windows::core::Result<()> {
Err(E_NOTIMPL.into())
}
}
This represents a fully functional class factory for an ICredentialProvider interface implemented by Provider. The better part of CreateInstance() consists of argument validation as mandated by the contract for IClassFactory::CreateInstance. The actual magic happens here:
let provider: ICredentialProvider = Provider::new().into();
This is doing a lot! Provider::new() is the obvious part: It instantiates a new Provider object. Not nearly as obvious is what the into() part does. It exercises the following From trait implementation that is ultimately generated by the #[implement] macro (cargo-expand is an indispensable tool for uncovering those hidden details):
impl ::core::convert::From<Provider> for ICredentialProvider {
fn from(this: Provider) -> Self {
let this = Provider_Impl::new(this);
let mut this = ::core::mem::ManuallyDrop::new(::std::boxed::Box::new(this));
let vtable_ptr = &this.vtables.0;
unsafe { ::core::mem::transmute(vtable_ptr) }
}
}
It takes a Provider instance, moves it into a (generated) Provider_Impl, then moves that into heap storage (Box::new()), wraps everything behind a ManuallyDrop, and finally transmutes the object pointer into the respective interface pointer.
Every bit of this is of crucial importance: Moving the Provider instance into heap memory makes sure that it remains valid across stack unwinding as part of function return, wrapping the Box inside a ManuallyDrop inhibits running the Drop implementation as the object falls out of scope (which would otherwise decrement the reference count to 0, destroying the object along the way), and returning an interface pointer allows the system to call into an unknown implementation through a known interface.
A corollary of the latter is that object cleanup is at the discretion of the implementation: When the reference count drops to 0, as clients call through Release(), it is the concrete implementation that frees resources (Provider_Impl::Release() specifically), irrespective of who the caller happens to be. Provider_Impl makes sure to match the allocation strategy used by the From trait implementation, so we don't have to worry about this detail.
A note on the LockServer implementation: It retuns E_NOTIMPL, mostly because I'm not confident to have understood it's purpose. This doesn't appear to have had any adverse effect while debugging, so I'm leaving it for now (more on this later).
With all that in place, we are in a good position to socialize: We have an ICredentialProvider implementation (Provider), that'll faithfully respond with E_NOTIMPL on every request, and an IClassFactory implementation (ProviderFactory), that'll dutifully procure as many of the aforementioned rascals as requested. What's missing in becoming BFF with the OS is to allow it to partake in our parenthood.
Detour on credential provider lookup and instantiation: Whenever the system needs to discover credential providers, it enumerates all values under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\ key in the registry. Each value's data names a UUID (generally referred to as a "GUID" in Windows programming) that identifies a credential provider implementation.
With a GUID identifying a COM interface implementation, what follows is rather generic COM infrastructure business. This applies to all of COM, not just credential providers. The (approximately accurate) algorithm is this:
Look up the GUID under the HKEY_CLASSES_ROOT\CLSID\ key. Its InprocServer32's data contains the (fully qualified) path to the implementing module.
Load the module and request an export called DllGetClassObject (the literal name is part of the contract, hence the #[no_mangle] attribute).
Request a class factory capable of instantiating a COM object identified by the given GUID (take note that GUID's are used to identify both COM interfaces as well as interface implementations, or "COM objects"; the former is generally spelled IID as in "interface ID", whereas the latter is referred to as CLSID as in "class ID").
Have the class factory create an instance.
That out of the way, it should be apparent why the .reg script above writes to two different keys, and also what's required to nudge the system into acknowledging this creation of our own: Step 3. above is the missing link, so let's fill that in:
#[no_mangle]
extern "system" fn DllGetClassObject(
rclsid: *const GUID,
riid: *const GUID,
ppv: *mut *mut ffi::c_void,
) -> HRESULT {
// The "class ID" this credential provider is identified by. This value needs to
// match the value used when registering the credential provider (see the .reg
// script above)
const CLSID_CP_DEMO: GUID = GUID::from_u128(0xDED30376_B312_4168_B2D3_2D0B3EADE513);
// Validate arguments
if ppv.is_null() {
return E_POINTER;
}
unsafe { *ppv = ptr::null_mut() };
if rclsid.is_null() || riid.is_null() {
return E_INVALIDARG;
}
let rclsid = unsafe { *rclsid };
let riid = unsafe { *riid };
// The following isn't strictly correct; a client *could* request an interface other
// than `IClassFactory::IID`, which this implementation is simply failing.
// This is safe, even if overly restrictive
if rclsid != CLSID_CP_DEMO || riid != IClassFactory::IID {
return CLASS_E_CLASSNOTAVAILABLE;
}
// Construct the factory object and return its `IClassFactory` interface
let factory: IClassFactory = ProviderFactory.into();
unsafe { *ppv = mem::transmute(factory) };
S_OK
}
#[no_mangle]
extern "system" fn DllCanUnloadNow() -> HRESULT {
// Since we aren't tracking module references (yet), it's never safe to unload this
// module
S_FALSE
}
This is following a familiar pattern: Most of DllGetClassObject() is argument validation, with let factory: IClassFactory = ProviderFactory.into(); doing the actual work.
The only difference here being that ProviderFactory is stateless (a "unit struct" in Rust parlance). It doesn't carry any information beyond it being a type. Which doesn't immediately appear to be useful, though it does allow us to implement traits on it (such as From). With ProviderFactory a unit struct expression we can call into() on it, kicking off the same machinery as described above, leaving use with a manually managed COM object living in heap memory so that we can return a pointer to it (*ppv = mem::transmute(factory)).
Note that the DllCanUnloadNow implementation changed from returning S_OK to S_FALSE. This isn't wrong as such, but it's also not what I'd like to do1. To address this we would need to record all references into this module, incrementing the reference count on every object creation, and decrementing it whenever objects implemented by this module get destroyed. I'm not entirely sure how to do that yet.
The final step closes the loop. With the above we can compile a credential provider DLL, allow the system to discover it and load the module, request a class factory from it, and have it create a credential provider. The credential provider doesn't do anything just yet; fleshing out the ICredentialProvider skeleton with functionality is for another update.
Debugging
TBD: VM and debugger setup
TBD: Test application
Dev experience
TBD: Add a build script that
generates a GUID on each build
generate Rust source to be used for CLSID_CP_DEMO
generate register.reg and unregister.reg files with matching GUIDs
1 This one doesn't strictly implement functionality; its purpose initially was to act as a performance optimization, allowing the implementing module to get unloaded when it's no longer needed. A conforming implementation could simply return S_FALSE unconditionally, and opt out of this optimization, though for a credential provider it's probably wise to allow the system to unload it as soon as possible. Keeping an attack surface into high value information around for longer than necessary ain't no smart move.
2 The actual interface methods do not need to be exported; they are returned to clients by way of arrays of function pointers.
3 A full implementation will also require us to provide an implementation of ICredentialProviderCredential, as that is returned from ICredentialProvider::GetCredentialAt.

Unable to work with interface imported from another pkg, says method is missing but its there

I'm unable to work with an interface imported from another package and not sure what I should do. Example code:
var inner types.TxData // <-- interface imported from types package
inner = &InternalTx{
ChainId: (*big.Int)(dec.ChainID),
Data: *dec.Data,
}
Out of all the methods listed in the interface, 1 method is not accepted:
func (tx *InternalTx) accessList() types.AccessList { return nil }
Go complains that InternalTx does not implement accessList() to satisfy types.TxData interface, but if i capitalise accessList() to Accesslist() then I get another complaint stating that :
have AccessList() types.AccessList
want accessList() types.AccessList
So i'm very confused what I need to do here?
Edit:
I've implemented also the following based on recent suggestion:
type TxData struct {
types.TxData
}
var inner TxData
internalTx := &InternalTx{
ChainId: (*big.Int)(dec.ChainID),
Data: *dec.Data,
}
inner = TxData{TxData: internalTx}
Issue still persists.
If an interface is declared with an unexported method (such as accessList), then you cannot implement that interface in another package, because that name is not exported. This is a common way to force you to use implementations in the original package.
One way to deal with this is to embed that interface, and then delegate functionality to an implementation of it:
type TxData struct {
types.TxData
}
someImplementation:=// Get an implementation of types.TxData
inner:=TxData{TxData: someImplementation}
This will use the accessList of someImplementation.

How does the "range interface" of golang run internally?

package main
import "fmt"
type Phone interface {
call()
sales() int
}
type NokiaPhone struct {
price int
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
func (nokiaPhone NokiaPhone) sales() int {
return nokiaPhone.price
}
type IPhone struct {
price int
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func (iPhone IPhone) sales() int {
return iPhone.price
}
func main() {
var phones = [5]Phone{
NokiaPhone{price: 350},
IPhone{price: 5000},
IPhone{price: 3400},
NokiaPhone{price: 450},
IPhone{price: 5000},
}
var totalSales = 0
**for _, phone := *range phones* {
totalSales += phone.sales()
}**
fmt.Println(totalSales)
}
i don't know the "range phones" run internally.i only understand that 'phone' is run in '[5]phone' order and i want to konw that golang how to recognizes iphone or NokiaPhone.
Thanks , forgive me for my lame English.
An interface is a declaration of a contract, that is of the behaviour that implementing instances must support. By providing methods that are defined on the interface, a struct in Go automatically implements that interface and can be used in place of that interface wherever an interface based declaration is present. This is slightly different from many other languages such as Java or C++ where an instance must explicitly declare interfaces it implements and classes it extends. At runtime the code will get a concrete instance and execute a method of that instance. For the coder or for the declaration it does not matter, what matters is that instance will have a method as defined on the interface and that method can be called so the code can be written in generic interface terms.
This idea to accept interfaces and provide structs/instances is central for the inversion of control and dependency injection.
This is exactly what happens in you example. NokiaPhone and IPhone implement the Phone interface by providing methods declared on the interface Phone. When it comes to the loop, at each iteration a concrete instance is taken out of array, for that instance type the method asked for in the code is looked up, so the implementation now, and is executed. It works in the same way in Go, Java, C++ (with virtual methods) and many other languages that support either inheritance or interface/implementation pattern as Go does.

Composition combining data and functions with interfaces and structs

I'm wondering if this is something that's done in Go or if I'm thinking about it all wrong: composing type x interface and type x struct so my interface methods have access to specific data too:
The C programmer in my wants to do this:
type PluginHandler interface {
onLoad()
pm *PluginManager
}
func (ph PluginHandler) onLoad() {
pm.DoSomething()
}
There I have an interface defined with a function, but also some data I want to pass to those functions but this is a syntax error.
So is this something that's doable in Go through some other method or am I just thinking about the problem wrong?
You have defined onLoad incorrectly. You cannot define a function directly on interface type.
Once you have an interface, you need another type to implement methods specified in the interface. For example, if another type implements onLoad method, they automatically (implicitly) implement the interface PluginHandler.
The other thing you need to do is change the interface function type to accept the required data:
type PluginHandler interface {
onLoad(*PluginManager)
}
struct SomeType {
// ...
}
func (s SomeType) onLoad(pm *PluginManager) { // SomeType now implements
pm.DoSomething() // PluginHandler interface.
}
This way, you get to inject whichever PluginManager required by PluginHandler.
Also, you can use SomeType as a PluginHandler type whereever required.
func someFuntion(ph PluginHandler) {
// ...
ph.onLoad(pm)
// ...
}
Can be called with an input argument of type SomeType:
s := SomeType{}
someFunction(s)
TL;DR; There is no direct translation to Go.
Long answer:
Go interfaces are only methods.
Go structs are only data (with the possibility of receiver methods).
You can reference, and even embed interfaces within structs:
type Frobnicator interface {
Frobnicate() error
}
type Widget struct {
Frobnicator
WidgetName string
}
But that's not really what you're talking about.
The best answer to your dilema is, I believe: Take a step back. You're focusing on the trees, and you need to look at the forest. Go takes a different approach than C, or classical OO languages like C++ and Java.
Look at the general problem to be solved, and find solutions to that in Go. This can be a painful process (I can say from experience), but it's really the only way to learn the new way of thinking.
Just for the record, you can add extra methods to an existing type, by introducing another (indirection) type as:
type HandlerManager PluginManager
func (x *HandlerManager) onLoad() {
((*PluginManager)(x)).DoSomething()
}
And if you need to go with a more generic solution, a combination of Adapter & Strategy patterns could do:
type PluginHandlerAdapter struct{ _onLoad func() }
func (x *PluginHandlerAdapter) onLoad() {
x._onLoad()
}
Used like (public/private access ignored):
type PluginManager struct {
PluginHandlerAdapter
}
func NewPluginManager() *PluginManager {
res := new(PluginManager)
res._onLoad = res.DoSomething
return res
}

Retrieve DISPID of outgoing dispinterface member

I'm stuck on this one.
Given three variables:
an IDispatch* to a connectable object
the IID (DIID) of an outgoing dispinterface on that object
the name of a member defined by the dispinterface
How can resolve the name to a DISPID?
pDispatch->GetIDsOfNames(...) returns DISP_E_UNKNOWNNAME, as I would expect (outgoing interfaces aren't implemented by the connectable object)
I need to support scenarios where 0 clients have yet connected to the outgoing interface, so I can't enumerate the existing connection points in order to call GetIDsOfNames on one of them (I'm not even sure this would work)
In order to perform manual reflection, I would need the dispinterface's ITypeInfo. I could get this from the coclass's ITypeInfo. However pDispatch->GetTypeInfo(0, ...) returns the ITypeInfo for the IDispatch implementation (as per the documentation), not the ITypeInfo for the coclass. (And there are no other ITypeInfos exposed by this object's IDispatch implementation.)
If the object is "quite standard", then it should be possible.
From the object/IDispatch interface, you should be able to get to the TLB (type library). From the type library, you should be able to browse all coclasses, and get interfaces that these coclasses implement. You need to get to interface you have the IID for, browse the member and get the one you're interested.
There are many cases where this just won't work. Here is a console sample I've put up that works with a shell object. I've written it in C# because it's easier, but there's nothing you can't do with a decent language. I've used an old TLBINF32.DLL com utility library (only x86 unfortunately) I talk about in my answer to this question here on SO: How to read COM TypeLib with C# or C++?
static void Main(string[] args)
{
// create a sample object every one has
object o = Activator.CreateInstance(Type.GetTypeFromProgID("shell.application")); // for example
TLIApplication app = new TLIApplication();
// not sure, but I believe in pure COM it's calling IDispatch::GetTypeInfo & ITypeInfo::GetContainingTypeLib
TypeLibInfo tli = app.InterfaceInfoFromObject(o).Parent;
// this is the guid for DShellFolderViewEvents
int dispid = GetDispId(tli, new Guid("{62112AA2-EBE4-11CF-A5FB-0020AFE7292D}"), "SelectionChanged");
Console.WriteLine("dispid:" + dispid); // should display 200
}
public static int GetDispId(TypeLibInfo tlb, Guid diid, string memberName)
{
// browse all coclasses
// in pure COM this is ITypeLib::GetTypeInfo
foreach (CoClassInfo ti in tlb.CoClasses)
{
// browse all interfaces in those coclasses
// in pure COM this is ITypeInfo::GetRefTypeOfImplType
foreach (InterfaceInfo itf in ti.Interfaces)
{
// only select [source] interfaces (events)
// this test is optional since the diid is unique
// in pure COM this is ITypeInfo::GetImplTypeFlags
if (((ImplTypeFlags)itf.AttributeMask & ImplTypeFlags.IMPLTYPEFLAG_FSOURCE) != ImplTypeFlags.IMPLTYPEFLAG_FSOURCE)
continue;
if (new Guid(itf.GUID) == diid)
{
// in pure COM this is ITypeInfo::GetTypeAttr & ITypeInfo::GetFuncDesc
foreach (MemberInfo mi in itf.Members)
{
if (mi.Name == memberName)
return mi.MemberId;
}
}
}
}
return -1;
}

Resources