Welcome to MSDN Blogs Sign in | Join | Help

Moving on...

If any of you read Gamerscoreblog, you may have noticed a suspicious resemblance between the photo of the zombie and the one that graces this site's News section...  Let me quell any rumors by saying that it is indeed true that the zombie and I are, in fact, one and the same. 

Earlier this year, I decided to leave the .NET Compact Framework team and to join the good folks in Windows Gaming.

I truly enjoyed my 7.5 years as a member of the .NET Compact Framework team.  I have been very fortunate to have gotten to work with a fantastic group of people on an extremely cool and important product.  From when I joined them in late 2000 (early in the v1 product), to the release of version 3.5, I have grown and learned quite a bit.

If someone had told me back in August 2000, that I would become an avid writer on a weblog and a presenter at developers conferences, I would have told them they were crazy.  In fact, I not only became those two things, I LOVED doing them.  Not only were they avenues for personal and professional growth, they lead me to meeting many many great people who came out to MEDC, Mix and other smaller (Redmond based) conferences.  Thanks to all who attended these conferences, met with me and read my writings.

Lately, you may have noticed that the flow of content here has been slower than usual.  Switching jobs can be a bit time consuming :).  This site's topic will move from the .NET Compact Framework to a general testing and development focus.  I expect to maintain this slower traffic pace for some time.  Over time, I will likely migrate the .NET Compact Framework topics into a smaller set of categories to make room for the new content.

Thanks again to everyone for the great time with the .NET Compact Framework and I hope to get to one day see some of you again at future conferences (ex: GDC, CES) or in and around Redmond.

Take care!
-- DK

Posted by DavidKlineMS | (Comments Off)
Filed under:

One of the great ones has passed

This evening, I came home to the news that my absolute favorite author, Arthur C. Clarke had died.  I have been an avid reader or Mr. Clarke since I was 12.  That year, I first picked up a copy of '2001: A Space Odyssey".  From the start, I was hooked.  Now, 26 years later, I have re-read '2001' at least 20 times (yes, I really do average almost once per year) and have devoured almost everything I have gotten my hands on.  I am, in fact, currently in the middle of 'Firstborn' (co-written by Stephen Baxter). 

While it is very difficult to create a list of his essential books, some of my favorites are:

  1. 2001: A Space Odyssey
  2. Childhood's End
  3. Songs of Distant Earth
  4. The Ghost of the Grand Banks
  5. Fountains of Paradise
  6. Hammer of God

Tonight, I'm going to stay up late, finish 'Firstborn' and grab one of my many copies of '2001' and start reading.  I'll likely have my DVD of the movie playing as well.

Mr. Clarke, you will be greatly missed.  Thanks for the legacy of great books.  You made me a hard core reader 26 years ago, and I remain one to this day.  I'll enjoy your work for life.

-- DK

Posted by DavidKlineMS | 1 Comments
Filed under:

See you at Mix 2008

Just a quick note to let folks know that I will be making the trip to Mix this year.  I have recently gotten my flight, hotel and conference in order and am now officially registered.  I'm very much looking forward to this conference.  It'll be my first time attending and I hear it's a fun one. :)


Hope to see you there,
-- DK

One other note, sorry for going dark last month.  Things have been extremely busy for me lately (and will likely continue as such).  My usual stream of posts has unfortunately been impacted and continue to be slower than "normal".

Posted by DavidKlineMS | (Comments Off)
Filed under:

Presentation tip: Showing code

Since MEDC 2005, I have been tapped to present sessions many times.  Over the past three years, I have learned a lot and have grown as a presenter.

When I was first getting started, I attended my first speaker training course.  Speaker training is a one-on-one class where you meet with your coach and he/she observes your presentation technique.  I found the experience extremely valuable and make a point to re-take training every year.

One of the most valuable pieces of advice I was given is in how to best configure Visual Studio for code readability in a presentation hall.  This advice applies to all audience and room sizes: from 16 person conference rooms to lecture halls that seat 400 or more.

