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.
Related
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",
]
I am trying to learn to how generics act in Compile and RunTime stages comparing with Protocols.
I know how to protocols react with existential types. Protocols are existential types so in compile stages step by step they taking the the Car type
struct Car: Drivable {
let numberOfWheels = 4
func drive() { }
}
car =
payload_data_0 = 0x0000000000000004,
payload_data_1 = 0x0000000000000000,
payload_data_2 = 0x0000000000000000,
instance_type = 0x000000010b50e410
ExistentialContainers`type metadata for
ExistentialContainers.Car,
protocol_witness_0 = 0x000000010b50e1c8
ExistentialContainers`protocol witness table for
ExistentialContainers.Car: ExistentialContainers.Drivable
in ExistentialContainers)
What about generics ? I really wonder how act in compile and runtime stages. They use pointers right ? when at Compile time ? In runtime everything is ready for Generics.
Also this example seems same but one is compile other is not..
protocol Returnable {
associateType ReturnType
}
//This will compile
func returnTheType<T: Returnable>(object: T) -> T.ReturnType { } ✅
//This won't compile
func returnTheType(object: Returnable) -> object.ReturnType { } ❌
func startTravelingWithCar(transportation: Car) { }
I suppose you have a function like this so,
Behind the scenes the function also receives the car’s PWT and VWT, giving the function the necessary information to be able to set up a value buffer if necessary and determine the car object’s protocol specific function implementation of drive(). This newly generated function is now type specific, giving you access to any associated types of the Car object and all of this type information is determined at compile time — which is part of the reason why we can have an associated type be the return type of a generic function, but can’t do the same for protocol based functions.
I am still learning the Go way of doing things, coming from a C++ background. I am looking for feedback contrasting OOP inheritance to interface composition.
I have a design situation in a Go program where, if I was implementing in C++, I would solve with an abstract base class.
Suppose I need a base class, which has many implementors. The base class has shared methods that do work on abstract data items. Different Worker implementations provide CRUD operations on different item types, but workers all use the shared methods of the base class for general work.
In C++ I might do it this way
class IItem
{
// virtual methods
};
class IWorker
{
public:
// one of many virtual functions that deal with IItem CRUD
virtual IItem* createItem() = 0;
// concrete method that works on interfaces
void doWork()
{
IItem* item = createItem();
// do stuff with an IItem*
}
};
class Toy : public IItem
{
};
// one of many kinds of workers
class ElfWorker : public IWorker
{
public:
ElfWorker()
{
// constructor implicitly calls IWorker()
}
IItem* createItem() override
{
return new Toy;
}
};
In Go you don't have abstract virtual methods such as IWorker::createItem(). Concrete classes need to supply the base with an interface or function that do the right thing.
So I think it is the case that the Go code the ew.ItemCRUD interface has to be explicitly set with a pointer to an ElfWorker.
The elf knows how to createItem(), which in his case happens to be Toy object. Other workers would implement their own ItemCRUD for their data objects.
type Item interface {
// various methods
}
type ItemCRUD interface {
create() Item
// other CRUD
}
type Worker struct {
ItemCRUD // embedded interface
}
func (w *Worker) doWork() {
item := w.create()
// do stuff with item
}
type Toy struct {
}
type ElfWorker struct {
Worker // embedded
// ..
}
func NewElfWorker() *ElfWorker {
ew := &ElfWorker{}
ew.ItemCRUD = ew // <-- #### set Worker ItemCRUD explicitly ####
return ew
}
func (ew *ElfWorker) createItem() Item {
return &Toy{}
}
// more ElfWorker Item CRUD
func bigFunction(w *Worker) {
// ...
w.doWork()
// ..
}
So the part that I am wrestling a bit with is explicit setting. Seems like the "Go way" of composition does require this explicit step if I want the base Worker class to provide shared methods on Items.
Thoughts?
Beeing new to go myself, this answer is not backed by years of go experience :-)
I don't know, if the way you tackle this is the correct approach.
go allows interfaces to be implemented without explicit declaration. If you have elves, and you need them to do ItemCRUD methods, just implement them.
The method set will then match the interface and you can use the elf as a ItemCRUD where required.
To supply any elf object with a default ItemCRUD Implementation, you should implement an adapter for the ItemCRUD and compose the adapter with your abstract elf. The abstract methods could have a default implementation as log.Fatal("not implemented")
The concrete elves shadow the adapter's methods, this answers your question: It is not required to insert the object during creation.
Yet, since go has no generics, it may not be the right approach to have an ItemCRUD.
Im not entirely clear what the plan is with the above code and without understanding that its hard to suggest specific solutions, what is clear is you are very much coming to the party with an established OOP mindset (I did too) which is rarely helpful in finding the best solution in golang.
In Golang I wouldnt usually embed an interface in an implementation, interfaces are satisfied implicitly in Golang which allows for a nice separation of expectation and implementation which should generally be respected.
A reciever method should expect an interface, the implementation passed at runtime should just satisfy the signature of that interface implicitly.
So perhaps my doWork method needs to be able to createItems then it would be the doWork method that would accept any implementation of ItemCRUD which it could call to create an item. But this is me guessing at what you really want to do here, I suspect if you just separate implementation from interface you will probably answer your own question.
How is this...
impl String {
fn foo(&self) {
//...
}
}
...any different to this?
fn foo(s: &String) {
//...
}
Then again, it is possible to extend the type implementation if you define a trait in your crate. Why?
There are several different arguments as indicated by the following source as to why one is unable to implement existing types that are outside of one's crate.
Local impl can be broken by future implementations. For example, consider "you've locally defined Add on Vec<T> as a concat operator, ... , and then ... after years of debate ... some mathy operation [is] to be performed instead. If you delete your impl and upgrade, your code will be ... broken2."
The readability of the code will also be affected by this change, that is, it could make the "value of that reading far more transient3."
There is also a security concern. Consider the following scenario that would be technically possible if this were allowed, that is, "an attacker [could] find a change in an impl in [some] library, a call site in an application they wish to backdoor, and send a "refactoring" pull request that "accidentally" replaces the new impl with the old impl so as to create a vulnerability, but their pull can reference the old code from the library. And they can embed the hostile impl into a macro in yet another create4."
Assuming the case that the local impl would be the preferred implementation if local impls were allowed. This would "would violate the coherence property [that is currently being maintained]5." This point can be further clarified through what is called the 'HashTable' problem5.
mod foo {
impl Hash for i32 { ... }
fn f(mut table: HashMap<i32, &'static str>) {
table.insert(0, "hello");
::bar::f(&table);
}
}
mod bar {
impl Hash for i32 { ... }
fn f(table: &HashMap<i32, &'static str>) {
assert_eq!(table.get(0), Some("hello"));
}
}
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;
}