reflection, base class private properties, etc.

S

Sunny

Hi,
I have an old problem which I couldn't solve so far. Now I have found a post
in that group that gave me an idea, but I can not fully understand it.
The problem is: I'm trying to use a Windows.Forms.UserControl in a COM
environment, i.e. I want to host that control in a COM host. So far, so
good, I can host it, but I can not reach the parent COM object from the
control (Parent property is null :( ).
I have stopped the control in the debugger and I found in the watch window
some properties, like ActiveXInstance
{System.Windows.Forms.Control.ActiveXImpl}
The problem is that they are private to the base class, so I can not access
them.

Now the question: is it possible somehow to access these base properties
programmatically. I have found a post which mentions something about
reflection (whatever that mean). Can I use a reflection to access these
properties?

Any help will be highly appreciated.

Thanks
Sunny
 
I

Ignacio Machin

Hi Sunny,


I think that yes, it's possible to access the private properties of a class
using reflection, I haven't tested it but I also saw the post of Nicholas
and until now all that he said is true ;) , the thing is ( also stated in
the post ) what are you planning to do with it? it;s a private property and
you have no idea of how it's used or what it means.

Cheers,
 
S

Sunny

Hi, it seems that I have to guess what property to get, so I'll play with
all of them, but that happen always with combination COM/.Net/Outlook :)
The problem is, that I have read some of docs for reflection, and I still
have no idea where to start and how to access these private members :(
So any example will be helpful.

Sunny
 
V

Vladimir Kouznetsov

Here is a sample code (in VB but it shouldn't not be difficult to translate
it) I used to get UserMode property:

Dim strAssembly As String
strAssembly =
Type.GetType("System.Object").Assembly.CodeBase.Replace("mscorlib.dll",
"System.Windows.Forms.dll")
strAssembly = strAssembly.Replace("file:///", String.Empty)
strAssembly = Reflection.AssemblyName.GetAssemblyName(strAssembly).FullName
Dim unmt As Type = _
Type.GetType(Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.UnsafeNativeMethods"))
Dim ioot As Type = unmt.GetNestedType("IOleObject")
Dim mi As Reflection.MethodInfo = ioot.GetMethod("GetClientSite")
Dim site As Object = mi.Invoke(Me, Nothing)
If site Is Nothing Then Return -1

' We are hosted by a COM container

Dim dsite As IDispatch = site

' All the uninitialized arguments work fine in this case
Dim id As Guid
Dim params As DISPPARAMS
Dim lcid As System.UInt32
Dim wFlags As System.UInt16 = System.UInt16.Parse(2.ToString())
Dim result As Object
dsite.Invoke(-709, id, lcid, wFlags, params, result, Nothing, Nothing)

If result Then
Return 1
Else
Return 0
End If

That should give you an idea how to do what you want. IIRC IOleClientSite
has method GetContainer() which I think you need (not the parent COM
object). BTW You can use ildasm on System.Windows.Forms.dll to make yourself
familiar with the internal classes and implementations.

thanks,
v
 
S

Sunny

Hi Vladimir,
IT WORKS IT WORKS. You are great. I have implemented this in C# and it gave
me the reference I needed. I suppose, that I had to implement all of your
code, but I stopped at the check for site is Nothing, because I can cast it
at the type I want. So, I do not know what the rest of the code have to do.
If you have a little time, pls, explain. Nevertheless so far, you are the
only guy around who have succeeded to solve that problem. I have posted it
allover, with different ideas, but ... nothing.

Thank you again
Sunny
 
V

Vladimir Kouznetsov

For a some reason my post didn't come through, so I repeat:

Hi Sunny,

I'm glad that was a help.
I was trying to solve a similar problem and the code was a part of the
solution. I don't think I can convert site to IOleClientSite because the
type is not accessible in compile time - it's defined in a private nested
class. Also for a some reason late binding didn't work for the object
(probably because it's a COM object). So I ComImport-ed IDispatch (couldn't
find anything predefined) and called Invoke() to get UserMode ambient
property. I found a (relatively) simpler way of detecting UserMode:

Dim strAssembly As String
strAssembly = Type.GetType("System.Object").Assembly.CodeBase
strAssembly = strAssembly.Replace("mscorlib.dll", _
"System.Windows.Forms.dll")
strAssembly = strAssembly.Replace("file:///", String.Empty)
strAssembly = Reflection.AssemblyName.GetAssemblyName(strAssembly).FullName
Dim qualifiedControlTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control")
Dim ct As Type = Type.GetType(qualifiedControlTypeName)
Dim bf As Reflection.BindingFlags = _
Reflection.BindingFlags.IgnoreCase Or _
Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Static Or _
Reflection.BindingFlags.Instance
Dim pi() As Reflection.PropertyInfo = ct.GetProperties(bf)
Dim axipi As Reflection.PropertyInfo = getMemberByName(pi, _
"ActiveXInstance")
Dim axi As Object = axipi.GetValue(Me, Nothing)
Dim site As Object = axi.GetClientSite()
If site Is Nothing Then Return -1

Dim qualifiedActiveXImplTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control+ActiveXImpl")
Dim axit As Type = Type.GetType(qualifiedActiveXImplTypeName)
pi = axit.GetProperties(bf)
Dim dmpi As Reflection.PropertyInfo = getMemberByName(pi, "DesignMode")
Dim ret As Boolean = dmpi.GetValue(axi, Nothing)
If ret Then Return 1
Return 0

getMemberByName() is function that goes through all array elements and finds
the one with the given name. Note that I had to use "Control+ActiveXImpl"
type name - GetNestedType() didn't work (any ideas why?). Note that late
binding (GetClientSite()) works fine now. Note that DesignMode property
actually stores UserMode so it's false in design time.

For a some reason .Net control's properties don't persist in COM containers.
I'm trying now to figure out why. If you have any thought about that I'd
appreciate any input.

thanks,
v
 
S

Sunny

Hi Vladimir,
my problem was not so complicated, but what I learned so far is, that to
expose your .Net property, you have to implement/reimplement it in your
component, and most probably to set a corresponding DispID.
The container I targeted (Outlook PropertyPage) needed one Property (bool
Dirty) and 2 methods (void Apply() and void GetPageInfo(ref string HelpFile,
ref int HelpContext)) so I had to implement them. And there was one
undocumented property string Caption for which I had to supply DispID to be
recognized:

[DispId(-518)]
public string Caption
{
get
{return this.Name;}
}
So maybe you have to reimplement some of the properties you want to expose
with a corresponding DispID, expected by the host.

All this stuff is new for me, so I'm just guessing, but maybe it should
work. The COM host should use these DispIDs, else what for are they :)

I'm sure that if I remove the DispID part of the declaration of Caption
property, the host didn't find it. Also I just checked, that if you supply
the correct DispID (in my case -518, which I found somewhere that means
Caption) you can change the name of your property to whatever you want. I
have renamed that property to Capsss, and the host still founds it.

Maybe you have to find what DispID's host is expecting, and just to make a
new properties with that DispID, which supplies the necessary values.

Please, let me know what happened.

Sunny
 
V

Vladimir Kouznetsov

Hi Sunny,

Thanks for the feedback.

Caption is a standard OLE control property. There are not many mentions of
it in the MSDN documentation, especially together with it's numeric value.
I'm pretty sure though it described in OLE 2.0 Programmer's Reference. Also
you can inspect olectl.h - a C header file that comes with VC - to find more
standard properties definitions, they start with DISPID_.

I actually have three problems: (a) Word doesn't persist _my_ properties - I
have a few properties, I can set them from Word programmatically but after
the document has been closed and loaded again the properties' values are
lost; (b) Word doesn't show my object in the drop-down box where the
document object and all other objects are (in the Properties window), I can
select my object and open the Properties window but the drop-down box is
empty (it should show the object's name and type) and; (c) Word doesn't show
my properties in it.

thanks,
v

Sunny said:
Hi Vladimir,
my problem was not so complicated, but what I learned so far is, that to
expose your .Net property, you have to implement/reimplement it in your
component, and most probably to set a corresponding DispID.
The container I targeted (Outlook PropertyPage) needed one Property (bool
Dirty) and 2 methods (void Apply() and void GetPageInfo(ref string HelpFile,
ref int HelpContext)) so I had to implement them. And there was one
undocumented property string Caption for which I had to supply DispID to be
recognized:

[DispId(-518)]
public string Caption
{
get
{return this.Name;}
}
So maybe you have to reimplement some of the properties you want to expose
with a corresponding DispID, expected by the host.

All this stuff is new for me, so I'm just guessing, but maybe it should
work. The COM host should use these DispIDs, else what for are they :)

I'm sure that if I remove the DispID part of the declaration of Caption
property, the host didn't find it. Also I just checked, that if you supply
the correct DispID (in my case -518, which I found somewhere that means
Caption) you can change the name of your property to whatever you want. I
have renamed that property to Capsss, and the host still founds it.

Maybe you have to find what DispID's host is expecting, and just to make a
new properties with that DispID, which supplies the necessary values.

Please, let me know what happened.

Sunny



For a some reason my post didn't come through, so I repeat:

Hi Sunny,

I'm glad that was a help.
I was trying to solve a similar problem and the code was a part of the
solution. I don't think I can convert site to IOleClientSite because the
type is not accessible in compile time - it's defined in a private nested
class. Also for a some reason late binding didn't work for the object
(probably because it's a COM object). So I ComImport-ed IDispatch (couldn't
find anything predefined) and called Invoke() to get UserMode ambient
property. I found a (relatively) simpler way of detecting UserMode:

Dim strAssembly As String
strAssembly = Type.GetType("System.Object").Assembly.CodeBase
strAssembly = strAssembly.Replace("mscorlib.dll", _
"System.Windows.Forms.dll")
strAssembly = strAssembly.Replace("file:///", String.Empty)
strAssembly = Reflection.AssemblyName.GetAssemblyName(strAssembly).FullName
Dim qualifiedControlTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control")
Dim ct As Type = Type.GetType(qualifiedControlTypeName)
Dim bf As Reflection.BindingFlags = _
Reflection.BindingFlags.IgnoreCase Or _
Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Static Or _
Reflection.BindingFlags.Instance
Dim pi() As Reflection.PropertyInfo = ct.GetProperties(bf)
Dim axipi As Reflection.PropertyInfo = getMemberByName(pi, _
"ActiveXInstance")
Dim axi As Object = axipi.GetValue(Me, Nothing)
Dim site As Object = axi.GetClientSite()
If site Is Nothing Then Return -1

Dim qualifiedActiveXImplTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control+ActiveXImpl")
Dim axit As Type = Type.GetType(qualifiedActiveXImplTypeName)
pi = axit.GetProperties(bf)
Dim dmpi As Reflection.PropertyInfo = getMemberByName(pi, "DesignMode")
Dim ret As Boolean = dmpi.GetValue(axi, Nothing)
If ret Then Return 1
Return 0

getMemberByName() is function that goes through all array elements and finds
the one with the given name. Note that I had to use "Control+ActiveXImpl"
type name - GetNestedType() didn't work (any ideas why?). Note that late
binding (GetClientSite()) works fine now. Note that DesignMode property
actually stores UserMode so it's false in design time.

For a some reason .Net control's properties don't persist in COM containers.
I'm trying now to figure out why. If you have any thought about that I'd
appreciate any input.

thanks,
v

cast
it to
do.
posted
it COM/.Net/Outlook
members
properties
in
a So
far, can
not
 
S

Sunny

Hi Vladimir,
If I understand correctly, you hooking an object and properties to a word
document. I just guess, but maybe there are something like user properties
(in Outlook there are). And after applying them, you gave to invoke Save
method to save them for next opening. Also, for the object, maybe you have
to capture the OpenDocument event (or similar) and there to hook the object
again. And maybe you have to post a new thread in
..office.developer.com.add_ins, or if there is a specific newsgroup for word
addins. Maybe someone there will know more for the word object model.
Sorry that I can not help you more, but I haven't deal with word so far.

Sunny

Vladimir Kouznetsov said:
Hi Sunny,

Thanks for the feedback.

Caption is a standard OLE control property. There are not many mentions of
it in the MSDN documentation, especially together with it's numeric value.
I'm pretty sure though it described in OLE 2.0 Programmer's Reference. Also
you can inspect olectl.h - a C header file that comes with VC - to find more
standard properties definitions, they start with DISPID_.

I actually have three problems: (a) Word doesn't persist _my_ properties - I
have a few properties, I can set them from Word programmatically but after
the document has been closed and loaded again the properties' values are
lost; (b) Word doesn't show my object in the drop-down box where the
document object and all other objects are (in the Properties window), I can
select my object and open the Properties window but the drop-down box is
empty (it should show the object's name and type) and; (c) Word doesn't show
my properties in it.

thanks,
v

Sunny said:
Hi Vladimir,
my problem was not so complicated, but what I learned so far is, that to
expose your .Net property, you have to implement/reimplement it in your
component, and most probably to set a corresponding DispID.
The container I targeted (Outlook PropertyPage) needed one Property (bool
Dirty) and 2 methods (void Apply() and void GetPageInfo(ref string HelpFile,
ref int HelpContext)) so I had to implement them. And there was one
undocumented property string Caption for which I had to supply DispID to be
recognized:

[DispId(-518)]
public string Caption
{
get
{return this.Name;}
}
So maybe you have to reimplement some of the properties you want to expose
with a corresponding DispID, expected by the host.

All this stuff is new for me, so I'm just guessing, but maybe it should
work. The COM host should use these DispIDs, else what for are they :)

I'm sure that if I remove the DispID part of the declaration of Caption
property, the host didn't find it. Also I just checked, that if you supply
the correct DispID (in my case -518, which I found somewhere that means
Caption) you can change the name of your property to whatever you want. I
have renamed that property to Capsss, and the host still founds it.

Maybe you have to find what DispID's host is expecting, and just to make a
new properties with that DispID, which supplies the necessary values.

Please, let me know what happened.

Sunny



message
For a some reason my post didn't come through, so I repeat:

Hi Sunny,

I'm glad that was a help.
I was trying to solve a similar problem and the code was a part of the
solution. I don't think I can convert site to IOleClientSite because the
type is not accessible in compile time - it's defined in a private nested
class. Also for a some reason late binding didn't work for the object
(probably because it's a COM object). So I ComImport-ed IDispatch (couldn't
find anything predefined) and called Invoke() to get UserMode ambient
property. I found a (relatively) simpler way of detecting UserMode:

Dim strAssembly As String
strAssembly = Type.GetType("System.Object").Assembly.CodeBase
strAssembly = strAssembly.Replace("mscorlib.dll", _
"System.Windows.Forms.dll")
strAssembly = strAssembly.Replace("file:///", String.Empty)
strAssembly = Reflection.AssemblyName.GetAssemblyName(strAssembly).FullName
Dim qualifiedControlTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control")
Dim ct As Type = Type.GetType(qualifiedControlTypeName)
Dim bf As Reflection.BindingFlags = _
Reflection.BindingFlags.IgnoreCase Or _
Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Static Or _
Reflection.BindingFlags.Instance
Dim pi() As Reflection.PropertyInfo = ct.GetProperties(bf)
Dim axipi As Reflection.PropertyInfo = getMemberByName(pi, _
"ActiveXInstance")
Dim axi As Object = axipi.GetValue(Me, Nothing)
Dim site As Object = axi.GetClientSite()
If site Is Nothing Then Return -1

Dim qualifiedActiveXImplTypeName As String = _
Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.Control+ActiveXImpl")
Dim axit As Type = Type.GetType(qualifiedActiveXImplTypeName)
pi = axit.GetProperties(bf)
Dim dmpi As Reflection.PropertyInfo = getMemberByName(pi, "DesignMode")
Dim ret As Boolean = dmpi.GetValue(axi, Nothing)
If ret Then Return 1
Return 0

getMemberByName() is function that goes through all array elements and finds
the one with the given name. Note that I had to use "Control+ActiveXImpl"
type name - GetNestedType() didn't work (any ideas why?). Note that late
binding (GetClientSite()) works fine now. Note that DesignMode property
actually stores UserMode so it's false in design time.

For a some reason .Net control's properties don't persist in COM containers.
I'm trying now to figure out why. If you have any thought about that I'd
appreciate any input.

thanks,
v

Hi Vladimir,
IT WORKS IT WORKS. You are great. I have implemented this in C# and it
gave
me the reference I needed. I suppose, that I had to implement all of your
code, but I stopped at the check for site is Nothing, because I can cast
it
at the type I want. So, I do not know what the rest of the code have to
do.
If you have a little time, pls, explain. Nevertheless so far, you
are
the
only guy around who have succeeded to solve that problem. I have
posted
it
allover, with different ideas, but ... nothing.

Thank you again
Sunny

"Vladimir Kouznetsov" <[email protected]>
wrote
in
message Here is a sample code (in VB but it shouldn't not be difficult to
translate
it) I used to get UserMode property:

Dim strAssembly As String
strAssembly =
Type.GetType("System.Object").Assembly.CodeBase.Replace("mscorlib.dll",
"System.Windows.Forms.dll")
strAssembly = strAssembly.Replace("file:///", String.Empty)
strAssembly =
Reflection.AssemblyName.GetAssemblyName(strAssembly).FullName
Dim unmt As Type = _
Type.GetType(Reflection.Assembly.CreateQualifiedName(strAssembly,
"System.Windows.Forms.UnsafeNativeMethods"))
Dim ioot As Type = unmt.GetNestedType("IOleObject")
Dim mi As Reflection.MethodInfo = ioot.GetMethod("GetClientSite")
Dim site As Object = mi.Invoke(Me, Nothing)
If site Is Nothing Then Return -1

' We are hosted by a COM container

Dim dsite As IDispatch = site

' All the uninitialized arguments work fine in this case
Dim id As Guid
Dim params As DISPPARAMS
Dim lcid As System.UInt32
Dim wFlags As System.UInt16 = System.UInt16.Parse(2.ToString())
Dim result As Object
dsite.Invoke(-709, id, lcid, wFlags, params, result, Nothing, Nothing)

If result Then
Return 1
Else
Return 0
End If

That should give you an idea how to do what you want. IIRC
IOleClientSite
has method GetContainer() which I think you need (not the parent COM
object). BTW You can use ildasm on System.Windows.Forms.dll to make
yourself
familiar with the internal classes and implementations.

thanks,
v

Hi, it seems that I have to guess what property to get, so I'll play
with
all of them, but that happen always with combination COM/.Net/Outlook
:)
The problem is, that I have read some of docs for reflection,
and
I members properties
Windows.Forms.UserControl
 