Whenever I am preparing for a presentation (or am helping a new presenter prepare), I set Visual Studio's Font and Color settings as follows:

Font: Lucida Console
Font size: 16-24 (depending on room size and projector resolution)

Text color: Black
Background color: White

Highlighted text color: Black
Highlighted text background color: Yellow

It is amazing the difference these settings can make in code readability for your audience.

One note on code layout.  When using large fonts (20) and small resolutions (1024x768), it is very important to format your code to avoid scrolling as much as possible.  Placing each argument on its own line is perhaps the best advise I can give here.  This way, the amount of side-to-side scrolling is minimized (there will still be some when enum / variable names are long).  The example below shows my typical demo code formatting (it is very similar to what I use on this site).

public DemoMethod(String arg1, 
                  Int32 arg2, 
                  Boolean arg3)
{
    // local variables

    try
    {
        String result = DemoHelperClass.AStaticMethodWithManyArgs( 
                            arg1, 
                            arg2, 
                            arg3,
                            var1,
                            var2,
                            var3);

        // additional code
    }
    catch(Exception e)
    {
        // handler code
    }
}


Ad you can see, the method the demo is calling has a pretty long name.  For this reason, all of the arguments are on their own lines.  Depending on the resolution of the screen, you may wish to further limit the horizontal scrolling by reformatting as below.

String result = 
    DemoHelperClass.AStaticMethodWithManyArgs(
        arg1,
        arg2, 
        arg3,
        var1,
        var2,
        var3);

It may look a bit odd when you are developing the demo, however it will be much easier to read for the audience.  And it has a nice side-effect: it's also better for reviewing printed copies of the code.

Take care and Happy Holidays!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:

.NET Compact Framework v3.5 Power Toys Released!

I've been meaning to post this for the last week... 

The .NET Compact Framework v3.5 Power Toys have officially been released.  You can download the Power Toys from here.

See the .NET Compact Framework Team weblog for details on the included tools.

Enjoy!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:

Quick Tip: Add files to Visual Studio projects the easy way

Here's another in my series of "why didn't I know this feature was there".

While I was doing a technical review last week, I learned about a much easier way to add files to my Visual Studio projects.

In the solution explorer

  • Select a project
  • Click the "Show all files" button
  • Right-click on the desired file (will be shown with an empty document icon)
  • Click the "Include in project" menu item

This is so much easier than my usual technique (Add | Existing Item...).

Take care!
-- DK

[Edit: fix spelling]

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

CoreCon API: Brought to you by the Visual Studio for Devices team

The Visual Studio for Devices team has been posting a very interesting and informative series on the CoreCon API.  I highly encourage anyone interested in writing remote tools to read the series.  In fact, I recommend everyone interested in developing applications and/or tools for devices to subscribe to their RSS feed.

As of this posting, there are five installments to the series:
Part I
Part II
Part III
Part IV
Part V

Enjoy!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:

Steven Pratschner on the CLR Profiler for the .NET Compact Framework

I love tools.  Debuggers, analyzers, performance monitors, profilers, everything.  In version 1 of the .NET Compact Framework, the only available tools where the Visual Studio debugger and the performance counter data file (also known as the app.stats file).  Version 2 added diagnostic logs and in Service Pack 1, the Remote Performance Monitor.

Version 3.5's power toys adds one of the most asked for tools - the CLR Profiler.  This is a very cool tool that enables developers to drill into the allocations made by their applications: what object types, how many of them, what methods are creating them, etc.  With the volume of the data the CLR Profiler provides, it can sometimes be hard to know where to begin. 

To our rescue comes Steven Pratschner and his series on the CLR Profiler for the .NET Compact Framework.  He's up to three installments and I highly encourage anyone interested in application performance and memory usage to read them.  I'm currently devouring them and learning quite a bit.

Part 1: Getting Started
Part 2: Histograms and "Show who Allocated"
Part 3: The Timeline View

