|
-
Dec 4th, 2014, 07:33 PM
#1
[RESOLVED] COM Pointers Please (Pun Intended)
Good day. I know there are some really competent COM (Component Object Model) people out there and in a personal quest for better understanding of COM and VB6, I have a question, but be forewarned that I am using class/interface interchangeably, right or wrong.
Let me use the stdFont as an example. From some testing, the stdFont seems to look like the following:
Class that implements 3 other classes:
Font aliased as IFontDisp
FontEvents aliased as IFontEventsDisp
IFont (VB's Object Browser doesn't show it as implemented by stdFont)
1) ObjPtr(Me.Font) is provided for testing, but just the pointer, no knowledge of what it is pointing to.
2) If I call IUnknown.QueryInterface for IDispatch implementation, I can get some really good info via that interface
From that info, I have determined that the class that IDispatch is for, is the Font/IFontDisp class. Seems to be verified when calling: TypeName(Me.Font). Great, but for educational purposes, this is my question:
How can I determine what class, if any, is implementing that specific instance of Font/IFontDisp? If I get the ITypeLib from IDispatch>ITypeInfo, I can enumerate the library & see that the stdFont implements Font/IFontDisp among 2 other classes, but I'd like to navigate up the class to end at the source, what I assume will be the stdfFont class, not the Font/IFontDisp
Again, this is for self-education.
Edited: After re-reading this, I realize didn't phrase my question correctly. ObjPtr(Me.Font) & ObjPtr(IFontDisp), return the same pointer, as expected. My question is really: how do I know that the ObjPtr() provided is for a sole-instance of IFontDisp class or the IFontDisp class being implemented by another class & if so, what other class(es)?
Last edited by LaVolpe; Dec 4th, 2014 at 07:54 PM.
-
Dec 5th, 2014, 01:39 AM
#2
Re: COM Pointers Please (Pun Intended)
I haven't been a C++ programmer in almost 20 years, and most of that was on older versions of BSD Unix. I did a little ATL programming back in the late 1990s but never did spend the time to unravel COM at that level, so most of my experience is from a VB6 programmer point of view. Basically I'm in the same boat as you or worse.
First thing: You can see IFont in the Object Browser but it is marked hidden so you need to Show Hidden Members there.
Second thing, see Dual Interfaces and ATL for some general info that may be useful. IDispatch is basically what you use in late binding.
Using early binding and late binding in Automation may help refresh your memory about some of the terminology from a VB perspective.
INFO: Visual Basic Object Browser Cannot See the Default Interface talks about a limitation in VB-created classes that may have an impact on what you are trying to do.
The StdFont class is a class. It implements 3 other interfaces (classes don't implement classes).
No idea whether any of this is helpful. Good luck.
-
Dec 5th, 2014, 08:10 AM
#3
Re: COM Pointers Please (Pun Intended)
Thanx for the links. Will try to decipher them later especially that last one.
Regarding IFont, I do know it is hidden. But when you look at the stdFont in VB's Object Browser, you can deduce that it is implementing the 8 properties of IFontDisp and the 1 method of IFontEventsDisp. Parsing the the ITypeLib interface, it definitively shows stdFont implements those two, but also implements IFont, though the object browser doesn't seem to show that (maybe VB is intentionally, hiding it from us, or stdFont doesn't always implement that interface?).
Just for grins:
Dim IIFont As IFont
Set IIFont = Me.Font
Msgbox ObjPtr(Me.Font) & vbNewLine & ObjPtr(IIFont) ' returns different pointers
Last edited by LaVolpe; Dec 5th, 2014 at 08:28 AM.
-
Dec 5th, 2014, 09:13 AM
#4
Frenzied Member
Re: COM Pointers Please (Pun Intended)
I don't understand at all, But I search Key words "IFontDisp" in .NET reflector:
Code:
using System;
using System.Runtime.InteropServices;
[Guid("BEF6E003-A874-101A-8BBA-00AA00300CAB"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComImport]
public interface IFontDisp
{
string Name
{
get;
set;
}
long Size
{
get;
set;
}
bool Bold
{
get;
set;
}
bool Italic
{
get;
set;
}
bool Underline
{
get;
set;
}
bool Strikethrough
{
get;
set;
}
short Weight
{
get;
set;
}
short Charset
{
get;
set;
}
}
Code:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[Guid("BEF6E002-A874-101A-8BBA-00AA00300CAB"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
public interface IFont
{
[return: MarshalAs(UnmanagedType.BStr)]
string GetName();
void SetName([MarshalAs(UnmanagedType.BStr)] [In] string pname);
[return: MarshalAs(UnmanagedType.U8)]
long GetSize();
void SetSize([MarshalAs(UnmanagedType.U8)] [In] long psize);
[return: MarshalAs(UnmanagedType.Bool)]
bool GetBold();
void SetBold([MarshalAs(UnmanagedType.Bool)] [In] bool pbold);
[return: MarshalAs(UnmanagedType.Bool)]
bool GetItalic();
void SetItalic([MarshalAs(UnmanagedType.Bool)] [In] bool pitalic);
[return: MarshalAs(UnmanagedType.Bool)]
bool GetUnderline();
void SetUnderline([MarshalAs(UnmanagedType.Bool)] [In] bool punderline);
[return: MarshalAs(UnmanagedType.Bool)]
bool GetStrikethrough();
void SetStrikethrough([MarshalAs(UnmanagedType.Bool)] [In] bool pstrikethrough);
[return: MarshalAs(UnmanagedType.I2)]
short GetWeight();
void SetWeight([MarshalAs(UnmanagedType.I2)] [In] short pweight);
[return: MarshalAs(UnmanagedType.I2)]
short GetCharset();
void SetCharset([MarshalAs(UnmanagedType.I2)] [In] short pcharset);
IntPtr GetHFont();
void Clone(out UnsafeNativeMethods.IFont ppfont);
[PreserveSig]
int IsEqual([MarshalAs(UnmanagedType.Interface)] [In] UnsafeNativeMethods.IFont pfontOther);
void SetRatio(int cyLogical, int cyHimetric);
void QueryTextMetrics(out IntPtr ptm);
void AddRefHfont(IntPtr hFont);
void ReleaseHfont(IntPtr hFont);
void SetHdc(IntPtr hdc);
}
Code:
// System.Windows.Forms.AxHost
/// <summary>Returns an OLE IFont object created from the specified <see cref="T:System.Drawing.Font" /> object.</summary>
/// <returns>The IFontIFont object created from the specified font, or null if <paramref name="font" /> is null or the IFont could not be created.</returns>
/// <param name="font">The font to create an IFont object from.</param>
/// <exception cref="T:System.ArgumentException">The <see cref="P:System.Drawing.Font.Unit" /> property value is not <see cref="F:System.Drawing.GraphicsUnit.Point" />.</exception>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected static object GetIFontFromFont(Font font)
{
if (font == null)
{
return null;
}
if (font.Unit != GraphicsUnit.Point)
{
throw new ArgumentException(SR.GetString("AXFontUnitNotPoint"), "font");
}
object result;
try
{
result = UnsafeNativeMethods.OleCreateIFontIndirect(AxHost.GetFONTDESCFromFont(font), ref AxHost.ifont_Guid);
}
catch
{
result = null;
}
return result;
}
Code:
// System.Windows.Forms.AxHost
/// <summary>Returns a <see cref="T:System.Drawing.Font" /> created from the specified OLE IFontDisp object.</summary>
/// <returns>The <see cref="T:System.Drawing.Font" /> created from the specified IFontDisp, <see cref="P:System.Windows.Forms.Control.DefaultFont" /> if the font could not be created, or null if <paramref name="font" /> is null.</returns>
/// <param name="font">The IFontDisp to create a <see cref="T:System.Drawing.Font" /> from.</param>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected static Font GetFontFromIFontDisp(object font)
{
if (font == null)
{
return null;
}
UnsafeNativeMethods.IFont font2 = font as UnsafeNativeMethods.IFont;
if (font2 != null)
{
return AxHost.GetFontFromIFont(font2);
}
SafeNativeMethods.IFontDisp fontDisp = (SafeNativeMethods.IFontDisp)font;
FontStyle fontStyle = FontStyle.Regular;
Font result;
try
{
if (fontDisp.Bold)
{
fontStyle |= FontStyle.Bold;
}
if (fontDisp.Italic)
{
fontStyle |= FontStyle.Italic;
}
if (fontDisp.Underline)
{
fontStyle |= FontStyle.Underline;
}
if (fontDisp.Strikethrough)
{
fontStyle |= FontStyle.Strikeout;
}
if (fontDisp.Weight >= 700)
{
fontStyle |= FontStyle.Bold;
}
Font font3 = new Font(fontDisp.Name, (float)fontDisp.Size / 10000f, fontStyle, GraphicsUnit.Point, (byte)fontDisp.Charset);
result = font3;
}
catch (Exception)
{
result = Control.DefaultFont;
}
return result;
}
Code:
// System.Windows.Forms.ControlPaint
internal static bool FontToIFont(Font source, UnsafeNativeMethods.IFont target)
{
bool result = false;
string name = target.GetName();
if (!source.Name.Equals(name))
{
target.SetName(source.Name);
result = true;
}
float num = (float)target.GetSize() / 10000f;
float sizeInPoints = source.SizeInPoints;
if (sizeInPoints != num)
{
target.SetSize((long)(sizeInPoints * 10000f));
result = true;
}
NativeMethods.LOGFONT lOGFONT = new NativeMethods.LOGFONT();
IntSecurity.ObjectFromWin32Handle.Assert();
try
{
source.ToLogFont(lOGFONT);
}
finally
{
CodeAccessPermission.RevertAssert();
}
short weight = target.GetWeight();
if ((int)weight != lOGFONT.lfWeight)
{
target.SetWeight((short)lOGFONT.lfWeight);
result = true;
}
bool bold = target.GetBold();
if (bold != lOGFONT.lfWeight >= 700)
{
target.SetBold(lOGFONT.lfWeight >= 700);
result = true;
}
bool italic = target.GetItalic();
if (italic != (0 != lOGFONT.lfItalic))
{
target.SetItalic(0 != lOGFONT.lfItalic);
result = true;
}
bool underline = target.GetUnderline();
if (underline != (0 != lOGFONT.lfUnderline))
{
target.SetUnderline(0 != lOGFONT.lfUnderline);
result = true;
}
bool strikethrough = target.GetStrikethrough();
if (strikethrough != (0 != lOGFONT.lfStrikeOut))
{
target.SetStrikethrough(0 != lOGFONT.lfStrikeOut);
result = true;
}
short charset = target.GetCharset();
if (charset != (short)lOGFONT.lfCharSet)
{
target.SetCharset((short)lOGFONT.lfCharSet);
result = true;
}
return result;
}
OleCreateFontIndirect API ( "oleaut32.dll")
Last edited by Jonney; Dec 5th, 2014 at 09:20 AM.
-
Dec 5th, 2014, 09:50 AM
#5
Re: COM Pointers Please (Pun Intended)
@Dilettante. Kinda understand why VB's object browser doesn't show IFont methods/properties within the stdFont definition.
stdFont:
Interface: IFont
dispinterface: Font/IFontDisp
dispinterface: FontEvents/IFontEventsDisp
Thinking that the object browser only displays implemented dispinterface classes/types.
Edited: follow up. IFont is not tagged as Dispatchable. That is probably the sole reason
@Jonney. Found very similar info also. Thank you
Getting my head around this. But still unsure if there is a way to determine from just a passed pointer, whether the class, from that pointer, is instantiated on its own or is implemented from a larger class/object.
Last edited by LaVolpe; Dec 5th, 2014 at 10:07 AM.
-
Dec 5th, 2014, 11:30 AM
#6
Re: COM Pointers Please (Pun Intended)
 Originally Posted by LaVolpe
Getting my head around this. But still unsure if there is a way to determine from just a passed pointer, whether the class, from that pointer, is instantiated on its own or is implemented from a larger class/object.
I'm not sure that you can. The only way might be to find some way to enumerate all implemented interfaces of an object.
-
Dec 5th, 2014, 11:44 AM
#7
Re: COM Pointers Please (Pun Intended)
 Originally Posted by dilettante
I'm not sure that you can. The only way might be to find some way to enumerate all implemented interfaces of an object.
That's the Catch22. I have a pointer to a class/interface. I can determine if that class is implemented by other classes. But I can't get the object which is what I was hoping to do. I'd think it is an offset from the pointer/vTable, but not comfortable enough to begin down that road yet
None of this is critical. I've already pretty much replicated VB's object browser using only COM interfaces, no APIs other than DispCallFunc. The idea I was playing with was attempting to 'rebuild' an object based off a pointer, not knowing what object the pointer was for. Just like puzzles & may very well lose interest soon anyway
-
Dec 5th, 2014, 08:55 PM
#8
Re: COM Pointers Please (Pun Intended)
-
Dec 6th, 2014, 10:16 AM
#9
Re: COM Pointers Please (Pun Intended)
Think that link may have did it, indirectly, but did it.
After reading that & following the links within it, I believe I found the answer to my question. Among the text, was a really simple statement: a COM object really has just 1 IUnknown. So, the IUnknown pointer returned by ObjPtr(), in this case was to the IFontDisp instance and not the true IUnknown. To determine if this instance is part of a larger class, we simply need to determine if the IUnknown is at the same memory address as what ObjPtr() gave us. But how? I read over those link topics regarding adjustor thunks & guesstimates, but a light turned on within & a far easier solution came to mind....
1) ObjPtr() gives us an address to an interface that is dispatchable (IFontDisp in this case)
2) To determine the top of the chain, i.e., the underlying COM object, we simply ask the IUnknown if the class implements IUnknown. It will of course
3) Compare the IUnknown address returned by QueryInterface when querying for IUnknown. If they are the same, you're at the top of the class, if not, rinse & repeat
Looks very promising. Thanx
Follow up: What I described above isn't a solution. However, it is interesting. The value returned from QueryInterface(IID_IUnknown) appears to be the 1st VTable entry of the stdFont co-class/object. I may be resigned to not being able to identify the co-class from the VTable address, but not giving up yet. Why I suggest query returns the 1st VTable is described below:
Dim IIFont As IFont, IIFontDisp As IFontDisp, IIUnk As IUnknown
Set IIFont = Me.Font: Set IIFontDisp = Me.Font: Set IIUnk = Me.Font
(ObjPtr(IIFontDisp) = ObjPtr(Me.Font)) = True
(ObjPtr(IIUnk) = ObjPtr(IIFont)) = True
(ObjPtr(IIUnk) = ObjPtr(Me.Font)-4) = True
Also: ObjPtr(IIUnk) = QueryInterface(IID_IUnknown)
FYI: Other resources to try and wrap one's head around:
The COM Programmer's Cookbook on msdn
Inside COM+ (e-book)
Last edited by LaVolpe; Dec 6th, 2014 at 02:34 PM.
-
Dec 6th, 2014, 02:25 PM
#10
Re: COM Pointers Please (Pun Intended)
The BaseInterface of an Instance is the one which shares the same VTable-Ptr as
an "explicitely IUnknown-casted Reference" (in almost all cases).
When I run this code here:
Code:
Option Explicit
Private Sub Form_Load()
Dim oStdFont As stdole.StdFont: Set oStdFont = Me.Font 'cast to StdFont per QueryInterface
Debug.Print "oStdFont", ObjPtr(oStdFont), TypeName(oStdFont)
Dim oFontDisp As IFontDisp: Set oFontDisp = Me.Font 'cast to IFontDisp per QueryInterface
Debug.Print "oFontDisp", ObjPtr(oFontDisp), TypeName(oFontDisp)
Dim oDispatch As Object: Set oDispatch = Me.Font 'cast to IDispatch per QueryInterface
Debug.Print "oDispatch", ObjPtr(oDispatch), TypeName(oDispatch)
Dim oFont As IFont: Set oFont = Me.Font 'cast to IFont per QueryInterface
Debug.Print "oFont", ObjPtr(oFont), TypeName(oFont)
Dim oUnknown As stdole.IUnknown: Set oUnknown = Me.Font 'cast to IUnknown per QueryInterface
Debug.Print "oUnknown", ObjPtr(oUnknown), TypeName(oUnknown)
End Sub
I'll get the Output:
Code:
oStdFont 11333756 Font
oFontDisp 11333756 Font
oDispatch 11333756 Font
oFont 11333752 Font
oUnknown 11333752 Font
So, IFont is the Root- (or Base-) Interface behind an instance which VB per default
hands out as a "StdFont"-Type.
I assume that this much already was clear - though not sure - or what the real goal is in the end.
Olaf
-
Dec 6th, 2014, 03:05 PM
#11
Re: COM Pointers Please (Pun Intended)
I think TypeName is misleading. TypeName(oFont) shouldn't return Font in my opinion. Font is aliased with IFontDisp. This can be seen in the IDL/TypeLib. Font/IFontDisp has 8 members. IFont has 9 members + 5 methods. I suspect that if some VB class implemented 5 interfaces, all 5 would return the same TypeName.
The real goal was simply to try to identify what class, if any, is implementing the specific instance of ObjPtr(someObject), knowing that the pointer may not be implemented by anything, just a stand-alone class.
Last edited by LaVolpe; Dec 6th, 2014 at 03:27 PM.
-
Dec 6th, 2014, 06:41 PM
#12
Re: COM Pointers Please (Pun Intended)
@LaVolpe: TypeName uses IDispatch::GetTypeInfo to get a ITypeInfo interface and then probably GetContainingTypeLib to retrieve coclass name. If this fails it returns interface name. TypeName completely fails if IDispatch is not implemented by the referenced object (prints Unknown which is like "this is a plain IUnknown, nothing else can be deduced"). A rehash of your thoughts.
Anyway, COM says that IUnknown is "identity" interface. So if you can compare two objects for equality by QI for IUnknown and compare longs. It does say something that QI for other interfaces should not fail depending on the source interface i.e. on your Font object if IUnknown.QI for IFontDisp succeeds then IDispatch.QI for IFontDisp should be discoverable too. What is not guaranteed is the ObjPtr on these IFontDisp will be equal!
VB6 uses this hack when implementing interfaces: Class1 implements Class2 so
Code:
Dim a As Class1
Dim b As Class2
Set a = New Class1
Set b = a
at this point ObjPtr(a) <> ObjPtr(b)
Code:
Dim pDispA As Object, pDispB as Object
Set pDispA = a
Set pDispB = b
at this point ObjPtr(pDispA) <> ObjPtr(pDispB) which is not expected (but is very convenient for VBScript clients)
Code:
Dim pUnkA As IUnknown, pUnkB as IUnknown
Set pUnkA = a
Set pUnkB = b
at this point ObjPtr(pUnkA) = ObjPtr(pUnkB) which is the "identity" rule.
Conclusion: you can have several IDispatch (or IWhatever) implementations on a single coclass, with separate vtbls and different ObjPtr's. Only IUnknown has a "canonical" ObjPtr although it is present as base interface in all vtbls for the object.
Regarding object browser in the IDE. The explanation is simple. A coclass in a typelib can have a single default interface and a single default source interface. From these two all the methods+events are "assigned" to the coclass in VB's object browser. In stdole2.tlb you have
Code:
coclass StdFont {
[default] dispinterface Font;
[default, source] dispinterface FontEvents;
interface IFont;
};
so no methods from IFont are "assigned" to StdFont. There is no requirement default interface being dual or dispatch only. On the other hand VB6 support only dispatch only source interfaces for events.
Regarding "how do I know that the ObjPtr() provided is for a sole-instance of IFontDisp class or the IFontDisp class being implemented by another class & if so, what other class(es)"... you can't. The idea is for the real instance to be indistinguishable from whatever ad-hoc proxy COM gives back to the client for inter-thread/process marshalling a.k.a call interception has been a major design goal.
cheers,
</wqw>
-
Dec 6th, 2014, 07:26 PM
#13
Re: COM Pointers Please (Pun Intended)
 Originally Posted by wqweto
... The idea is for the real instance to be indistinguishable from whatever ad-hoc proxy COM gives back to the client for inter-thread/process marshalling a.k.a call interception has been a major design goal.
This is what I was gathering from the research and you have validated it. In summary, it is really unnecessary for an implemented interface to know anything about the class that's implementing it. Thank you; therefore, marked as resolved.
In the process of playing, at least, I learned quite a bit about accessing these COM interfaces via low-level APIs and some of the guts of COM. That was a plus.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|