C

Chris Peacock

Your code has been really helpful to me, as I encountered exactly the
same problem as Sunny (i.e. adding property pages to Outlook), except
that I'm using VB.NET. I thought that anyone trying to achieve this
may be interested to know that the code can be simplified to the
following:-

Public ReadOnly Property PageSite() As PropertyPageSite
Get
Dim typeUnsafeNativeMethods As Type = GetType
_(Windows.Forms.Control).Assembly.GetType
_("System.Windows.Forms.UnsafeNativeMethods")
Dim typeOleObject As Type =
typeUnsafeNativeMethods.GetNestedType _("IOleObject")
Dim methodGetClientSite As Reflection.MethodInfo = _
typeOleObject.GetMethod("GetClientSite")
Dim site As Object = methodGetClientSite.Invoke(Me,
Nothing)

Try
Return CType(site, PropertyPageSite)
Catch ex As InvalidCastException
'Handle the exception
End Try
End Get
End Property

The above PageSite property is added to your class that implements the
Outlook.PropertyPage interface.

One further necessity though, will probably be to set the caption of
the property page (the tab heading). In order to do this, I found that
I had to define my own interface as follows (substitute a GUID where
the x's are):-

<GuidAttribute("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
InterfaceTypeAttribute _ (ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface IMyProperties
Inherits PropertyPage
<DispId(-518)> ReadOnly Property Caption() As String
End Interface

Instead of implementing Outlook.PropertyPage, your class would then
implement IMyProperties.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top