Enjoy!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:

[Off-topic] A present to myself

I don't often go off-topic on this site.  In fact, I probably start each off-topic post with the same basic statement.  Seeing that tomorrow is a national holiday in the United States, today seemed like a good time to take a little break here.

Yesterday, I purchased a Zune 8GB device and just have to give public kudos to the hardware and software teams.  This device feels and sounds great.  Everything I have listened to (from classic jazz to fusion to modern rock) sounds crisp and clean.

As for feel, it just feels.... right.  It fits my hand well and has just the right weight for my taste.  The device software adds to the overall experience.  It's very easy to navigate and contents of the device are easy to find -- even with hundreds of songs on it.

Quite possibly the best feature of this little device is the touch pad.  Sliding your finger from left to right selects the next song, right to left goes back to the previous.  Moving up and down adjusts the volume.  There is no clicking required, just a light touch.  You can click too, if that is your preference.

Nice job and congratulations to the Zune team!  You've produced a fantastic device.  I love it.

-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

The Journey of the Lunch Launcher: Part 8 - What did I learn?

Part 1 - The origins of the 'lunch launcher'
Part 2 - MEDC 2007
Part 3 - Managing the Transport
Part 4 - Sending messages
Part 4b - The output channel
Part 5 - Receiving messages
Part 6 - Processing messages
Part 7 - The Lunch Manager

Now that we have seen the story of the Lunch Launcher, from Roman's original introduction, through my journey implementing the MEDC 2007 demo to today's current version, it is time to take a step back and look at what I have learned.

Store and Forward Messaging is REALLY cool
The introduction of Store and Forward Messaging in version 3.5 of the .NET Compact Framework was the key to getting the Lunch Launcher from idea to reality.  By leveraging the email accounts that we all have, devices have become addressable and applications like the Lunch Launcher have become possible.

What started as a dream (being able to coordinate lunch with friends, no matter where they may be) has become a reality thanks to Store and Forward Messaging and the Windows Communication Foundation.

Windows Communication Foundation code is very reusable
When I first started working on the Lunch Launcher, I thought I would be writing a significant amount of message type (invitation, reply, etc) code.  I started with separate implementations for sending and receiving each type of message.  After reviewing the code, I found that there was a very high level of similarity in each implementation.  So much similarity that I was able to merge all three into a set of generic methods.  This consolidation has helped to eliminate code issues and to make them easier to fix (one place instead of three).

One way communications can be challenging
When working with traditional two way communications (request-reply model), you get rapid feedback as to whether or not the message was received and whether or not the task succeeded.

One way communication is different.  You need to implement a stateless programming model.  There is no built-in acknowledgement of receipt, nor is there a confirmation of task completion.  There are both benefits and challenges to these one way communications behaviors. 

The primary benefit of one way communications is that you get true asynchronous communications.  The sender and receiver can go offline at any time and the communications will still go through (once back online).

The primary challenge is knowing when the other side receives the message.  Since there is no way of knowing when a device will come back online, there is no way to accurately gauge the communications latency.  I won't go into detail here, as this can become a long discussion.

As mentioned earlier, there is no receipt confirmation or results transfered in one way communications.  To get this data, applications and services will need to engage in additional one way communications (in the reverse direction of the initial transaction).  As with the original communications, this too has the same challenges and benefits.

Is this the end for the Lunch Launcher?
No.  The Lunch Launcher will continue to evolve.  I still need to implement the voting system and clean up some fit and finish issues as well as a thorough round (or twelve) of testing, testing, testing.  In fact, I plan on using Visual Studio 2008's Unit Testing feature to implement a suite of tests for the LunchLauncher.Logic assembly.

I also hope to give it a much nicer looking user interface.  What I have is functional, though I see much room for improvement.  The first step is to create a Windows Mobile Standard / Windows Mobile Smartphone version and then to borrow the eyes of some of my aesthetically minded friends with the hopes that they will have some ideas for a nicer look and feel.

