Hello,
I have a BaseClass and many Classes which all inherit (directly) from the BaseClass.
One of the functions in the BaseClass is to (de)serialize the (inherited) Class to/from disk.
1. The Deserialization goes like:
#Region " Load "
Public Function Load(ByVal path As String) As Object
Return DeserializeXML(className, path)
End Function
Public Function Load(ByVal path As String, ByVal user As String) As Object
Return DeserializeXML(className, path, user)
End Function
#End Region
The "problem" with this approach is that when a class get's instanciated it is returned as an Object instead a the Derived type so I have to Cast it (option strict on):
Dim Setup As New Namespace.Settings.Setup
Setup = DirectCast(Setup.Load(PathInfo.Settings), Namespace.Settings.Setup)
If I could return it with the right Type the code would be reduced to:
Dim Setup As New Namespace.Settings.Setup
Setup = Setup.Load(PathInfo.Settings)
One way to do this is to put the Load-Method in each derived Class (but that wouldn't be very OO), so I'd like to Dynamicaly (genericaly) Change the Return-Type of the Load-Method.
Only: I haven't got a clue how-to!
2. The Deserialization goes like:
#Region " Persist "
Public Sub Persist(ByVal path As String)
Try
SerializeXML(Me, className, path)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Public Sub Persist(ByVal path As String, ByVal user As String)
Try
SerializeXML(Me, className, path, user)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
#End Region
The following code is in a Module (in the same project)
Private Sub PerformSerialization(ByVal o As Object, ByVal callerClass As String, ByVal path As String, ByVal user As String)
.........
.........
Dim swFile As StreamWriter = New IO.StreamWriter(fileName)
Dim t As Type
Dim pi As PropertyInfo()
Dim p As PropertyInfo
Select Case callerClass.
Case "settings.setup"
t = GetType(Setup)
.........
.........
Case "settings.mailserver"
t = GetType(MailServer)
.........
.........
Case "settings.products"
t = GetType(Products)
End Select
pi = t.GetProperties()
' Encrypt Strings
For Each p In pi
If p.PropertyType.ToString = "System.String" Then I also think this syntax is "Not Done". What would be the Best Syntax?
p.SetValue(o, Crypto.EncryptString(p.GetValue(o, Nothing)), Nothing)
End If
Next
' Save to Disk
xmlSerializer = New Xml.Serialization.XmlSerializer(t)
xmlSerializer.Serialize(swFile, o)
swFile.Close()
End Sub
Not very OO hum!
Instead of passing "an Object" I would like to pass the Type of the Inheriting Class. But how do I "know" that in the BaseClass.
Any clues would be greatly appreciated.
Michael
PS: I use vs.Net 2003 12 3663
Hi Michael,
1. I think we can not pass the ctype function the type of string. But I
think you may take a look at the code below, did that work for you?
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim kc As Color = Color.FromKnownColor(KnownColor.Blue)
Dim cr As Color = Color.FromArgb(kc.ToArgb())
Dim c As KnownColor
For i As Integer = 1 To 137
If Color.FromKnownColor(i).ToArgb() = cr.ToArgb() Then
Debug.WriteLine(CType(i, KnownColor).ToString())
End If
Next
Dim o As New derivedclass
o.str = "Test"
o.se()
Dim b As derivedclass
b = o.de()
MsgBox(b.str)
End Sub
Public Class baseclass
Public Sub se()
Dim ser As New Xml.Serialization.XmlSerializer(Me.GetType())
Dim writer As TextWriter = New StreamWriter("Test.xml")
ser.Serialize(writer, Me)
writer.Close()
End Sub
Public Function de() As Object
Dim ser As New Xml.Serialization.XmlSerializer(Me.GetType())
Dim fs As New FileStream("Test.xml", FileMode.Open)
Return ser.Deserialize(fs)
End Function
End Class
Public Class derivedclass
Inherits baseclass
Public str As String
End Class
2.
PropertyInfo.PropertyType Property [Visual Basic]
Gets the type of this property.
PropertyType is readonly property http://msdn.microsoft.com/library/de...us/cpref/html/
frlrfSystemReflectionPropertyInfoClassPropertyType Topic.asp
3.
Even if the method is in the baseclass.
e.g.
Public Function Hello(ByVal o As BaseClass) As String
Return o.GetType().ToString()
End Function
but if we pass an derived class instance into the method, we will still get
the derived method's type.
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
Thanks for your reply.
I have though stumbled upon another Issue namely:
I have a LibraryClass "Assembly_Phone"
Assembly_Phone has <Assembly: AssemblyDefaultAlias("I don't know.")>
Lets suppose it contains a Method:
Class Hello
Sub WhoIsCallingMe()
Dim at As Type = GetType(AssemblyDefaultAliasAttribute)
Dim r() As Object =
Me.GetType.Assembly.GetCallingAssembly.GetCustomAt tributes(at, False)
If IsNothing(r) Or r.Length = 0 Then
MsgBox("No Default defined")
Else
MsgBox(CType(r(0), AssemblyDefaultAliasAttribute).DefaultAlias)
End If
End Sub
End Class
I have another Assembly (Executable) in antoher Solution "Assembly_EXE"
Assembly_EXE references Assembly_Phone
Assembly_EXE has <Assembly: AssemblyDefaultAlias("It's me!")>
Sub Main()
Dim Ring As New Assembly_Phone.Hello
Ring.WhoIsCallingMe()
End
End Sub
Running Assembly_EXE should display a Messagbox prompting: "It's me!" BUT
it's prompting "I don't know".
What am I missing here?
Regards,
Michael
Hi Michael,
It is strange that I can not reproduce problem with your code.
But I think you may try to add a debug output in the WhoIsCallingMe method
to see what is the Me.GetType.Assembly.GetCallingAssembly actually is.
Based on my test, this will return the exe file which call the dll.
Dim at As Type = GetType(AssemblyDefaultAliasAttribute)
Debug.WriteLine(Me.GetType.Assembly.GetCallingAsse mbly.FullName)
Dim r() As Object =
Me.GetType.Assembly.GetCallingAssembly.GetCustomAt tributes(at, False)
You may have a try and let me know the result.
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
I found out what "went wrong".
I had the 'Me.GetType.Assembly.GetCallingAssembly.GetCustomA ttributes(at, False)' in the BaseClass, so it returned it's own Assembly since the Caller was the Derived Class (= same assembly)
'Me.GetType.Assembly.GetCallingAssembly.GetCalling Assembly.GetCustomAttributes(at, False)' doesn't seem to do the trick (although it seems logical to me) so I guess I will have to define the Calling Assembly in the (all) Derived Class(es).
Maybe you could come up with a better alternative?
Thanks,
Michael
Hi Michael,
I did not understanding your senario very well.
Do you mean your senario is as below?
you have a BasedClass in assembly liba as below
Imports System.Reflection
Public Class BaseClass
Public Sub WhoIsCallingMe()
Dim at As Type = GetType(AssemblyDefaultAliasAttribute)
Dim r() As Object =
Me.GetType.Assembly.GetCallingAssembly.GetCallingA ssembly.GetCustomAttribute
s(at, False)
If IsNothing(r) Or r.Length = 0 Then
MsgBox("No Default defined")
Else
MsgBox(CType(r(0), AssemblyDefaultAliasAttribute).DefaultAlias)
End If
End Sub
End Class
and an exe assembly
Module Module1
Public Class DerivedClass
Inherits LibA.BaseClass
End Class
Sub Main()
Dim o As New DerivedClass
o.WhoIsCallingMe()
End Sub
End Module
Based on my test, this will return the "It's me".
If I have any misunderstanding, please feel free to let me know.
Or can you post a simple sample to demostrator your senario?
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
I'm sorry. It was my mistake. I just "simplified" my real issue too much.
I give you here a more realistic "Dummy".
There is one additional issue I face : The MarkDirty-Method doesn't work. I guess it's a scoping issue, but I can't figure out why it doesn't work.
Kind regards,
Michael
////////////////////////////////////////////////
This is more like the Architecture:
LIBRARY:
Imports System.IO
Imports System.Xml.Serialization
Imports Stegosoft.Security
Imports Stegosoft.Security.DotFuscator
Imports System.ComponentModel
Imports System.Reflection
Namespace Settings
''' -----------------------------------------------------------------------------
''' Project : StegoSettings
''' Class : Settings.BaseSettings
'''
''' -----------------------------------------------------------------------------
''' <summary>
'''
''' </summary>
''' <remarks>
''' </remarks>
''' <history>
''' [Michael.Maes] 6/12/2003 Created
''' [Michael.Maes] 30/06/2004 Transform to better OO
''' </history>
''' -----------------------------------------------------------------------------
<Serializable(), _
SkipRenaming(), _
DebuggerStepThrough()> _
Public MustInherit Class BaseSettings
#Region " Declarations "
Dim xmlSerializer As xmlSerializer
Friend Crypto As RijndaelSimple
Friend CallingAssembly As [Assembly]
Friend CallerClass As String
Dim TrackDirty As Boolean = False
Dim _IsDirty As Boolean = False
#End Region
#Region " Constructors "
Public Sub New()
' Set the Calling Assembly
CallingAssembly = Me.GetType.Assembly.GetCallingAssembly
Instanciate()
CallerClass = Me.GetType.Name.ToString.ToLower()
Crypto = New RijndaelSimple
End Sub
Friend Sub Instanciate()
GetProductFamily()
GetCompany()
End Sub
Private Function GetProductFamily() As String
Dim t As Type = GetType(AssemblyDefaultAliasAttribute)
Dim r() As Object = CallingAssembly.GetCustomAttributes(t, False)
If IsNothing(r) Or r.Length = 0 Then
Return Nothing
Else
_ProductFamily = CType(r(0), AssemblyDefaultAliasAttribute).DefaultAlias
Return _ProductFamily
End If
End Function
Private Function GetCompany() As String
Dim t As Type = GetType(AssemblyCompanyAttribute)
Dim r() As Object = CallingAssembly.GetCustomAttributes(t, False)
If IsNothing(r) Or r.Length = 0 Then
Return Nothing
Else
_Company = CType(r(0), AssemblyCompanyAttribute).Company
Return _Company
End If
End Function
#End Region
#Region " Properties "
Dim _ProductFamily As String = Nothing
Friend ReadOnly Property ProductFamily() As String
Get
If IsNothing(_ProductFamily) Then _ProductFamily = GetProductFamily()
Return _ProductFamily
End Get
End Property
Dim _Company As String = Nothing
Friend ReadOnly Property Company() As String
Get
If IsNothing(_Company) Then _Company = GetCompany()
Return _Company
End Get
End Property
Public Overridable ReadOnly Property IsDirty() As Boolean
Get
Return _IsDirty
End Get
End Property
Protected Sub MarkDirty()
If TrackDirty Then _IsDirty = True
End Sub
Public Sub StartMarkingDirty() 'Protected
TrackDirty = True
End Sub
#End Region
#Region " FileExists "
<Description("Checks if the file exists on the Harddisk with the given Path.")> _
Public Function FileExists(ByVal path As String, Optional ByVal user As String = Nothing) As Boolean
If Not path.EndsWith("\") Then path += "\"
' If User is Undefined -> Set it to the Assembly-Name of the Calling Assembly
If user Is Nothing Then user = Me.ProductFamily
If File.Exists(path + user + FilePrefix + CallerClass + ".xml") = True Then
Return True
Else
Return False
End If
End Function
#End Region
#Region " Load "
Public Overridable Function Load(ByVal path As String, Optional ByVal user As String = Nothing) As Object
Debug.WriteLine("Public Overridable Function Load")
Return PerformDeserialization(path, user)
End Function
Friend Function PerformDeserialization(ByVal path As String, ByVal user As String) As Object
Debug.WriteLine("PerformDeserialization")
If Not path.EndsWith("\") Then path += "\"
If user Is Nothing Then user = Me.ProductFamily
If File.Exists(path + user + FilePrefix + CallerClass + ".xml") = True Then
Dim srFile As StreamReader = New StreamReader(path + user + FilePrefix + CallerClass + ".xml")
Dim t As Type
Dim pi As PropertyInfo()
Dim p As PropertyInfo
Try
xmlSerializer = New Xml.Serialization.XmlSerializer(Me.GetType())
Dim cls As Object
cls = xmlSerializer.Deserialize(srFile)
t = cls.GetType
pi = t.GetProperties()
For Each p In pi
If p.PropertyType.Equals(GetType(System.String)) Then
p.SetValue(cls, Crypto.DecryptString(CType(p.GetValue(cls, Nothing), String)), Nothing)
End If
Next
Return cls
Catch ex As Exception
Debug.WriteLine(ex.ToString)
MsgBox(ex.ToString)
Finally
srFile.Close()
End Try
Else
Dim ClassType As Type = Me.GetType()
Return Activator.CreateInstance(ClassType, True)
End If
End Function
#End Region
#Region " Persist "
Public Sub Persist(ByVal path As String, Optional ByVal user As String = Nothing)
If user Is Nothing Then user = Me.ProductFamily
PerformSerialization(path, user)
End Sub
Private Sub PerformSerialization(ByVal path As String, ByVal user As String)
If Not path.EndsWith("\") Then path += "\"
Dim FileName As String = path + user.ToLower + FilePrefix + CallerClass.ToLower + ".xml"
Dim swFile As StreamWriter = New IO.StreamWriter(FileName)
Dim t As Type = Me.GetType()
Dim pi As PropertyInfo()
Dim p As PropertyInfo
pi = t.GetProperties()
' Encrypt Strings
For Each p In pi
If p.PropertyType.Equals(GetType(System.String)) Then
p.SetValue(Me, Crypto.EncryptString(CType(p.GetValue(Me, Nothing), String)), Nothing)
End If
Next
' Save to Disk
xmlSerializer = New Xml.Serialization.XmlSerializer(t)
xmlSerializer.Serialize(swFile, Me)
swFile.Close()
End Sub
#End Region
End Class
End Namespace
************************
Namespace Settings
''' -----------------------------------------------------------------------------
''' Project : StegoSettings
''' Class : Settings.TestData
'''
''' -----------------------------------------------------------------------------
''' <summary>
''' This is "A" Demo-Class. In reality there are many classes like this, all inheriting from BaseSettings
''' </summary>
''' <remarks>
''' </remarks>
''' <history>
''' [Michael.Maes] 6/12/2003 Created
''' [Michael.Maes] 30/06/2004
''' </history>
''' -----------------------------------------------------------------------------
<Serializable()> Public Class TestData
Inherits BaseSettings
Public Sub New()
MyBase.New()
' CallingAssembly = Me.GetType.Assembly.GetCallingAssembly
' MyBase.Instanciate()
End Sub
#Region " Load "
Public Shadows Function Load(ByVal path As String, Optional ByVal user As String = Nothing) As TestData
Try
StartMarkingDirty()
Return DirectCast(PerformDeserialization(path, user), TestData)
Catch ex As Exception
Return Nothing
End Try
End Function
#End Region
Private _connectionName As String
Private _dataSource As String
Private _initialCatalog As String
Private _DataLibrary As String
Private _DataLibraryUrl As String
Private _password As String
Private _persistSecurityInfo As Boolean = False
Private _userID As String
Public Property ConnectionName() As String
Get
Return _connectionName
End Get
Set(ByVal Value As String)
_connectionName = Value : MarkDirty()
End Set
End Property
Public Property DataLibrary() As String
Get
Return _DataLibrary
End Get
Set(ByVal Value As String)
_DataLibrary = Value : MarkDirty()
End Set
End Property
Public Property DataLibraryUrl() As String
Get
Return _DataLibraryUrl
End Get
Set(ByVal Value As String)
_DataLibraryUrl = Value : MarkDirty()
End Set
End Property
Public Property DataSource() As String
Get
Return _dataSource
End Get
Set(ByVal Value As String)
_dataSource = Value : MarkDirty()
End Set
End Property
Public Property InitialCatalog() As String
Get
Return _initialCatalog
End Get
Set(ByVal Value As String)
_initialCatalog = Value : MarkDirty()
End Set
End Property
Public Property Password() As String
Get
Return _password
End Get
Set(ByVal Value As String)
_password = Value : MarkDirty()
End Set
End Property
Public Property PersistSecurityInfo() As Boolean
Get
Return _persistSecurityInfo
End Get
Set(ByVal Value As Boolean)
_persistSecurityInfo = Value : MarkDirty()
End Set
End Property
Public Property UserID() As String
Get
Return _userID
End Get
Set(ByVal Value As String)
_userID = Value : MarkDirty()
End Set
End Property
Public Function ConnectionString() As String
Dim cn As String = _
"workstation id=" & SystemInformation.ComputerName.ToString & ";" & _
"packet size=4096;" & _
"User ID=" & MyClass.UserID & ";" & _
"Password=" & MyClass.Password & ";" & _
"data source=" & MyClass.DataSource & ";" & _
"persist security info=" & MyClass.PersistSecurityInfo & ";" & _
"initial catalog=" & MyClass.InitialCatalog
Return cn
End Function
End Class
End Namespace
******************************
******************************
EXE:
''' -----------------------------------------------------------------------------
''' <summary>
''' Just a test
''' </summary>
''' <remarks>
''' </remarks>
''' <history>
''' [Michael.Maes] 1/07/2004 Created
''' </history>
''' -----------------------------------------------------------------------------
Module StartUp
Sub Main()
Dim DataConnection As New Stegosoft.Settings.TestData
DataConnection = DataConnection.Load("..\", "Jef")
Dim s As String = Nothing
s += "ConnectionName: " & DataConnection.ConnectionName & vbCrLf
s += "DataLibrary: " & DataConnection.DataLibrary & vbCrLf
s += "DataLibraryUrl: " & DataConnection.DataLibraryUrl & vbCrLf
s += "DataSource: " & DataConnection.DataSource & vbCrLf
s += "InitialCatalog: " & DataConnection.InitialCatalog & vbCrLf
s += "Password: " & DataConnection.Password & vbCrLf
s += "PersistSecurityInfo: " & DataConnection.PersistSecurityInfo.ToString & vbCrLf
s += "UserID: " & DataConnection.UserID & vbCrLf
s += vbCrLf
s += "IsDirty: " & DataConnection.IsDirty.ToString & vbCrLf
s += vbCrLf
' Changing Values => Object SHOULD become dirty (but it doesn't)
With DataConnection
..ConnectionName = "Jeff_BlaBlaBla"
..DataLibrary = "Jeff_DataLibrary"
..DataLibraryUrl = "Jeff_DataLibraryUrl"
..DataSource = "Jeff_DataSource"
..InitialCatalog = "Jeff_InitialCatalog"
..Password = "Jeff_Password"
..PersistSecurityInfo = True
..UserID = "Jeff_UserID"
End With
s += "ConnectionName: " & DataConnection.ConnectionName & vbCrLf
s += "DataLibrary: " & DataConnection.DataLibrary & vbCrLf
s += "DataLibraryUrl: " & DataConnection.DataLibraryUrl & vbCrLf
s += "DataSource: " & DataConnection.DataSource & vbCrLf
s += "InitialCatalog: " & DataConnection.InitialCatalog & vbCrLf
s += "Password: " & DataConnection.Password & vbCrLf
s += "PersistSecurityInfo: " & DataConnection.PersistSecurityInfo.ToString & vbCrLf
s += "UserID: " & DataConnection.UserID & vbCrLf
s += vbCrLf
s += "IsDirty: " & DataConnection.IsDirty.ToString & vbCrLf
s += vbCrLf
'Explicitly trigger the dirty-checking (For Testing)
s += "StartMarkingDirty: " & vbCrLf
DataConnection.StartMarkingDirty()
With DataConnection
..ConnectionName = "BliBliBli"
End With
s += "ConnectionName: " & DataConnection.ConnectionName & vbCrLf
s += "DataLibrary: " & DataConnection.DataLibrary & vbCrLf
s += "DataLibraryUrl: " & DataConnection.DataLibraryUrl & vbCrLf
s += "DataSource: " & DataConnection.DataSource & vbCrLf
s += "InitialCatalog: " & DataConnection.InitialCatalog & vbCrLf
s += "Password: " & DataConnection.Password & vbCrLf
s += "PersistSecurityInfo: " & DataConnection.PersistSecurityInfo.ToString & vbCrLf
s += "UserID: " & DataConnection.UserID & vbCrLf
s += vbCrLf
s += "IsDirty: " & DataConnection.IsDirty.ToString
MsgBox(s)
DataConnection.Persist("..\", "Jef")
End
End Sub
End Module
Hi Michael,
I understand that you have three Assembly , A,B and C.
A has the method WhoIsCallme which calls GetCallingAssembly
B inhertis A
C call the method WhoIsCallme.
I think in this way the behavior is by design, if you need to reach your
goal, you may try to override the function in the class B.
As for the second question,
I think why firsttime we change the DataConnection's property, the IsDirty
is always false is because the TrackDirty is false.
I think we may try to change the code as below in the baseclass.
Dim _TrackDirty As Boolean = False
Public Property TrackDirty() As Boolean
Get
Return _TrackDirty
End Get
Set(ByVal Value As Boolean)
_TrackDirty = Value
End Set
End Property
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
I'm mailing you a test-project.
Regards,
Michael
Hi Michael,
Yes, the constructor of TestData will be called twice.
1. first time we new the testdata
2. we deserialized the object from the file
But the two constructors will construct two instance of testdata.
Dim DataConnection As New Settings.Settings.TestData
Dim bdc As Settings.Settings.TestData
bdc = DataConnection.Load(Path, User)
Console.WriteLine(Object.ReferenceEquals(DataConne ction, bdc))
If we run the code above, we will find that the instance created by New and
the one created by Load are two different objects.
If you still have any concern, please feel free to let me know.
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
I do have soome questions left:
a.. Is it possible to determine the CallingAssembly of the CallingAssembly (so I can have the 'CallerAssembly'-Definition in the BaseForm instead of in every Derived Class)
b.. Any suggestions on how to track the Dirtyness of the Object? You suggested to make a Property out of the field TrackDirty. I don't see how that would influence the good operation. It still triggers True all the time :-(
TIA & Kind regards,
Michael
Hi Michael, a.. Is it possible to determine the CallingAssembly of the
CallingAssembly (so I can have the 'CallerAssembly'-Definition in the
BaseForm instead of in every Derived Class)
From the document said,
Assembly.GetCallingAssembly Method
Returns the Assembly of the method that invoked the currently executing
method.
The method is used to retrieve the assembly which is calling the currently
running code so the GetCallingAssembly.GetCallingAssembly will not work.
But we can simplified the code to call GetCallingAssembly in every derived
class and then pass the returned assembly to a method in the base class so
that we can save a lot of coding job.
b.. Any suggestions on how to track the Dirtyness of the Object? You
suggested to make a Property out of the field TrackDirty. I don't see how
that would influence the good operation. It still triggers True all the
time :-(
It seems that your design is OK that setting a flag to indicate if object
is dirty and changing the flag in every set operation of property. In
general, we think any change to the object's property will make the object
dirty. And you can set the flag to false when consider it has been reset to
be not dirty.
If you still have any concern, please feel free to post here.
Best regards,
Peter Huang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Hi Peter,
I think we can "Close" point a.
Point b however remains a big problem. I think we better switch to my thread "BaseClass + DerivedClass + Serialization -- Dirty"
Kind regards,
Michael This discussion thread is closed Replies have been disabled for this discussion. Similar topics
9 posts
views
Thread by Marco Aschwanden |
last post: by
|
20 posts
views
Thread by Jakob Bieling |
last post: by
|
2 posts
views
Thread by Rhino |
last post: by
|
1 post
views
Thread by Jack Addington |
last post: by
|
4 posts
views
Thread by Noah Roberts |
last post: by
|
14 posts
views
Thread by tshad |
last post: by
|
14 posts
views
Thread by Alexander Stoyakin |
last post: by
|
13 posts
views
Thread by cppquester |
last post: by
|
6 posts
views
Thread by exander77 |
last post: by
|
4 posts
views
Thread by barcaroller |
last post: by
| | | | | | | | | | |