Thanks for reading
It has been a joy to not only write the Lunch Launcher, it has also been great to share my experience with you -- from MEDC 2007 to this site. 

Take care!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.
The information contained within this post is in relation to beta software.  Any and all details are subject to change.

Posted by DavidKlineMS | 2 Comments

Quick Tip: Debugging Smart Device Unit Tests

While browsing MSDN today, I came upon a handy article that I wanted to share.   Titled 'How to: Debug while Running a Smart Device Unit Test', it provides step by step instructions on debugging your test projects.

There is one additional step I would like to add to those listed in the article.

WARNING: Using Remote Registry Editor incorrectly can cause serious problems that may require you to hard reset your device. Microsoft cannot guarantee that problems resulting from the incorrect use of Remote Registry Editor can be solved. Use Remote Registry Editor at your own risk.


7. Disable support for attaching to managed processes
To do this, use the Visual Studio Remote Registry Editor:

a. Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETCompactFramework
b. Set the value of Managed Debugger\AttachEnabled to 0
-or-
Delete the Managed Debugger node

Take care!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.
The information contained within this post is in relation to beta software.  Any and all details are subject to change.

The Journey of the Lunch Launcher: Part 7 - The Lunch Manager

Part 1 - The origins of the 'lunch launcher'
Part 2 - MEDC 2007
Part 3 - Managing the Transport
Part 4 - Sending messages
Part 4b - The output channel
Part 5 - Receiving messages
Part 6 - Processing messages

The journey is almost at an end.  Today, I am going to talk about the last major piece of the Lunch Launcher puzzle -- the lunch manager.

As with all entries in this series, please note that the following disclaimer covers all examples contained herein.

//-----------------------------------------------------------------------
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//-----------------------------------------------------------------------

What is the lunch manager?
The lunch manager is a class (conveniently called "LunchManager") which controls the transports, serializers and other objects which come together to allow users to coordinate lunch with their friends.  In fact, other than purely data objects (Buddy, LunchInvitation, etc), the LunchManager is the only object known to the application.

MEDC 2007
From the beginning of the Lunch Launcher project, I separated the user interface from the application logic.  As any who have attended my sessions (or watched on the conference DVDs) in the past several years can attest, I am a huge proponent of GUI-Logic separation.  The result of this design is the LunchManager.  As mentioned above, the LunchManager is the central object of the Lunch Launcher's logic.

It is the responsibility of the LunchManager to load the buddy and restaurant lists as well as control the lifetime of the listeners and the sending of messages.

For MEDC 2007, the LunchManager's Start method was very simple.

private void Start()
{
    // start the listeners
    ThreadPool.QueueUserWorkItem(InvitationListener);
    ThreadPool.QueueUserWorkItem(ReplyListener);
    ThreadPool.QueueUserWorkItem(LunchDetailsListener);
}


The remainder of the LunchManager (not counting buddy and restaurant list loading methods) has been previously discussed as part of the sending and receiving installments.

Current Version
In the current (November 2007) version, the LunchManager contains many of the details of the optimizations seen in the sending and receiving discussions.  Specifically, the management of serializers and transports.

private Dictionary<Type, LunchObjectSerializer> m_Serializers =
    new Dictionary<Type, LunchObjectSerializer>();
private LunchObjectSerializer GetSerializer<T>()
{
    // get the serializer
    LunchObjectSerializer serializer = this.m_Serializers[typeof(T)];
    Debug.Assert(null != serializer,
                "Requested serializer has not been created");
    return serializer;
}

private Dictionary<Type, TransportObjects> m_Transports =
    new Dictionary<Type, TransportObjects>();

private TransportObjects GetTransport<T>()
{
    // get the transport
    TransportObjects transport = this.m_Transports[typeof(T)];
    Debug.Assert(null != transport,
                "Requested transport has not been created");
    return transport;
}


Serializers and transports are now created at startup and kept for the duration of the application.  This improves performance by reducing the amount reflection (serializer creation time) and garbage (recreating transports and serializers).

As a result, Start has been refined.

public void Start()
{
    // add the event handlers to the lookup table
    this.m_Events.Clear();
    this.m_Events.Add(typeof(LunchInvitation), LunchInvitationReceived);
    this.m_Events.Add(typeof(LunchInvitationReply), LunchReplyReceived);
    this.m_Events.Add(typeof(LunchDetails), LunchDetailsReceived);

    // add serializers to the dictionary
    this.m_Serializers.Clear();
    this.m_Serializers.Add(typeof(LunchInvitation),
                           new LunchObjectSerializer(typeof(LunchInvitation)));
    this.m_Serializers.Add(typeof(LunchInvitationReply),
                           new LunchObjectSerializer(typeof(LunchInvitationReply)));
    this.m_Serializers.Add(typeof(LunchDetails),
                           new LunchObjectSerializer(typeof(LunchDetails)));
                
    // add transport objects to the dictionary
    this.m_Transports.Clear();
    this.m_Transports.Add(typeof(LunchInvitation),
                          new TransportObjects(InviteChannelName));
    this.m_Transports.Add(typeof(LunchInvitationReply),
                          new TransportObjects(ReplyChannelName));
    this.m_Transports.Add(typeof(LunchDetails),
                          new TransportObjects(DetailsChannelName));                

    // start the listeners
    ThreadPool.QueueUserWorkItem(InvitationListener);
    ThreadPool.QueueUserWorkItem(ReplyListener);
    ThreadPool.QueueUserWorkItem(LunchDetailsListener);
}


As you can see, Start now performs a bit more work.  The event, serializer and transport collections (Dictionary<TKey, TValue>) are populated prior to the listeners being started.

Stop gets the following implementation. 

public void Stop()
{
    // channels are stopped by closing them
    foreach(TransportObjects trans in this.m_Transports.Values)
    {
        trans.InputChannel.Close();
    }
            
    // NOTE: The listener threads will uninitialize the transports when they
    //  exit (shutdown or fatal condition)
}


As I described when talking about the listeners, calls to Receive return immediately with a value of null when the input channel is closed.  Stop takes advantage of this and simply closes the input channel for each transport in the collection.

What's next?
That wraps up the details of the Lunch Launcher.  After a long and winding road, I have brought the idea to life and had the privilege to demonstrate it in front of a packed session at MEDC.  Thanks to the entire .NET Compact Framework team for helping to keep the idea alive and to keep pushing the platform to make it possible.

In the next (final?) installment, I will take a higher level view of what I have learned and what is next on my journey.  As I have mentioned previously, as I get time to work on the Lunch Launcher, I plan to update the relevant posts with any significant changes.

Take care!
-- DK

[Edit: add snippet disclaimer && fix snippet formtting]

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.
The information contained within this post is in relation to beta software.  Any and all details are subject to change.

Posted by DavidKlineMS | 2 Comments

Microsoft Tester Center open for business

Alan Page announced today that the new Microsoft Tester Center site was live.  This is very cool.  What I love about this site is summed up in three words... it's about testing.

I encourage anyone interested in learning more about testing and what testers do to check out the new site -- especially the whiteboard videos.

Enjoy!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:

The Journey of the Lunch Launcher: Part 6 - Processing messages

Part 1 - The origins of the 'lunch launcher'
Part 2 - MEDC 2007
Part 3 - Managing the Transport
Part 4 - Sending messages
Part 4b - The output channel
Part 5 - Receiving messages

Last time, I talked about how the Lunch Launcher receives messages.  Today's topic is now the messages are processed.

As with all entries in this series, please note that the following disclaimer covers all examples contained herein.

//-----------------------------------------------------------------------
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//-----------------------------------------------------------------------

MEDC 2007
Just like the listeners, the MEDC 2007 version of the Lunch Launcher demo contained three nearly identical processing methods.

/// <summary>
/// Processes a received lunch invitation
/// </summary>
/// <param name="msg">
/// The lunch invitation message
/// </param>
/// <param name="from">
/// The email address of the sender
/// </param>
private void ProcessInvitation(Message msg, String from)
{
    LunchObjectSerializer serializer =
        new LunchObjectSerializer(typeof(LunchInvitation));

    // get the buddy who sent the invitation
    Buddy buddy = LookupBuddy(from);
           
    // ignore invitations from those who
    //  are not in the buddy list
    if(null != buddy)
    {
        // get the invitation
        LunchInvitation invitation =
            msg.GetBody<LunchInvitation>(serializer);
           
        // notified registered event handlers
        SendInvitationReceived(
            new InvitationReceivedEventArgs(buddy, invitation));
    }
}

When a message is processed, the method creates a serializer for the LunchInvitation object to be used when the message body is extracted.

Next, the sender of the invitation is compared with the user's buddy list.  This is done to avoid unsolicited invitations from people with whom the user would rather not have lunch.

If the sender is found in the buddy list, the message body is retrieved and the user interface is notified.  When the user interface handles the event, it presents the user with a 'you have been invited' form.

Current version
In the current version (October 2007), the processing methods have been consolidated into a single generic method.

/// <summary>
/// Process a received message
/// </summary>
/// <typeparam name="T">
/// The type of object contained within the message
/// </typeparam>
/// <param name="msg">
/// The lunch invitation message
/// </param>
/// <param name="from">
/// The email address of the sender
/// </param>
private void ProcessMessage<T>(Message msg, String from)
{
    // get the buddy who sent the message
    Buddy buddy = LookupBuddy(from);
           
    // ignore invitations from those who
    //  are not in the buddy list
    if(null != buddy)
    {
        // get the serializer
        LunchObjectSerializer serializer = GetSerializer<T>();

        // get the invitation
        T payload = msg.GetBody<T>(serializer);
           
        // notified registered event handlers
        SendMessageReceived(
            new LunchLauncherMessageReceivedEventArgs<T>(buddy, payload));
    }
}

There are a few changes to this version.  First, like last time's example, the serializer is retrieved from a class-global collection.  This saves on reflection costs and reduces the amount garbage generated as the application receives messages.

The second change is the consolidation of the event handlers into a generic method.  As I was reviewing the code right after my session, I took notes on the areas where code was highly similar.  Wherever possible, I merged the methods into generic versions.  This has resulted in much less code to review :) and fewer issues to investigate.  Fixing once is much better than fixing multiple times, or worse yet, forgetting to apply the fix to one of the duplicates.

Lastly, I moved the serializer retrieval into the conditional block.  There is no need to fetch it unless we recognize the sender of the message -- a very small performance increase.

In closing
We are getting towards the end of this series -- there are only a couple more topics that I would like to cover.  I hope you are enjoying reading about my experiences writing the Lunch Launcher demo.  As I mentioned last time, as I continue to work on the demo, I plan on keeping the examples in this series fresh.  This will likely result in edits (read: extensions) to the posts in this series.  As I make changes, I will post a topic pointing them out.

Next up, a look at the 'lunch manager'.

Take care,
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.
The information contained within this post is in relation to beta software.  Any and all details are subject to change.

Recommended Reading: Windows Mobile Data Synchronization with SQL Server 2005 and SQL Server Compact 3.1

I recently finished reading Rob Tiffany's newest book 'Windows Mobile Data Synchronization with SQL Server 2005 and SQL Server Compact 3.1' (special thanks to Rob for blogging about the process and the chapters).  For anyone, like me, who attended MEDC 2007 and saw his SQL Merge Replication demo, this book explains how he built the demo.

I particularly enjoyed and appreciated the 'Rob's commentary' sections.  They cover practical performance and scalability advice and some great insights on data synchronization.

Enjoy!
-- DK

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by DavidKlineMS | 1 Comments
Filed under:
More Posts Next page »
 
Page view tracker