هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.
A second honeymoon with Microsoft Agent. Do you remember our article Programming Microsoft Agent in WinForms and our sample application PartIt? After you have included your desired agent in your application, are you feeling bad with the default popup menu? If so, then you are in the right place (welcome :).)
Enough talking, let’s get to work! First, prepare your code that loads the Microsoft Agent and brings it to the screen.
After that, create your System.Windows.Forms.ContextMenuStrip and add your required items (well, including ‘Hide’ maybe) and finish the item event handlers.
Now, let’s complete it. Get to the code that loads the agent character (e.g. calls the Characters.Load() method of the agent control object, AxAgentObjects.AxAgent) and just disable the AutoPopupMenu flag/property of the character object, AgentObjects.IAgentCtlCharacterEx. This flag/property determines whether to allow the default popup menu or not.
For example, the following code disables this property:
Next comes the interesting point. When the character is clicked, the ClickEvent event of the agent control (AxAgent) fires. So the next step is to handle this event and to provide your code that brings up your custom context menu. Consider the following code:
// agentCtl.ClickEvent += agent_ClickEvent;
public void agentCtl_ClickEvent(object sender, AxAgentObjects._AgentEvents_ClickEvent e)
{
if (e.characterID == "Merlin") // check for this if you have many characters
{
if (e.button == 2) // 1 = left, 2 = right
{
myContextMenu.Show(e.x, e.y);
}
}
}
هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.
Overview
First, this writing concentrates of and compares between three programming languages, C#, C++/CLI, and ISO/ANSI C++. It discusses 9 rules that every developer should keep in mind while working with constructors, destructors, and finalizers and class hierarchies:
Rule #1: Contrsuctors are called in descending order
Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing
Rule #3: Destructors are called in ascending order
Rule #4: Finalizers are a feature of GC-managed objects only
Rule #5: You cannot determine when finalizers would be called
Rule #6: C++/CLI differs between destructors and finalizers
Rule #7: In C++/CLI and classic C++, you can determine when destructors are called
Rule #8: In C++/CLI, destructors and finalizers are not called together
Rule #9: Beware of virtual functions in constructors
Rule #1: Constructors are called in descending order
Rule #1: Constructors are called in descending order; starting from the root class and stepping down through the tree to reach the last leaf object that you need to instantiate. Applies to C#, C++/CLI, and ANSI C++.
Let’s consider a simple class hierarchy like this:
class BaseClass
{
public BaseClass()
{
Console.WriteLine("ctor of BaseClass");
}
}
class DerivedClass : BaseClass
{
public DerivedClass()
{
Console.WriteLine("ctor of DerivedClass");
}
}
class ChildClass : DerivedClass
{
public ChildClass()
{
Console.WriteLine("ctor of ChildClass");
}
}
ChildClass inherits from DerivedClass, and DerivedClass, in turn, inherits from BaseClass.
When we create a new instance of ChildClass using a simple line like this:
static void Main()
{
ChildClass cls = new ChildClass();
}
the code outputs the following results:
ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing
Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing; the function called before the object is fully-removed from the memory (i.e. GC-collected). Applies to C# only.
Let’s consider the same class hierarchy but with destructors:
class BaseClass
{
public ~BaseClass()
{
Console.WriteLine("dtor of BaseClass");
}
}
class DerivedClass : BaseClass
{
public ~DerivedClass()
{
Console.WriteLine("dtor of DerivedClass");
}
}
class ChildClass : DerivedClass
{
public ~ChildClass()
{
Console.WriteLine("dtor of ChildClass");
}
}
When you define a class destructor with that C++-alike syntax (preceding the function name with a ~) the compiler actually replaces your destructor with an override of the virtual Object.Finalize() function. That is, before the object is removed (i.e. GC-collected) from the memory, the finalizer (i.e. destructor) is called first. This finalizer first executes your code. After that it calls the finalizer of the base type of your object. If we could decompile our assembly, we would see that our destructor in the ChildClass (so other classes) has been replaced with this function:
Rule #3: Destructors are called in ascending order
Rule #3: Destructors are called in ascending order, starting from the leaf object that you need to instantiate and moving up through the tree to reach the very first base class of your object. In reverse of constructors calling order. Applies to C#, C++/CLI, and ANSI C++.
Now, instantiate your class:
static void Main()
{
ChildClass cls = new ChildClass();
// 'cls' is removed from memory here
}
the code should outputs the following results:
dtor of ChildClass
dtor of DerivedClass
dtor of BaseClass
Rule #4: Finalizers are a feature of GC-managed objects
Rule #4: Finalizers are a feature of GC managed objects (i.e. managed classes). That’s because the finalizer is called only when the GC removes the object from the memory (i.e. frees memory associated with).
Now, try to create a simple structure with a destructor:
struct MyStruct
{
~MyStruct()
{
Console.WriteLine("dtor of MyStruct");
}
}
The code won’t compile. That’s because that GC doesn’t handle structures.
Rule #5: You can’t determine when finalizers would be called
That’s because you don’t know when the next garbage collection would occur, even if you performed a manual garbage collection (using System.GC.Collect() function) you won’t know exactly when memory would be released. In addition, GC always delay releasing of finalizable object, it puts them in a special GC queue called freachable (pronounced ef-reachable, F stands for Finalize) queue. Applies to C# and C++/CLI (.NET.)
Rule #6: C++/CLI differs between destructors and finalizers
Rule #6: C++/CLI differs between destructors and finalizers. That is, finalizers are called by GC, and destructors are called when you manually delete the object.
Let’s consider the same example but in C++/CLI:
ref class BaseClass
{
public:
BaseClass()
{
Console::WriteLine("ctor of BaseClass");
}
~BaseClass()
{
Console::WriteLine("dtor of BaseClass");
GC::ReRegisterForFinalize(this);
}
};
ref class DerivedClass : BaseClass
{
public:
DerivedClass()
{
Console::WriteLine("ctor of DerivedClass");
}
~DerivedClass()
{
Console::WriteLine("dtor of DerivedClass");
GC::ReRegisterForFinalize(this);
}
};
ref class ChildClass : DerivedClass
{
public:
ChildClass()
{
Console::WriteLine("ctor of ChildClass");
}
~ChildClass()
{
Console::WriteLine("dtor of ChildClass");
GC::ReRegisterForFinalize(this);
}
};
When we run the code:
int main()
{
ChildClass^ cls = gcnew ChildClass();
}
it outputs the following results:
ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
The destructors are not called. Why? Unlike C#, in C++/CLI there is a big difference between destructors and finalizers. As you know, the finalizer is called when the GC removes the object from the memory. Destructors, on the other hand, are called when you destroy the object yourself (e.g. use the delete keyword.)
Now, try to change the test code to the following:
The delete statement calls object destructors and removes the object from memory.
Or else, declare the object with stack-semantics:
int main()
{
ChildClass cls;
}
Now, destructors are called when the scope of the object ends.
Rule #8: In C++/CLI, destructors and finalizers are not called together
Rule #8: In C++/CLI, destructors and finalizers are not called together. Only destructors or finalizers are called. If you manually delete the object or you declare it with stack-semantics, destructors are called. If you leaved the object for GC to handle, finalizers are called.
Now try to run the code. The code should outputs the following results:
ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
dtor of ChildClass
dtor of DerivedClass
dtor of BaseClass
Rule #9: Beware of virtual functions in constructors
Rule #9: Beware of virtual (overridable) functions in constructors. In .NET (C# and C++/CLI,) the overload of the most derived object (the object to be instantiated) is called. In traditional C++ (ISO/ANSI C++,) the overload of the current object constructed is called.
Let’s update our C# example:
class BaseClass
{
public BaseClass()
{
Foo();
}
public virtual void Foo()
{
Console.WriteLine("Foo() of BaseClass");
}
}
class DerivedClass : BaseClass
{
public DerivedClass()
{
}
public override void Foo()
{
Console.WriteLine("Foo() of DerivedClass");
}
}
class ChildClass : DerivedClass
{
public ChildClass()
{
}
public override void Foo()
{
Console.WriteLine("Foo() of ChildClass");
}
}
When you execute the code:
static void Main()
{
ChildClass cls = new ChildClass();
}
you would get the following results:
Foo() of ChildClass
The same code in C++/CLI:
ref class BaseClass
{
public:
BaseClass()
{
Foo();
}
virtual void Foo()
{
Console::WriteLine("Foo() of BaseClass");
}
};
ref class DerivedClass : BaseClass
{
public:
DerivedClass()
{
}
virtual void Foo() override
{
Console::WriteLine("Foo() of DerivedClass");
}
};
ref class ChildClass : DerivedClass
{
public:
ChildClass()
{
}
virtual void Foo() override
{
Console::WriteLine("Foo() of ChildClass");
}
};
The code outputs the same results.
But what if you need to call the virtual function of the BaseClass? Just change the code to the following:
ref class BaseClass
{
public:
BaseClass()
{
BaseClass::Foo();
}
virtual void Foo()
{
Console::WriteLine("Foo() of BaseClass");
}
};
Now, the code outputs:
Foo() of BaseClass
Let’s consider the same example but in classic ISO/ANSI C++:
This writing discusses the Twitter API and how you can utilize it in your managed application. It begins by a brief discussion of the API and the methods used. After that, it digs into the discussion of how you can utilize the API into your application with help of code samples and examples. At the end of this writing, there’s a nice open-source Twitter client application available for download. Worth mentioning that this article focuses on the REST API set of Twitter, specifically XML endpoints.)
Introduction
Today, we are talking about Twitter as it is one of the most famous social networking services. This article discusses how you can create Twitter clients using .NET Framework. Oh, before we start, I’d like to introduce you my twitter account first; it’s @elsheimy.
Twitter API
What Twitter API is
Before we dig into the discussion of Twitter API we need to know first what the Twitter API is.
Twitter API is a set of endpoints (or methods) that allow applications to work with Twitter data.
REST API:
This is the core API set; it allows developers to access core Twitter data, it contains most (if not all) of the methods and functions you would use to utilize Twitter data in your application, and it supports (mostly) three formats (or endpoints) for each method: XML, Atom, and JSON formats.
Search API:
Fully integrating search methods allows you to search for tweets based on specific criteria and to search for daily/weekly hot trends. It supports only the JSON format. This set of API was originally developed by Summize, Inc. and integrated into the Twitter API. (Twitter is looking forward to unify the REST API.)
Streaming API:
A set of API methods that allow near-realtime access to various subsets of Twitter public statuses. All methods are in JSON format, no XML or ATOM endpoints available.
Actually, the REST API would be very sufficient for you unless you find it easier to handle the JSON format.
It is worth mentioning that the REST API provides very handful and sufficient search methods that your application can use. If you need more searching capabilities (like searching for tweets that confirm to a set of criteria,) you can dig into the REST Search API.
You are not required to stick to a specific set of API methods. You can mix the methods you like from the three APIs.
Wikipedia: REST (Representational State Transfer) is a style of software architecture for distributed hypermedia systems such as the World Wide Web.
Wikipedia: JSON (JavaScript Object Notation) is a lightweight text-based open standard designed for human-readable data interchange based on the JavaScript programming language.
Wikipedia: Atom Publishing Protocol (AtomPub or APP) is a simple HTTP-based protocol for creating and updating web resources.
This writing focuses on the XML endpoints of the REST API.
API Documentation
Twitter has with a very nice API documentation that documents each and all methods and endpoints of the three parts of the API. This documentation can be found here: http://apiwiki.twitter.com.
To avoid duplication, we are not going to discuss the Twitter API again in this writing; it is already documented and available for all users. Instead, we will take a brief look at the API and discuss how we can utilize it in our .NET application. For simplicity, we will focus on XML endpoints. Therefore, all of our code would rely on the XML features of .NET Framework 2.0 (i.e. System.Xml.dll library.) Oh, you are free to write the code the way you like (e.g. integrating LINQ for XML into the code.)
Calls to the Twitter API are limited:
Don’t expect to post unlimited updates or to follow thousands of users in just an hour! Many methods have a call limit (hourly/daily.) For example, at the time of this writing, you have only 150 API requests per hour, further requests would return an exception. In addition, you can send only 1,000 updates and 250 direct messages per day (can we remove this word ‘only€?) You can check Twitter limits here.
Every endpoint has its HTTP methods that need to be set (e.g. GET, POST, and DELETE) in the request. The documentation of each endpoint lists the HTTP methods required for that endpoint. For more information about HTTP and HTTP methods check the RFC 2616 Section 9 document.
Some methods (like methods send updates) require user authentication and others not. Authentication is discussed soon.
API Methods
The following is a discussion about Twitter API methods and how you can call them.
This method returns the 20 most recent statuses from arbitrarily-selected non-protected Twitter users; this list is called the Public Timeline. Because we have selected the XML format (or endpoint) we end up with XML data.
From the data returned we can extract the structure of statuses and users. Twitter API is good enough; it uses the same structure for all data returned. In other words, all methods (in the REST API, remember?) use the same structure (i.e. schema) to represent status and user objects. Those structures are covered soon.
Methods with Arguments
Most of methods accept arguments, some are optional and others are required. An example of a function requires a single argument is the users/show method that returns profile information about a specific user.
This function accepts one of three arguments:
id:
The ID or screen name of the user.
user_id:
The ID of the user.
screen_name:
The screen name of the user.
We set arguments the same way we set web pages query strings. Considering my twitter account @elsheimy as an example, we could query user profile using one of four ways:
Provide your Twitter username and password to pass to the results. If authentication was OK, you should receive the most recent 20 statuses of your friend timeline in the same structure (schema) as the Public Timeline method. On the other hand, if the authentication process failed, you should receive an error:
/1/statuses/friends_timeline.xml
Could not authenticate you.
Let’s consider another example, the direct_messages/new method. This function sends a new direct message to a specific user from the current user authenticated.
This function accepts two required arguments:
user:
The ID or screen name of the recipient user. You can use one of two arguments in place of this argument:
screen_name:
Screen name of the user.
user_id:
The ID of the user.
text:
Message body, not longer than 140 UTF-8-encoded characters.
The following two calls send the same message (‘hi, how is it going€) to the same user:
Notice that we need to encode arguments before we pass them to the server.
Because this function updates data, it requires HTTP POST method. Therefore, you won’t be able to try it from your browser unless your browser allows you to set HTTP methods in the request.
If the function succeeded, it should return the message object that has been sent, and it should be like this:
88619848
1401881
all your bases are belong to us.
7004322
Wed Apr 08 20:30:04 +0000 2009
dougw
igudo
1401881
Doug Williams
dougw
San Francisco, CA
Twitter API Support. Internet, greed, users, dougw ...
http://s3.amazonaws.com/twitter_production/...
http://www.igudo.com
false
1036
9ae4e8
000000
0000ff
e0ff92
87bc44
290
Sun Mar 18 06:42:26 +0000 2007
0
-18000
<time>Eastern Time (US & Canada)</time>
http://s3.amazonaws.com/...
false
3394
false
false
true
7004322
Doug Williams
igudo
North Carolina
A character.
http://s3.amazonaws.com/...
http://www.igudo.com
false
19
69A1AA
000000
F00
ACBEC1
8A8F85
3
Thu Jun 21 21:16:21 +0000 2007
0
-18000
<time>Eastern Time (US & Canada)</time>
http://static.twitter.com/...
false
382
false
true
true
Notice that the sender and recipient are just user objects. It is worth mentioning that the direct_messages function return a list of direct_message objects (like the timeline functions that return list of status objects.)
Twitter and .NET
Now, you have a solid understanding of the Twitter API and how you can access it. Now, let’s utilize this API into our .NET applications.
Accessing the API
To access the API from your .NET application you need to create a HTTP request and send it to the server and wait for server response. Let’s consider an example. The following code snippet connects to the server and retrieves a System.Xml.XmlDocument that contains the returned data.
// C# Code
public static Main()
{
GetResponse("http://api.twitter.com/1/statuses/public_timeline.xml");
}
public static XmlDocument GetResponse(string uri)
{
WebRequest req = WebRequest.Create(new Uri(uri));
XmlDocument doc = new XmlDocument();
doc.Load(req.GetResponse().GetResponseStream());
return doc;
}
' VB.NET Code
Sub Main()
GetResponse("http://api.twitter.com/1/statuses/public_timeline.xml")
End Sub
Public Function GetResponse(ByVal uri As String) As XmlDocument
Dim req As WebRequest = WebRequest.Create(New Uri(uri))
Dim doc As New XmlDocument()
doc.Load(req.GetResponse().GetResponseStream())
Return doc
End Function
We have used the System.Xml.WebRequest class to create the request and to get the response (as an instance of System.Xml.WebResponse class) from the server. Once we get the XmlDocument, we can walk through the data and retrieve it.
Authentication
You can take one of two approaches to authenticate Twitter users:
OAuth Authentication:
An authentication protocol that allows users to approve application to act on their behalf without sharing their password. As this function requires more overhead and requires your application to be registered in the Twitter clients’ directory, we would better use the second approach in our examples.
Basic Authentication:
To provide authentication information in each request you make to the server. Unfortunately, Twitter announced that this method will not be available later.
Considering a method like statuses/update method that updates the status information of the user (i.e. sends a tweet) we would develop our previous code to be like this:
// C# Code
public static void Main()
{
GetResponse("http://api.twitter.com/1/statuses/update.xml?status=hello%20from%20the%20API",
"elsheimy", "b@zzword", true);
}
public static XmlDocument GetResponse(string uri, string username, string password, bool post)
{
WebRequest req = WebRequest.Create(new Uri(uri));
if (post)
req.Method = "POST";
if ((username != null) && (username.Trim() != String.Empty) && (!String.IsNullOrEmpty(password)))
req.Credentials = new NetworkCredential(username.Trim(), password);
XmlDocument doc = new XmlDocument();
doc.Load(req.GetResponse().GetResponseStream());
return doc;
}
' VB.NET Code
Sub Main()
GetResponse("http://api.twitter.com/1/statuses/update.xml?status=hello%20from%20the%20API", "elsheimy", "b@zzword", True)
End Sub
Public Function GetResponse(ByVal uri As String, ByVal username As String, ByVal password As String, ByVal post As Boolean) As XmlDocument
Dim req As WebRequest = WebRequest.Create(New Uri(uri))
If (post) Then
req.Method = "POST"
End If
If ((username Nothing) And (username.Trim() String.Empty) And (Not String.IsNullOrEmpty(password))) Then
req.Credentials = New NetworkCredential(username.Trim(), password)
End If
Dim doc As XmlDocument = New XmlDocument()
doc.Load(req.GetResponse().GetResponseStream())
Return doc
End Function
Notice how we set the HTTP method based on the function requirements. It is worth mentioning that a status should not exceed 140 UTF-8-encoded characters.
Encoding URIs
Have you noticed the previous code? It tries to post the update ‘hello from the API€. Because the text is included with the URI, special handling to the text should be carried. This special handling for text included in URIs is usually called Percent-encoding or URL Encoding. This encoding replaces unsafe characters with their hexadecimal values preceded by percentage (%) signs. Unsafe characters are those somewhat conflicted with URI special characters. For example, if we would encode the text ‘hello from the API€ we would end up with ‘hello%20from%20the%20API€.
There are many unsafe characters that should be percent-encoded including $, +, &, :, and =. A nice discussion of URL Encoding can be found in the article URL Encoding by Brian Wilson.
Once we get the idea, we can create our percent-encoding class that encodes/decodes strings:
Public Function EncodeUrl(ByVal url As String) As String
For i As Integer = 0 To _chars.GetUpperBound(0) – 1
url = url.Replace(_chars(i, 0), _chars(i, 1))
Next
Return url
End Function
Public Function DecodeUrl(ByVal url As String) As String
For i As Integer = 0 To _chars.GetUpperBound(0) – 1
url = url.Replace(_chars(i, 1), _chars(i, 0))
Next
Return url
End Function
End Module
For clearness, we have included the encoded string of each character along with the character itself. You don’t have to do this. You can convert the character to a number and just output the number in hex format.
Now we could change the code that updates the status to the following:
// C# Code
public static void Main()
{
string uri;
string text = UrlEncoder.EncodeUrl("hello from the API");
uri = "http://api.twitter.com/1/statuses/update.xml?status=" + text;
GetResponse(uri, "elsheimy", "b@zzwrd", true);
}
' VB.NET Code
Public Sub Main()
Dim uri As String
Dim text As String = UrlEncoder.EncodeUrl("hello from the API")
uri = "http://api.twitter.com/1/statuses/update.xml?status=" & text
GetResponse(uri, "elsheimy", "b@zzwrd", True)
End Sub
.NET includes a nice function that escapes (encodes) a URI, System.Uri.EscapeUriString() function. However, this function does not encode all unsafe characters.
Business Objects
Once you know the structure of the XML data returned, you can create your business objects that would encapsulate this data. The following are the three classes that would represent our three core objects, the user, the status, and the message.
// C# Code
public structure TwitterUser
{
public long ID;
public string Name;
public string ScreenName;
public string Location;
public string Description;
public string ProfileImage;
public string Url;
public bool IsProtected;
public long FollowersCount;
public long FriendsCount;
public string CreatedAt;
public long FavoritesCount;
public bool Verified;
public bool Following;
public long StatusCount;
}
public structure TwitterStatus
{
public string CreatedAt;
public long ID;
public string Text;
public string Source;
public bool Truncated;
public long InReplyToStatusID;
public long InReplyToUserID;
public bool Favorited;
public string InReplyToScreenName;
public TwitterUser User;
}
public structure TwitterMessage
{
public long ID;
public long SenderID;
public long SenderScreenName;
public long RecipientID;
public long RecipientScreenName;
public string Text;
public string CreatedAt;
public TwitterUser Sender;
public TwitterUser Recipient;
}
' VB.NET Code
Public Structure TwitterUser
Public ID As Long
Public Name As String
Public ScreenName As String
Public Location As String
Public Description As String
Public ProfileImage As String
Public Url As String
Public IsProtected As Boolean
Public FollowersCount As Long
Public FriendsCount As Long
Public CreatedAt As String
Public FavoritesCount As Long
Public Verified As Boolean
Public Following As Boolean
Public StatusCount As Long
End Structure
Public Structure TwitterStatus
Public CreatedAt As String
Public ID As Long
Public Text As String
Public Source As String
Public Truncated As Boolean
Public InReplyToStatusID As Long
Public InReplyToUserID As Long
Public Favorited As Boolean
Public InReplyToScreenName As String
Public User As TwitterUser
End Structure
Public Structure TwitterMessage
Public ID As Long
Public SenderID As Long
Public SenderScreenName As Long
Public RecipientID As Long
Public RecipientScreenName As Long
Public Text As String
Public CreatedAt As String
Public Sender As TwitterUser
Public Recipient As TwitterUser
End Structure
Retrieving Data
Now you can walk through the XML data and get that data inside your objects. The following code returns a list of statuses from your friends’ timeline.
// C# Code
public static void Main()
{
GetStatuses("elsheimy", "b@zzword");
}
public static List GetStatuses(string username, string password)
{
XmlNode node = GetResponse("http://api.twitter.com/1/statuses/friends_timeline.xml",
username, password, true);
List lst = new List(node.ChildNodes.Count);
foreach (XmlNode nd in node.ChildNodes) // for each status
lst.Add(HandleStatus(nd));
return lst;
}
public static TwitterStatus HandleStatus(XmlNode nd)
{
// HandleNumber, FormatText, HandleBool
// are just functions that converts strings
// to numbers, decoded strings, and bool
TwitterStatus status = new TwitterStatus(
nd["created_at"].InnerText,
HandleNumber(nd["id"]),
FormatText(nd["text"]),
FormatText(nd["source"]),
HandleBool(nd["truncated"]),
HandleNumber(nd["in_reply_to_status_id"]),
HandleNumber(nd["in_reply_to_user_id"]),
HandleBool(nd["favorited"]),
FormatText(nd["in_reply_to_screen_name"]),
HandleUser(nd["user"]));
return status;
}
public static TwitterUser HandleUser(XmlNode nd)
{
// HandleNumber, FormatText, HandleBool
// are just functions that converts strings
// to numbers, decoded strings, and bool
long id = HandleNumber(nd["id"]);
TwitterUser user;
user = new TwitterUser(
id,
FormatText(nd["name"]),
FormatText(nd["screen_name"]),
FormatText(nd["location"]),
FormatText(nd["description"]),
nd["profile_image_url"].InnerText,
nd["url"].InnerText,
HandleBool(nd["protected"]),
HandleNumber(nd["followers_count"]),
HandleNumber(nd["friends_count"]),
nd["created_at"].InnerText,
HandleNumber(nd["favourites_count"]),
HandleBool(nd["verified"]),
HandleBool(nd["following"]),
HandleNumber(nd["statuses_count"]));
return user;
}
' VB.NET Code
Sub Main()
GetStatuses("elsheimy", "b@zzword")
End Sub
Public Function GetStatuses(ByVal username As String, ByVal password As String) As List(Of TwitterStatus)
Dim node As XmlNode = GetResponse("http://api.twitter.com/1/statuses/friends_timeline.xml", username, password, True)
Dim lst As New List(Of TwitterStatus)(node.ChildNodes.Count)
For Each nd As XmlNode In node.ChildNodes
lst.Add(HandleStatus(nd))
Next
Return lst
End Function
Public Function HandleStatus(ByVal nd As XmlNode) As TwitterStatus
' HandleNumber, FormatText, HandleBool
' are just functions that converts strings
' to numbers, decoded strings, and bool
Dim status As New TwitterStatus( _
nd("created_at").InnerText, _
HandleNumber(nd("id")), _
FormatText(nd("text")), _
FormatText(nd("source")), _
HandleBool(nd("truncated")), _
HandleNumber(nd("in_reply_to_status_id")), _
HandleNumber(nd("in_reply_to_user_id")), _
HandleBool(nd("favorited")), _
FormatText(nd("in_reply_to_screen_name")), _
HandleUser(nd("user")))
Return status
End Function
Public Function HandleUser(ByVal nd As XmlNode) As TwitterUser
' HandleNumber, FormatText, HandleBool
' are just functions that converts strings
' to numbers, decoded strings, and bool
Dim id As Long = HandleNumber(nd("id"))
Dim user As New TwitterUser( _
id, _
FormatText(nd("name")), _
FormatText(nd("screen_name")), _
FormatText(nd("location")), _
FormatText(nd("description")), _
nd("profile_image_url").InnerText, _
nd("url").InnerText, _
HandleBool(nd("protected")), _
HandleNumber(nd("followers_count")), _
HandleNumber(nd("friends_count")), _
nd("created_at").InnerText, _
HandleNumber(nd("favourites_count")), _
HandleBool(nd("verified")), _
HandleBool(nd("following")), _
HandleNumber(nd("statuses_count")))
Return user
End Function
twittoo; Sample Application
twitto, is our WinForms sample application that utilizes the Twitter API. This is just a very simple application with basic functionalities.
This application was created using C# and WinForms 2.0 technology; it allows the user to navigate through his friends’ timeline, mentions, direct messages, retweets, and friends, and to update his status, reply to tweets, retweets, and to direct messages. Data is not refreshed automatically, the user have to click the ‘refresh€ button. (You can create your own routine that creates a timer that updates the data automatically.)
Interface
As you see, the application uses just the Windows Common Controls all around the application; no 3rd party controls were used.
To represent a status, message, or a user, the application overuses the System.Windows.Forms.TableLayoutPanel control to represent each status, message, or user. It consists of four columns and two rows. The following figure shows how the control is laid-out.
Figure 8 - TableLayoutPanel Status Template
URL Shortening
twittoo, has a very nice feature, it allows the user to insert a shortened URL into his tweets. For this to work, the application makes use of http://is.gd URL shortening service. The following is the function that utilizes the http://is.gd API:
// C# Code
public static string Shorten(string url)
{
if (!System.Text.RegularExpressions.Regex.IsMatch
(url, @"(http|ftp|https)://[w-_]+(.[w-_]+)+([w-.,@?^=%&:/~+#]*[w-@?^=%&/~+#])?"))
throw new FormatException("The URL you specificed is not in the current format.");
url = Uri.EscapeUriString(url);
string reqUri = String.Format(@"http://is.gd/api.php?longurl={0}", url);
WebRequest req = WebRequest.Create(reqUri);
req.Timeout = 5000;
using (System.IO.StreamReader reader =
new System.IO.StreamReader(req.GetResponse().GetResponseStream()))
{
return reader.ReadLine();
}
}
' VB.NET
Public Function Shorten(ByVal url As String) As String
If (Not System.Text.RegularExpressions.Regex.IsMatch _
(url, "(http|ftp|https)://[w-_]+(.[w-_]+)+([w-.,@?^=%&;:/~+#]*[w-@?^=%&;/~+#])?")) Then
Throw New FormatException("The URL you specificed is not in the current format.")
End If
url = Uri.EscapeUriString(url)
Dim reqUri As String = String.Format("http://is.gd/api.php?longurl={0}", url)
Dim req As WebRequest = WebRequest.Create(reqUri)
req.Timeout = 5000
Dim reader As New System.IO.StreamReader(req.GetResponse().GetResponseStream())
url = reader.ReadLine()
reader.Close()
Return url
End Function
What is the problem with this code? It starts multiple threads and passes them a locking object and the object is locked successfully using the lock statement, so why threads overlap? Why the synchronization doesn’t take effect?
Well, after a long period and after pushing a new thread in the MSDN forums (we are all developers do make silly mistakes, aih? :P), I come up with the solution and discovered the drawback of the code.
The problem was that each time we start off a thread we pass it a new locking object different from the others:
arr[i].Start(new object());
Therefore, every thread is locking on its own objects, so no thread synchronization take effect.
The solution is very easy, you should lock on a shared object; an object that is shared between all your threads accessing this block of code. Read more about thread synchronization here.
For example, we should change our code to the following:
private static object _lockThis = new object();
static void Main()
{
Thread[] arr = new Thread[10];
for (int i = 0; i < 10; i++)
{
arr[i] = new Thread(ThreadProc);
arr[i].IsBackground = true;
arr[i].Start();
}
foreach (Thread t in arr)
t.Join();
}
private static void ThreadProc(object obj)
{
lock (_lockThis)
{
int i = 0;
while (i < 10)
{
Thread.Sleep(1);
Console.WriteLine("Thread #{0},t{1}",
Thread.CurrentThread.ManagedThreadId, i++);
}
}
}
Finally, this was one of the bad practices and mistakes me (and many others too) fall in. Honestly, I would like to start my Bad Practices series :”>. I would write about problems I came across and solutions I found for them. In addition, I’m thinking of starting the Questions series. I got a huge number of questions every week, if we could publish them I think it would be great for all.
هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.
Contents
Contents of this article:
Contents
Overview
Introduction
Enumerating Databases on a Server
INFORMATION_SCHEMA.SCHEMATA System View
sys.sysdatabases System Table/View
sys.databases System View
sys.sp_databases Stored Procedure
Attaching Databases to the Server
CREATE DATABASE Statement
sys.sp_attach_db Stored Procedure
sys.sp_attach_single_file_db Stored Procedure
Detaching Databases from the Server
CREATE DATABASE Statement
sys.sp_detach_db Stored Procedure
Security Considerations
Overview
This writing is like an encyclopedia for the SQL statements and stored procedures used to enumerate, attach, and detach databases on a SQL Server instance. It lists the statements and stored procedures that you can use and discusses them in simple examples.
Introduction
Today we are going to talk about how to programmatically enumerate databases on a SQL Server instance and how you can attach and detach SQL Server databases.
Enumerating Databases on a Server
You can get a list of databases on a server using one of many ways:
INFORMATION_SCHEMA.SCHEMATA system view (SQL Server 2000 only)
sys.sysdatabases system table (a view in SQL Server 2005 and higher versions)
sys.databases system view (SQL Server 2005 and higher versions)
sys.sp_databases stored procedure
INFORMATION_SCHEMA.SCHEMATA System View
If you are using SQL Server 2000, you can query the system view INFORMATION_SCHEMA.SCHEMATA to get information about current databases on the server.
The following is the table diagram for INFORMATION_SCHEMA.SCHEMATA system view:
Actually, you don’t need to worry about any of the view columns, just the first column, CATALOG_NAME, that you need to worry about, it is the database (i.e. catalog) name.
The following code simply prints out the databases currently found on the default SQL Server instance in the current machine:
// C# Code
SqlConnection conn = new SqlConnection("Server=(local);Data Source=;Integrated Security=SSPI");
SqlCommand cmd = new SqlCommand("", conn);
SqlDataReader rdr;
cmd.CommandText = "SELECT DISTINCT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA";
conn.Open();
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
Console.WriteLine(rdr.GetString(0));
}
rdr.Dispose();
cmd.Dispose();
conn.Dispose();
' VB.NET
Dim conn As New SqlConnection("Server=(local);Data Source=;Integrated Security=SSPI")
Dim cmd As New SqlCommand("", conn)
Dim rdr As SqlDataReader
cmd.CommandText = "SELECT DISTINCT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA"
conn.Open()
rdr = cmd.ExecuteReader()
While (rdr.Read())
Console.WriteLine(rdr.GetString(0))
End While
rdr.Dispose()
cmd.Dispose()
conn.Dispose()
Again, this is for SQL Server 2000 only.
Check the MSDN documentation for INFORMATION_SCHEMA.SCHEMATA system view here.
sys.sysdatabases System Table/View
This is a system table specific to SQL Server 2000. In SQL Server 2005 it is provided for backward compatibility as a system view. Therefore, do not rely on this system view (or table) because it is expected to be removed in a future version of SQL Server.
The definition of this table/view is as following:
Only the first column, name, is the most important to us, it contains the database name. Other columns of importance (not for the subject of this topic) are:
dbid:
Database ID.
sid:
Security ID for the database.
crdate:
Creation date of the database.
filename:
Database filename.
You can change the line that sets the command text in the previous code to this line:
// C# Code
cmd.CommandText = "SELECT [name] FROM sys.sysdatabases";
' VB.NET Code
cmd.CommandText = "SELECT [name] FROM sys.sysdatabases";
Again, using the sys.sysdatabases system table/view is not recommended because it would be removed in a future version of SQL Server.
Check the MSDN documentation for sys.sysdatabases system view/table here.
Check this MSDN article out: Mapping System Tables to System Views for more information about SQL Server 2000 tables mapped to SQL Server 2005 views or stored procedure.
sys.databases System View
This is the new version included in SQL Server 2005 (and higher versions) replaces the SQL Server 2000 sys.sysdatabases table.
This is a very lengthy system view, it includes tenths of columns, we are interested only on the first column, name, that contains the database name.
You can change the line that sets the command text in the first code to this line:
// C# Code
cmd.CommandText = "SELECT [name] FROM sys. databases";
' VB.NET Code
cmd.CommandText = "SELECT [name] FROM sys. databases"
Check the MSDN documentation for sys.sysdatabases system view/table here.
sys.sp_databases Stored Procedure
This way is different from all others because it is not a system view or a system table, it is a system stored procedure.
This stored procedure accepts no parameters and returns a result set of three columns:
DATABASE_NAME:
The name of the database.
DATABASE_SIZE:
The size of the database (in kilobytes.)
REMARKS:
Always NULL. For the Database Engine.
The following code demonstrates this stored procedure:
' VB.NET Code
Dim conn As New SqlConnection("Server=(local);Data Source=;Integrated Security=SSPI")
Dim cmd As New SqlCommand("", conn)
cmd.CommandText = "CREATE DATABASE 'MyDatabase' ON " & _
"PRIMARY ( FILENAME = 'database.mdf' ) " & _
"FOR ATTACH"
conn.Open()
cmd.ExecuteNonQuery()
cmd.Dispose()
conn.Dispose()
If no log file (.LDF) can be found, SQL Server creates one for you.
The following code attaches the same database along with its log file. Just change the third line of the previous example that sets the command text with this line:
Check the MSDN documentation for the CREATE DATABASE statement here.
More about database files and filegroups can be found in the MSDN documentation here.
sys.sp_attach_db Stored Procedure
Another way that allows to attach a database to the server is the sys.sp_attach_db stored procedure. The definition of this stored procedure is as following:
This function takes the database name as the first argument. In addition it accepts another 16 arguments (only the first is required) represent database files. The following code attaches the same database to the server. Again, just change the third line of the previous code to the following line:
' VB.NET Code
Dim conn As New SqlConnection("Server=(local);Data Source=;Integrated Security=SSPI")
Dim cmd As New SqlCommand("", conn)
cmd.CommandText = "DROP DATABASE MyDatabase"
conn.Open()
cmd.ExecuteNonQuery()
cmd.Dispose()
conn.Dispose()
Check the MSDN documentation for the sys.sp_attach_single_file_db statement here.
sys.sp_detach_db Stored Procedure
Huh, the last one. This stored procedure is used to detach a database from the server. It has the following syntax:
sp_detach_db [ @dbname= ] 'database_name'
It accepts a single argument, the database name. The following code removes our database, MyDatabase, from the server:
Today, we are going to create a simple application, actually a simple sheet designing tool. This tool gives the user the flexibility to design his sheets, reports, bills, invoices, and receipts (whatever.)
In this writing, we will refer to reports, bills, invoices, receipts, etc. with just the name sheets. For this, we will need to give a sheet a definition.
A sheet is just like an empty page (i.e. template) that contains sheet items. For example, a resume template is a sheet that has many items (name, birth date, address, etc.) A cash receipt is a sheet that has a few items (date, amount, charged to, received by, etc.)
We will go through this tool in a nice way. We will begin by a very simple system analysis and application design. After that, we will get into coding.
Actually, I€™m not an architect, and I think I will never be. So please, DO NOT blame me for this bad analysis. It is just an illustration to get you have a solid understanding of what our application is designed for.
Problem
The user is tied into the sheet designs that the developer has created for him. The user should have the ability to design his own sheets and/or to edit application defined sheets.
Requirements
User Requirements
The following is list of common user requirements:
The application should allow the user to create, edit, and delete sheets, and to group those sheets into categories he creates.
For the sake of simplicity, those categories would be only one level, no nested categories are allowed.
The application should be persistent. The data should be stored in a database and loaded when the user needs it.
The sheet should be loaded with right data and printed whenever the user asks.
Like Visual Studio, the user should have a Toolbox that has all types of sheet items. The user could insert a sheet item into his sheet by drawing it into the screen, dragging it from the toolbox, or just double-clicking its icon in the toolbox.
The user should have a ruler to allow him measuring sheet item dimensions. In addition, he should have a design-time-only grid.
The user should be allowed to set attributes of the sheet like the page setup, grid settings, background color, etc.
Not all sheet attributes are printed out; some are for design-mode only (like background color, and grid.)
Each type of sheet items should have its own attributes and the user should be allowed to change them too.
The user should be allowed to drag sheet items around the screen and position them.
The user should be allowed to cut, copy, paste, and delete sheet items.
Of course, the user should be allowed to print out the sheet, and to preview it too.
The application should be generic to be used in any system and in any application.
Other pieces of the system should have the possibility to interact with the sheet.
Functional Requirements
Common functional (developer) requirements are:
Abstraction. Component/Class abstraction should be considered into the design. The system should consist of several components each of which groups a related features together (e.g. interface objects, business objects, and data management.)
Extensibility. The system should be extensible. A good class hierarchy should be created, and derivation (inheritance) should be taken into consideration. In addition, every sheet item should be represented by a class in this hierarchy.
Technically, sheet items would be called shapes. Because they are just drawings into the sketch (or sheet.)
To allow the user to work with sheet items (shapes) and the application to populate the sheet with data, sheet items should not be drawn directly into the sheet page. Instead every item should represented by a class that has its own drawing routine.
The sheet should be a container for the shapes. All shapes are children of the sheet.
The sheet and rulers should be Windows controls to allow them to be hosted by a Windows Form.
Sheet items should be Windows controls too to allow them to be hosted by the sheet.
The sheet items should be owner-drawn controls and they should not be derived from existing Windows controls to help integrating them easily into the sheet.
For the sake of simplicity, the sheet and shapes should be serialized into XML and stored in the database.
The database should be a SQL Server database to allow faster communication and easier manipulation of XML data.
The database should have three tables for the three core system components, the category, the sheet, and the shape.
Every sheet item in a given sheet should have a unique name (or a tag) to allow other system components to interact with it (e.g. populate it with the right data.)
The developer should have the ability to control the quality of the painting process for each control or item independently.
Solution
After going through project requirements and considering a sophisticated system design, we got a nice plan for our project that would be called Geming SISC (Sheet Infrastructure Components.)
This project would be created using C# and .NET 2.0 (or future versions of course.)
Snapshots
The following are final snapshots of the application. Figure 1 shows a cash receipt sheet designed by labels, boxes, and lines. Figure 2 shows a simple resume template designed by only a few labels and a picture. The left pane of the application lists current sheets grouped by their categories.
As you know other system components could fill those fields up with correct information.
Component Design
Figure 3 shows the three components of our system.
Figure 3 - Geming SISC Component Design
The three core components of our system, Geming SISC are:
Geming.Sisc.Infrastructure:
Contains the sheet control and shapes controls (label, text box, etc.) The sheet, rulers, and sheet items controls are all derived from System.Windows.Forms.Control.
Geming.Sisc.Data:
Business objects that would be sent to the data among database manager objects. This component references the Geming.Sisc.Infrastructure component.
Geming.Sisc.Driver:
The application interface that would be used to design sheets. It references the other two components.
Class Diagrams
Extensibility is one of the main goals of the system. A flexible class hierarchy should be considered as well as derivation (i.e. inheritance) of controls for other external objects.
The following is the class hierarchy for the sheet and the ruler controls.
Figure 4 - Sheet and Rulers Class Hierarchy
As we can see, the base class is the System.Windows.Forms.Control class. This allows the sheet and rulers controls to be inserted into a Windows form or a Windows control.
The following figure, figure 5, shows the class hierarchy of sheet items (technically called shapes.)
Figure 5 - Sheet Items (Shapes) Class Hierarchy
As we can see, the base class for all shapes is the abstract Geming.Sisc.Infrastructure.ShapeBase class which inherits from System.Windows.Forms.Control class. All other shapes are derived from ShapeBase.
Two edit shapes were created, TextBoxShape that has a look like a Windows Text Box, and LabelShape that has a look like a normal Windows Label. Both are derived from the abstract EditableShapeBase.
Other shapes are BoxShape, LineShape, ImageShape, and CheckBoxShape.
All classes are serializable (implement System.Runtime.Serialization.ISerializable interface,) so they can easily converted into XML and pushed to the database.
For simplicity, we have developed just the box, line, image, check box, and two edit shapes (or sheet items.) You can go further and create any other shapes you like. In addition, you can create more specific items like CurrencyField, DateField, etc.
For more detailed class diagrams like those shows class members, check the application code.
Database Diagram
In its simplicity, database is defined as the following diagram illustrates:
Figure 6 - Geming SISC Database Diagram
Notice that, all data operations are carried out through stored procedures in the database.
The Shape.Value column is of the type xml to allow easier manipulation of XML data in the future.
Characteristics
Here are some of the characteristics (i.e. attributes) of the sheet (some are represented by properties):
Background color:
The user should be able change the background color. Notice that the background color is not printed.
Non-printable grid:
To help the user position sheet items. The user should be able to display them or not. In addition, the user can change the grid color.
Margin:
The application should set the page margin based on print settings.
Title:
Every sheet has a title, a description, and a category.
In addition, next is a list of some the characteristics of a shape (sheet item):
Selected:
Is the shape currently selected or not. The user could select a shape by the mouse. A selection frame is drawn around the shape when selected.
Non-printable size grip:
Each shape should have a size grip to allow the user to resize the shape.
Cloning:
The shape should be able to be cloned; that is copied, to allow the copy and paste feature.
Background
Here are some refreshers of techniques we use in this system:
Painting
We will rely on custom painting in most situations. Therefore, well request help from System.Drawing classes specially the System.Drawing.Graphics class.
In some shapes like those mimic existing Windows controls (like the text box and the check box,) we will get help from classes in System.Windows.Forms.VisualStyles namespace to reflect the visual styles of Windows in our controls. In addition, System.Windows.Forms.ControlPaint class is used for drawing borders and selection rectangles.
Painting Quality
Every control in our project (sheet, rulers, and shapes) has a paining quality property that determines the quality of the drawing (low, medium, and high.) For this to work we will make use of some properties of the System.Drawing.Graphics object like those related to smoothing and anti-aliasing feature.
Serialization
All serialized objects should be marked with System.SerializableAttribute attribute. With help from System.Runtime.Serialization namespace we could customize the serialization process.
The core interface that would allow us to customize the serialization process is the System.Runtime.Serialization.ISerializable interface (implemented in all serializable classes.) Notice that, we should add the deserialization constructor to get correct deserialization.
Database
The database is a SQL Server client database with all operations included as stored procedures.
The system uses two-tier architecture; means that it accesses the database directly using just three objects, a connection, a command, and a data reader.
Design-Mode Support
For extending design-mode support we have created custom designer service for the Sheet class that inherits the System.Windows.Forms.Design.ParentControlDesigner class to allow nested controls in the Sheet object in the design-mode.
Code Highlights
In this section we will talk about significant blocks of code that would be of interest.
Painting Routines
The following is the code for OnPaint() routine overridden by only the ShapeBase class.
This function first calls the GraphicsManager.SetQuality() function that sets the quality attributes of the Graphics object. We will get back to this function in about a moment.
After that the function paints the control border using the System.Windows.Forms.ControlPaint class.
Next comes the interesting point. The function calls the virtual function PaintShape() that a derived class overrides to provide its own drawing routines.
For example, the TextBoxShape class has this PaintShape() override:
public override void PaintShape(Graphics dc)
{
base.PaintShape(dc);
using (Brush b = new SolidBrush(this.BackColor))
dc.FillRectangle(b, this.ClientRectangle);
using (Pen p = new Pen(SystemColors.ActiveCaption, 1))
{
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
Rectangle r = this.ClientRectangle;
dc.DrawRectangle(p, r);
}
Rectangle rect = this.ClientRectangle;
rect.Offset(2, 2);
rect.Width -= 4; rect.Height -= 4;
StringFormat format = new StringFormat();
format.LineAlignment = this.Alignment;
if (this.RightToLeft == RightToLeft.Yes)
format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
using (Brush b = new SolidBrush(this.ForeColor))
dc.DrawString(this.Text, this.Font, b, rect, format);
}
After that the OnPaint() function draws selection frame if the shape is currently selected.
Just before the function closes, it calls the ControlPaint.DrawSizeGrip() function to draw the sizing grip that the user would resize the shape from.
Painting Quality
In our library Geming.Sisc.Infrastructure, we have created a helper class, GraphicsManager, which contains only one function, SetQuality().
This function accepts two input parameters, the graphics object and the quality you wish to set to that object. The following is the code for the SetQuality() function.
This writing talks about hard links and soft links; two of the nice features of NTFS file system.
You can divide file links into two categories:
Normal Links (shortcuts)
Symbolic Links:
This divided into two subcategories:
Hard Links (for files)
Soft Links (for directories)
Introducing Hard Links
A hard link is a way to represent a single file (i.e. data volume) by more than one path. In other words, it is a way to create multiple directory entries for a single file.
Consider the following example, we could have two files, e.g. D:DocumentsBills.doc and D:FilesCompanyInvoices.doc, both files represent the same data, so when you change one file the other changes too!
A hard link is almost like a normal link (i.e. shortcut) but there is a big difference. If you delete the source file of a shortcut, the shortcut would be broken (points to a non-existing file.) A hard link on the other hand, would be still working fine if you delete the source file as if it was just a copy.
Notice that, hard disk space is not multiplied for each hard link. Because they are share the same data volume, they share the same size.
There is something that you should be aware of. File attributes are not updated for each of the hard links. This means that if you set a hard link (e.g. D:FilesCompanyInvoices.doc) to read-only, it is the only link that is set to this attribute. Other hard links including the source would be still read-write (unless you set them too.)
Hard links are only supported on NTFS file system; they are not supported by Win9x versions of Windows. Besides this, hard links creation is limited to the same local disk (or network drive) of the source file. For example, you cannot create a hard link on the C: drive for a file on the drive D: or on a network share.
Introducing Soft Links
Soft links (also called junctions,) are identical to hard links except that soft links are designated for directories not files.
For example, if you have one or more soft links to the directory D:Tools (e.g. D:Software and D:ProgramsTools,) all directories (including the source) would be updated when any one of them changes. That is, if you created a new file in one directory, it is created in all directory links, and so on.
Like hard links, soft links require NTFS file system. Plus, deleting the source directory does not affect other directories, all links share the same data volume, changing attributes of one directory does not be applied to others.
Unlike hard links, creating soft links is a special feature of Windows Vista and Windows Server 2008 (and future versions of course.) Therefore, it cannot be applied to earlier versions of Windows (like Windows XP or Windows Server 2003.)
Creating a Hard Link
Unfortunately, neither hard links nor soft links are supported by the .NET Framework. Therefore, you will need to dig into the Windows API to allow your application to consume this feature.
You can create a hard link using a single line of code using a simple call to the Win32 function, CreateHardLink(), that resides in the Kernel32.dll library. The definition of this function is as follows:
lpFileName:
The path of the new hard link to be created. Notice that, it should be resides in the location (local disk or network share) as the source file.
lpExistingFileName:
The path of the source file to create the hard link from.
lpSecurityAttributes:
The security descriptor for the new file. Till now, it is reserved for future use and it should not be used.
This function returns TRUE if succeeded or FALSE otherwise.
Keeping the definition of the function in our minds, we can create its PInvoke method in C#. Simply, this is our C# PInvoke method:
As you know, we have set the character encoding to Unicode because Windows NT allows Unicode file names. In addition, we have marshaled lpSecurityAttributes as System.IntPtr to allow passing it a null value using System.IntPtr.Zero.
Then, you simply call the function:
Change the source file to meet an existing file into your system. In addition, check that the directory of the new link exists or the call would fail.
Take into consideration that the function creates the new file for you, it should not be found or the function would fail (as the case with soft links.)
CreateHardLink(
"D:\Files\Company\Invoices.doc", // New Link
"D:\Documents\Bills.doc", // Source
IntPtr.Zero);
And this is another link:
CreateHardLink(
"D:\Important\Work Invoices.doc", // New Link
"D:\Files\Company\Invoices.doc", // Source
IntPtr.Zero);
As you see, it does not matter if you created the new link from the original file or from another link.
Now try to change any of the links, and check the others. They are all updated at the time you commit the change (save the file, for instance.)
Creating a Soft Link
Like hard links, you can create a soft link (a junction) using a single call to a Win32 API function; it is CreateSymbolicLink() function. Again, this function is only supported by Windows Vista and Windows Server 2008 (and future versions.)
Actually, the function CreateSymbolicLink() can be used to create both types of symbolic links, hard and soft links. The definition of this function is as follows:
The following code creates two soft links for one directory:
Again, the source should be the only directory that exists in your machine or the function would fail.
CreateSymbolicLink(
"D:\Software", // New Link
"D:\Tools", // Source
SYMBOLIC_LINK_FLAG_DIRECTORY);
// Source
CreateSymbolicLink(
"D:\Programs\Tools", // New Link
"D:\Software", // Source
SYMBOLIC_LINK_FLAG_DIRECTORY);
Now try changing one directory, other links change too. Try deleting the source, nothing would be happen to the other links.
This writing will focus on how you can record sound from an input device and how you can play sound files using MCI (Media Control Interface) in C and C#.
This writing does not involve a discussion or even an introduction to MCI. Instead, it provides technical discussion of what we will need to use to record and to play sound files. If you need an introduction to MCI refer to the MSDN documentation.
We will begin by a discussion to types and functions required to accomplish our tasks. Then we will look on how you can utilize those types and functions in your C or C# application.
Our demonstration examples in this writing will be in C. In the last section we will have a look at .NET and C#. Besides this, there are sample applications written by C and C# attached with the article.
Actually, there are many ways to record and play sound files. However, in this writing we will focus on MCI (Media Control Interface) as it is one of the high-level easy-to-use interfaces for controlling all media types.
You can control a multimedia hardware using MCI by either sending commands like Windows messages or by sending string messages to the MCI. The second approach is preferable in scripting languages. As we are concentrating on languages C and C#, we will consider the first approach only.
Because .NET Framework does not include classes for handling MCI (or multimedia at all,) we will need to dig into Windows SDK, specifically WinMM.dll (Windows Multimedia Library) that resides in the system directory.
In order to access this library from your C# application, you will need to create your own marshaling types and PInvoke (Platform Invocation) methods to allow your managed code to communicate with the unmanaged server. That is what the last section of this article is devoted for.
To access this library from your C application, just reference the library object file WinMM.lib in your project. Notice that all of the functions and structures related to the MCI are prefixed with mci.
In the next sections we will talk about the functions and structures that we need to be aware of before digging into coding.
Sending MCI Commands
The key function to Windows multimedia programming using MCI is mciSendCommand(). This function sends a command to the MCI. As we have said, you can program MCI using one of two approaches, you can send Windows-messages-like commands, and you can also send string messages to the MCI. The key function that we will use to send MCI commands is mciSendCommand(). To send string messages, you can use the function mciSendMessage() which is not covered here.
The definition of the function mciSendCommand() is as follows:
IDDevice:
The ID of the device to which to receive the command. For example, the ID of the input device when recording or the output device if playing. As you know, many devices could be connected to the PC. You can forget about this argument and just pass the function 0 to direct the message to the default device (selected in the Sound applet in the Control Panel.)
uMsg:
The message (command) to be sent. Common messages are covered in the next few sections.
fdwCommand:
Flags (options) of the message. Every message has its own options. However, all messages share the flags MCI_WAIT, MCI_NOTIFY, and MCI_TEST (covered soon.)
dwParam:
A structure contains the specific parameter for the command message.
As a result, every command (message) has its name, flags, and structure parameter.
This function returns 0 if succeeded or an error code otherwise.
The Wait, Notify, and Test Flags
Common flags for all MCI messages are, MCI_WAIT, MCI_NOTIFY, and MCI_TEST.
The Wait flag, MCI_WAIT, indicates that the message should be processed synchronously; means that the function would not return until message processing finishes. For example, if you send a play message with the MCI_WAIT, your application would be suspended until the entire file finishes. Therefore, you should not use MCI_WAIT for play or record messages.
The Notify flag, MCI_NOTIFY, is the reverse of the Wait flag. It indicates that the message should be processed asynchronously; means that the function would return immediately and does not wait for the completion of the message. When the processing of the message finishes, it sends a MM_MCINOTIFY message to the windows specified in the dwCallback member of the message parameter. This flag should be used for play and record messages.
The wParam value of the MM_MCINOTIFY message is usually set to either MCI_NOTIFY_SUCCESSFUL to indicate command success or MCI_NOTIFY_FAILURE otherwise.
The Test flag, MCI_TEST, checks if the device can process this message, it does not process the message. The function would return 0 if the device can process the message or non-zero otherwise. You will use this flag in rare cases only.
Keep in mind that you should choose only one of the three flags, you cannot combine them.
If you didn’t specify any of these three flags, the call would be treated asynchronously and you will not be notified when it completes.
Handling MCI Errors
Every MCI command could succeed or fail. If the command succeeded, mciSendCommand() returns 0 (FALSE/false.) Otherwise, it returns the error code.
MCI defines error codes as constants that are prefixed with MCIERR_ like MCIERR_DEVICE_NOT_INSTALLED and MCIERR_FILE_NOT_FOUND (names are self-explanatory.) You can get the friendly error message of the code using the function mciGetErrorString(). The definition of this function is as follows:
To record from an input device using MCI, follow these steps:
Open the input device to receive the data from.
Order the MCI to start the recording process.
When you finish, stop the recording process.
Save the record buffer if applicable.
Close the opened device.
Keep in mind that you should check whether an error occurred or not after sending each command. The previous approach to retrieve error messages would be very useful.
Opening the Device
To open a device just pass the open command MCI_OPEN to the mciSendCommand() function along with its flags and parameter.
The parameter of MCI_OPEN is the structure MCI_OPEN_PARMS. This structure contains information about the open command. The definition of this structure is as following:
Actually, you will make use of only the third and fourth members, lpstrDeviceType and lpstrElementName, of the structure when you open a device. lpstrDeviceType determines the type of the device (digital-audio, digital-video, etc.) that will be used. In our example that records and plays sound files, we will set this member to €œwaveaudio€ to indicate that we are going to work with waveform (WAV) data.
lpstrElementName on the other hand, should be set to an empty string (that is €œ€) if you are opening an input device for recording. If you want to play a file, set this member to the full path of that file.
Common flags of the command MCI_OPEN are:
The Wait, Notify, and Test flags:
The Wait command is usually used for MCI_OPEN.
MCI_OPEN_ELEMENT:
Mandatory. The lpstrDeviceType of the MCI_OPEN_PARMS is set. It is set to €œwaveaudio€ for WAV data.
MCI_OPEN_TYPE:
Mandatory. The lpstrElementType of the MCI_OPEN_PARMS is set. It is set to an empty string if recording or a path to a file if you want to play it.
You will always combine the flags MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE for the MCI_OPEN command.
When the function returns, the wDeviceID member of the structure is set to the ID of the device opened. You should keep this ID for future calls on that device until you close it using the close command.
The following C code shows how you can open an input device for recording:
MCI_OPEN_PARMS parmOpen;
WORD wDeviceID;
parmOpen.dwCallback = 0;
parmOpen.wDeviceID = 0; // the default device
parmOpen.lpstrDeviceType = TEXT("waveaudio");
parmOpen.lpstrElementName = TEXT("");
parmOpen.lpstrAlias = 0;
mciSendCommand(0, // the default device
MCI_OPEN,
MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
(DWORD)&parmOpen);
// Keep the device ID for future calls
wDeviceID = parmOpen.wDeviceID;
Starting Recording
After you have opened the input device, you can order the MCI to start the recording process using the command MCI_RECORD. This command requires an opened input device and a parameter of type MCI_RECORD_PARMS. The definition of this structure is as following:
dwCallback:
The window handle that should be called after the processing of the command finishes if MCI_NOTIFY is specified in command flags.
dwFrom:
Indicates the position of the buffer (in thousands of a second) to start recording from. In most cases this would be set to zero.
dwTo:
Indicates the position of the buffer to stop recording when it is reached. Unless you want to record for a given period, this member should be zero.
Common flags for MCI_RECORD are:
MCI_WAIT, MCI_NOTIFY, and MCI_TEST:
Usually you will use the MCI_NOTIFY flag. If so, you should handle the MM_MCINOTIFY message.
MCI_FROM:
Set if you used the dwFrom member of the parameter.
MCI_TO:
Set if you used the dwTo member of the parameter.
If you want to record for a specific period, set dwTo member of the structure to that specific period (in thousands of seconds) and combine your flags with MCI_TO. When this period ends, MCI automatically stops the recording process and it sends a MM_MCINOTIFY message if you have set MCI_NOTIFY in the flags.
The following C example shows how you can start recording:
MCI_RECORD_PARMS parmRec;
parmRec.dwCallback = 0;
parmRec.dwFrom = 0;
parmRec.dwTo = 0;
// We do not need a notification message
// we will send a Stop command, when
// we finish.
mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD)&parmRec);
The following code shows how you can record for a specific period:
MCI_RECORD_PARMS parmRec;
parmRec.dwCallback = hWnd; // window handle
parmRec.dwFrom = 0;
parmRec.dwTo = 30000; // 30 seconds
// Notify me when you finish the 30 seconds
mciSendCommand(wDeviceID, MCI_RECORD,
MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);
Pausing Recording
To pause the recording process, just pass the MCI_PAUSE command to the MCI. This command accepts a parameter of the structure MCI_GENERIC_PARMS which is defined as following:
This structure contains only one member, dwCallback. As you know, if you specify MCI_NOTIFY in command flags, MCI will send a MM_MCINOTIFY message to the window specified in this member.
MCI_PAUSE does not have specific flags, just the Wait, Notify, and Test flags.
The following C example shows how you can pause the recording process. Notice that you should already be recording or an error would be returned by the mciSendCommand().
To resume after pausing, you can send a MCI_RESUME command to MCI. This command is very similar to the MCI_PAUSE command. It accepts the same parameter and the same flags. The example is the same, just change command name.
Stopping Recording
After you finish recording you will need to stop the recording process. To accomplish this, pass the MCI_STOP command along with the device ID and its parameter to the MCI.
This command is the same as MCI_PAUSE and MCI_RESUME. It accepts the same flags and the same parameters, and the example is identical too, just change the command name.
Retrieving Buffer Length
How long have you been recording? This could be easily answered with the MCI_STATUS command. This command queries MCI and retrieves information about the current session.
MCI_STATUS accepts a structure parameter of type MCI_STATUS_PARMS which is defined as following:
dwReturn:
After returning from the call, it should contains the return value of the query.
dwItem:
The item to query about.
dwTracks:
Length or number of tracks (specific to some query items.)
Common flags that MCI_STATUS accepts:
The Wait, Notify, and Test flags:
You will usually use the Wait flag.
MCI_STATUS_ITEM:
Mandatory in most cases. Indicates that the dwItem of the structure is set.
If you want to query MCI about specific information, pass the MCI_STATUS command to the MCI along with MCI_STATUS_ITEM and MCI_WAIT flags, and set the dwItem field of the parameter to one of the following values (some are for output devices):
MCI_STATUS_LENGTH:
Retrieves the total length of the buffer (in thousands of a second.)
MCI_STATUS_MODE:
Retrieves current mode of the device which can be one of the following values (names are self-explanatory):
MCI_MODE_NOT_READY
MCI_MODE_PAUSE
MCI_MODE_PLAY
MCI_MODE_STOP
MCI_MODE_RECORD
MCI_MODE_SEEK
MCI_STATUS_NUMBER_OF_TRACKS:
Retrieves the total number of tracks.
MCI_STATUS_POSITION:
Retrieves current position (in thousands of a second.)
MCI_STATUS_READY:
Returns TRUE (true in C#) if the device is ready or FALSE (false in C#) otherwise.
When the function returns, the dwReturn field of the structure should contain the result of the query item selected.
The following C example retrieves the length of the buffer recorded:
MCI_STATUS_PARMS parmStatus;
parmStatus.dwCallback = 0;
parmStatus.dwReturn = 0;
parmStatus.dwItem = MCI_STATUS_LENGTH;
parmStatus.dwTrack = 0;
mciSendCommand(0, MCI_STATUS,
MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);
// Display the number of seconds
// parmStatus.dwReturn / 1000
Saving the Recorded Buffer
Before you close the device, you can save the current recorded buffer in an audio (waveform) file. The command MCI_SAVE orders MCI to save the buffer to the file specified in its structure parameter.
The parameter of MCI_SAVE is a MCI_SAVE_PARMS structure and it is defined as following:
This structure contains only two members, dwCallback (discussed before) and lpfilename. lpfilename points to a string buffer contains the name and full path of the file to save.
MCI_SAVE accepts few flags:
The Wait, Notify, and Test flags:
You will always use the Wait (MCI_WAIT) flag.
MCI_SAVE_FILE:
Mandatory. You will always set this flag. It indicates that lpfilename contains the path of the target file.
The following example saves the recorded buffer to the file €œrecording.wav€:
MCI_SAVE_PARMS parmSave;
parmSave.dwCallback = 0;
parmSave.lpfilename = TEXT("recording.wav");
// save to the current directory
mciSendCommand(wDeviceID, MCI_SAVE,
MCI_WAIT | MCI_SAVE_FILE, (DWORD)&parmSave);
Closing the Device
You strictly should always close the device when you finish working with it, and this is done through the MCI_CLOSE command which accepts a parameter of type MCI_GENERIC_PARMS (discussed before.)
MCI_CLOSE accepts only the Wait, Notify, and Test flags.
Keeping in mind what you have learnt from the previous section would be very helpful in our discussion on how to play sound files using MCI.
Actually, the same rules and commands applied in the previous section, will be applied here.
To play a sound file on MCI, you should follow these steps:
Load the audio file. This automatically opens the output device.
Order the MCI to start the playing process.
Close the opened device when you finish.
Again, you should check whether an error occurred or not after each call to mciSendCommand().
Loading the File
As you know, to open a multimedia device, you pass MCI the MCI_OPEN command. To specify a file to be loaded, specify the full path of the file in the lpstrElementName field of the MCI_OPEN_PARMS structure.
The following C example shows how you can open an output device and load a file for playing:
MCI_OPEN_PARMS parmOpen;
WORD wDeviceID;
parmOpen.dwCallback = 0;
parmOpen.wDeviceID = 0; // the default device
parmOpen.lpstrDeviceType = TEXT("waveaudio");
parmOpen.lpstrElementName = TEXT("recording.wav");
parmOpen.lpstrAlias = 0;
mciSendCommand(0, // the default device
MCI_OPEN,
MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
(DWORD)&parmOpen);
// Keep the device ID for future calls
wDeviceID = parmOpen.wDeviceID;
Playing the File
To start playing the currently loaded file, you can use the MCI_PLAY command. This command accepts a structure parameter of type MCI_PLAY_PARMS which is defined as following:
At first sight, you notice that it is identical to the MCI_RECORD_PARMS. Actually, you are right. This structure defines the same members as the MCI_RECORD_PARMS structure. In addition, the description of the members is the same.
dwFrom specifies the position (in thousands of a second) where to start playing. dwTo on the other hand specifies the position (in thousands of a second too) where to end playing. If you set dwFrom, you will need to set the flag MCI_FROM. Conversely, if you set dwTo, you will need to set the flag MCI_TO. If you need to play the file from the start to the end, leave both members and do not specify either MCI_FROM or MCI_TO.
You will most likely combine MCI_PLAY flags with the Notify flag, MCI_NOTIFY, to allow the code to continue execution while the file plays. If you did so, your application would receive MM_MCINOTIFY message to the window specified in the dwCallback member of the parameter structure.
The following C example shows how to start playing a file:
MCI_PLAY_PARMS parmPlay;
// Play the file
// from the start to the end
parmRec.dwCallback = 0;
parmRec.dwFrom = 0;
parmRec.dwTo = 0;
// notify me when you finish
mciSendCommand(wDeviceID, MCI_PLAY,
MCI_NOTIFY, (DWORD)&parmRec);
And the following code plays only three minutes from the file:
MCI_PLAY_PARMS parmPlay;
// play only 3 minutes
parmRec.dwCallback = 0;
parmRec.dwFrom = 0;
// 3 * 60 * 1000
parmRec.dwTo = 180000;
// notify me when you finish
mciSendCommand(wDeviceID, MCI_PLAY,
MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);
Pausing
As you know, you can pause the playback using the MCI_PAUSE command which accepts the MCI_GENERIC_PARMS parameter.
Resuming
To resume the playback after pausing, you can use the MCI_RESUME command discussed before.
Retrieving Current Position
To retrieve the current position of the file, pass the MCI_STATUS command along with its flags (MCI_WAIT and MCI_STATUS_ITEM) and its parameter (MCI_STATUS_PARMS) to the MCI. Do not forget to set dwItem member of the structure to MCI_STATUS_POSITION to retrieve the current position (in thousands of a second) in the dwReturn member of the structure. The following C example demonstrates this:
MCI_STATUS_PARMS parmStatus;
parmStatus.dwCallback = 0;
parmStatus.dwReturn = 0;
parmStatus.dwItem = MCI_STATUS_POSITION;
parmStatus.dwTrack = 0;
mciSendCommand(wDeviceID, MCI_STATUS,
MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);
// Display the current position
// parmStatus.dwReturn / 1000
Retrieving File Length
Like retrieving current position, you can retrieve full file length the same way. However, you will need to specify the item MCI_STATUS_LENGTH instead.
Seeking a Specific Position
To change the current position, pass MCI the command MCI_SEEK. This command accepts the structure parameter MCI_SEEK_PARMS which is defined as following:
You should close the device as soon as you finish working with it, and this is done (as you know) through the MCI_CLOSE command.
In a Nutshell
The following table summarizes the commands that you will usually use with audio files along with their parameter structures and flags.
Command
Input/Output
Parameter Structure
Commonly Used Flags
MCI_OPEN
In/Out
MCI_OPEN_PARMS
MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE
MCI_RECORD
In
MCI_RECORD_PARMS
(none) or MCI_NOTIFY
MCI_PLAY
Out
MCI_PLAY_PARMS
MCI_NOTIFY
MCI_PAUSE
In/Out
MCI_GENERIC_PARMS
MCI_WAIT
MCI_RESUME
In/Out
MCI_GENERIC_PARMS
MCI_WAIT
MCI_STOP
In/Out
MCI_GENERIC_PARMS
MCI_WAIT
MCI_SEEK
Out
MCI_SEEK_PARMS
MCI_WAIT and MCI_TO / MCI_SEEK_TO_START / MCI_SEEK_TO_END
MCI_SAVE
In
MCI_SAVE_PARMS
MCI_WAIT and MCI_SAVE_FILE
MCI_STATUS
In/Out
MCI_STATUS_PARMS
MCI_WAIT and MCI_STATUS_ITEM
MCI_CLOSE
In/Out
MCI_GENERIC_PARMS
MCI_WAIT
MCI and .NET
Creating the Managed Signature
Because .NET does not support MCI and it does not allow you to call unmanaged code directly, you will need to create your own marshaling types and PInvoke methods.
Keep in mind that you can get the handle of a window by using Control.Handle property.
The following class is the managed signature of our unmanaged structures and functions along with the required constants:
internal static class SafeNativeMethods
{
// Constants
public const string WaveAudio = "waveaudio";
public const uint MM_MCINOTIFY = 0x3B9;
public const uint MCI_NOTIFY_SUCCESSFUL = 0x0001;
public const uint MCI_NOTIFY_SUPERSEDED = 0x0002;
public const uint MCI_NOTIFY_ABORTED = 0x0004;
public const uint MCI_NOTIFY_FAILURE = 0x0008;
public const uint MCI_OPEN = 0x0803;
public const uint MCI_CLOSE = 0x0804;
public const uint MCI_PLAY = 0x0806;
public const uint MCI_SEEK = 0x0807;
public const uint MCI_STOP = 0x0808;
public const uint MCI_PAUSE = 0x0809;
public const uint MCI_RECORD = 0x080F;
public const uint MCI_RESUME = 0x0855;
public const uint MCI_SAVE = 0x0813;
public const uint MCI_LOAD = 0x0850;
public const uint MCI_STATUS = 0x0814;
public const uint MCI_SAVE_FILE = 0x00000100;
public const uint MCI_OPEN_ELEMENT = 0x00000200;
public const uint MCI_OPEN_TYPE = 0x00002000;
public const uint MCI_LOAD_FILE = 0x00000100;
public const uint MCI_STATUS_POSITION = 0x00000002;
public const uint MCI_STATUS_LENGTH = 0x00000001;
public const uint MCI_STATUS_ITEM = 0x00000100;
public const uint MCI_NOTIFY = 0x00000001;
public const uint MCI_WAIT = 0x00000002;
public const uint MCI_FROM = 0x00000004;
public const uint MCI_TO = 0x00000008;
// Structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_OPEN_PARMS
{
public IntPtr dwCallback;
public uint wDeviceID;
public IntPtr lpstrDeviceType;
public IntPtr lpstrElementName;
public IntPtr lpstrAlias;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_RECORD_PARMS
{
public IntPtr dwCallback;
public uint dwFrom;
public uint dwTo;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_PLAY_PARMS
{
public IntPtr dwCallback;
public uint dwFrom;
public uint dwTo;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_GENERIC_PARMS
{
public IntPtr dwCallback;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_SEEK_PARMS
{
public IntPtr dwCallback;
public uint dwTo;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_SAVE_PARMS
{
public IntPtr dwCallback;
public IntPtr lpfilename;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_STATUS_PARMS
{
public IntPtr dwCallback;
public uint dwReturn;
public uint dwItem;
public uint dwTrack;
} ;
// Functions
[DllImport("winmm.dll", CharSet = CharSet.Ansi,
BestFitMapping = true, ThrowOnUnmappableChar = true)]
[return: MarshalAs(UnmanagedType.U4)]
public static extern uint mciSendCommand(
uint mciId,
uint uMsg,
uint dwParam1,
IntPtr dwParam2);
[DllImport("winmm.dll", CharSet = CharSet.Ansi,
BestFitMapping = true, ThrowOnUnmappableChar = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool mciGetErrorString(
uint mcierr,
[MarshalAs(UnmanagedType.LPStr)]
System.Text.StringBuilder pszText,
uint cchText);
}
Receiving MCI_MMNOTIFY
In C, you can handle the MCI_MMNOTIFY message in the way you handle any other message. Just add the handler to the window procedure (WndProc) function.
In .NET, you do not have a WndProc function. However, .NET emulates this function using the protected Control.WndProc() function. You can override this function in your form and do the required processing. The following example demonstrates this:
public partial class MainForm : Form
{
. . .
protected override void WndProc(ref Message m)
{
if (m.Msg == SafeNativeMethods.MM_MCINOTIFY)
{
// Handle the message
}
// DO NOT REMOVE the following line
base.WndProc(ref m);
}
}
Appendix A: Setting Device Information
After we received your feedbacks and comments about the article, we decided to add a small appendix to the end of the article about setting information (volume, channel, sampling rate, etc.) to a MCI device (a Waveform device of course.)
Like anything else in MCI, you can set device information using a MCI command (string/numeric), and this time it’s the MCI_SET command.
This command is used to set information about a specific device. This command requires an input parameter of the MCI_SET_PARMS structure. However, that input parameter might have extended members for specific devices. Because we are concentrating of Waveform devices, so we are going to use the MCI_WAVE_SET_PARMS structure that contains the extended members for our device and is defined as following:
typedef struct {
DWORD_PTR dwCallback;
DWORD dwTimeFormat;
DWORD dwAudio;
UINT wInput;
UINT wOutput;
WORD wFormatTag;
WORD wReserved2;
WORD nChannels;
WORD wReserved3;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wReserved4;
WORD wBitsPerSample;
WORD wReserved5;
} MCI_WAVE_SET_PARMS;
This structure contains all and every little piece of information that can be set to a device. I expect that you read the main article and you are already familiar with members like dwCallback (other members are self-explanatory) that we have talked about many times, and you are fine too with function calls and setting up input parameters, so I won’t get into the discussion of the structure or how you are going to use that command. However, if you need more help setting up the input parameters for the structure, you should take a brief look at the MCI_WAVE_SET_PARMS Structure documentation in the MSDN.
As you know, the MCI_WAVE_SET_PARMS unmanaged structure can be marshaled in C# as following:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_WAVE_SET_PARMS
{
public IntPtr dwCallback;
public uint dwTimeFormat;
public uint dwAudio;
public uint wInput;
public uint wOutput;
public ushort wFormatTag;
public ushort wReserved2;
public ushort nChannels;
public ushort wReserved3;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wReserved4;
public ushort wBitsPerSample;
public ushort wReserved5;
}
Congratulations! You did set the device information! So how to get them back?
This can be done through the MCI_STATUS (discussed earlier) by setting up the MCI_STATUS_ITEM flag and setting the query item to the required information you need to query about (like MCI_DGV_STATUS_VOLUME to query about volume.)
More about the MCI_STATUS command can be found in the MSDN documentation.
Sample Code
Attached with this writing the sample applications, SampleRec (C#) and SMPREC (C).
This chapter demonstrates how to marshal compound types. Compound types are those build of other types, for example structures and classes.
Like the previous chapter. This chapter breaks unmanaged compound types into two categories, structures and unions. We first discuss structures and then we will dive into unions and how to marshal them.
You might ask, why you have divided compound types into just two categories, structures and unions, I can create classes too? The answer is easy. For its simplicity, this book will focus primarily on Windows API. Therefore, you will find much of our talking about Win32 functions and structures. However, the same rules apply to classes and other unmanaged types.
Introduction
A compound type encapsulates related data together; it provides an organized and arranged container for transmitting a group of variables between the client application and the unmanaged server. It consists (usually) of variables of simple types and (optionally) other compound types. In addition, it could define other compound types inside.
Compound types come in two kinds:
Unmanaged Structures
Unmanaged Unions
An example of a structure is OSVERSIONINFOEX structure that encapsulates operating system version information together. For those who are somewhat familiar with DirectX, they may find that DirectX API relies heavily on structures.
As you know, because there is no compatibility between .NET and unmanaged code, data must undergo some conversion routines for transmitting from the managed code to the unmanaged server and vice versa, and compound types are no exception.
In the next section, we will focus of the first kind, structures.
Marshaling Unmanaged Structures
How to Marshal a Structure
Unmanaged structures can be marshaled as managed structures or even classes. Choosing between a managed structure and a class is up to you, there are no rules to follow. However, when marshaling as managed classes, there are some limitations with the passing mechanism as we will see later in this chapter.
When marshaling structures in the managed environment, you must take into consideration that while you access a variable into your by its name, Windows accesses it via its address (i.e. position) inside the memory, it does not care about field name, but it cares about its location and size. Therefore, the memory layout and size of the type are very crucial.
You can marshal an unmanaged structure in few steps:
Create the marshaling type either a managed structure or a class.
Add the type fields (variables) only. Again, layout and size of the type are very crucial. Therefore, fields must be ordered as they are defined, so that the Windows can access them correctly.
Decorate your type with StructLayoutAttribute attribute specifying the memory layout kind.
Handling Memory Layout Problem
When marshaling an unmanaged structure, you must take care of how that type is laid-out into memory.
Actually, application memory is divided into blocks (in a 4-bytes base,) and every block has its own address. When you declare a variable or a type in your program it is stored inside the memory and got its memory address. Consequently, all data members inside a structure have their own addresses that are relative to the address of the beginning of the structure.
Consider the following structures:
Listing 3.1 SMALL_RECT and COORD Unmanaged Signature
typedef struct SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
};
typedef struct COORD {
SHORT X;
SHORT Y;
};
When we declare those structures in our code they are laid-out into memory and got addresses like that:
Figure 3.1 How Memory is Laid-Out
Thus, you should keep in mind that the size and location of each of type members is very crucial and you strictly should take care of how this type is laid-out into the memory.
For now, you do not have to think about the last illustration. We will cover memory management in details in chapter 6.
For handling the memory layout problem, you must apply the StructLayoutAttribute attribute to your marshaling type specifying the layout kind using the LayoutKind property.
This property can take one of three values:
LayoutKind.Auto (Default):
Lets the CLR chooses how the type is laid-out into memory. Setting this value prevents interoperation with this type, that means that you will not be able to marshal the unmanaged structure with this type, and if you tried, an exception will be thrown.
LayoutKind.Sequential:
Variables of the type are laid-out sequentially. When setting this value ensure that all variables are on the right order as they are defined in the unmanaged structure.
LayoutKind.Explicit:
Lets you control precisely each variable’s location inside the type. When setting this value, you must apply the FieldOffsetAttribute attribute to every variable in your type specifying the relative position in bytes of the variable to the start of the type. Note that when setting this value, order of variables becomes unimportant.
For the sake of simplicity, you should lay-out all of your types sequentially. However, when working with unions, you are required to explicitly control every variable’s location. Unions are covered in the next section.
We have said that you should add only the type members into the marshaling type, however, this is not always true. In structures where there is a member that you can set to determine the structure size (like the OPENFILENAME structure,) you can add your own members to the end of the structure. However, you should set the size member to the size of the entire structure minus the new members that you have added. This technique is discussed in details in chapter 6.
Try It Out!
The following example demonstrates how to marshal the famous structures SMALL_RECT and COORD. Both used earlier with the ScrollConsoleScreenBuffer() function in the last chapter. You can check code listing 3.1 earlier in this chapter for the definition of the structures.
Next is the managed signature for both the structures. Note that you can marshal them as managed classes too.
Listing 3.2 SMALL_RECT and COORD Managed Signature
// Laying-out the structure sequentially
[StructLayout(LayoutKind.Sequential)]
//public class SMALL_RECT
public struct SMALL_RECT
{
// Because we are laying the structure sequentially,
// we preserve field order as they are defined.
public UInt16 Left;
public UInt16 Top;
public UInt16 Right;
public UInt16 Bottom;
}
// The same as SMALL_RECT applies to COORD
[StructLayout(LayoutKind.Sequential)]
//public struct COORD
public struct COORD
{
public UInt16 X;
public UInt16 Y;
}
Marshaling Unions
A Short Speech About Unions
A union is a memory location that is shared by two or more different types of variables. A union provides a way for interpreting the same bit pattern in two or more different ways (or forms.)
In fact, unions share structures lots of characteristics, like the way they defined and marshaled. It might be helpful to know that, like structures, unions can be defined inside a structure or even as a single entity. In addition, unions can define compound types inside, like structures too.
To understand unions, we will take a simple example. Consider the following union:
Listing 3.3 SOME_CHARACTER Unmanaged Signature
typedef union SOME_CHARACTER {
int i;
char c;
};
This was a simple union defines a character. It declared two members, i and c, it defined them in the same memory location. Thus, it provides two ways for accessing the character, by its code (int) and by its value (char). For this to work it allocates enough memory storage for holding the largest member of the union and that member is called container. Other members will overlap with the container. In our case, the container is i because it is 4 bytes (on Win32, 16 on Win16), while c is only 1 byte. Figure 3.2 shows how the memory is allocated for the union.
Figure 3.2 SOME_CHARACTER Union
Because the two members are sharing the same memory location, when you change one member the other is changed too. Consider the following C example:
Listing 3.4 Unions Example 1
int main()
{
union CHARACTER ch;
ch.i = 65; // 65 for A
printf("c = %c", ch.c); // prints 'A'
printf("n");
ch.c += 32; // 97 for a
printf("i = %d", ch.i); // prints '97'
printf("n");
return 0;
}
When you change any of the members of the union, other members change too because they are all share the same memory address.
Now consider the same example but with values that won’t fit into the char member:
Listing 3.5 Unions Example 2
int main()
{
union CHARACTER ch;
ch.i = 330;
printf("c = %c", ch.c); // prints 'J'
printf("n"); // Ops!
ch.c += 32;
printf("i = %d", ch.i); // prints '362'
printf("n");
return 0;
}
What happened? Because char is 1 bye wide, it interprets only the first 8 bits of the union that are equal to 32.
The same rule applies if you add another member to the union. See the following example. Notice that order of member declarations doesn’t matter.
Listing 3.6 Unions Example 3
int main()
{
union {
int i;
char c;
short n;
} ch;
ch.i = 2774186;
printf("i = %d", ch.i);
printf("n");
printf("c = %i", (unsigned char)ch.c);
printf("n");
printf("n = %d", ch.n);
printf("n");
return 0;
}
Now, member i, the container, interprets the 32 bits. Member c, interprets the first 8 bits (notice that we converted it to unsigned char to not to show the negative value.) Member n, interprets the first high word (16 bits.)
You might ask: Why I need unions at all? I could easily use the cast operator to convert between data types!
The answer is very easy. Unions come very efficient when casting between types require much overhead. Consider the following example: You are about to write an integer to a file. Unfortunately, there are no functions in the C standard library that allow you to write an int to a file, and using fwrite() function requires excessive overhead. The perfect solution is to define a union that contains an integer and a character array to allow it to be interpreted as an integer and as a character array when you need to pass it to fwrite() for example. See the following code snippet:
Listing 3.7 Unions Example 4
typedef union myval{
int i;
char str[4];
};
In addition, unions offer you more performance than casts. Moreover, your code will be more readable and efficient when you use unions.
More on how unions are laid-out into memory in chapter 6.
How to Marshal a Union
You can marshal a union the same way as you marshal structures, except that because of the way that unions laid-out into memory, you will need to explicitly set variable positions inside the type.
Follow these steps to marshal a union:
Create your marshaling type, no matter whether your marshaling type is a managed structure or class. Again, classes require special handling when passed as function arguments. Passing mechanism is covered soon.
Decorate the type with the StructLayoutAttribute attribute specifying LayoutKind.Explicit for the explicit layout kind.
Add the type fields. Do not add fields other than those defined in the unmanaged signature. Because we are controlling the type layout explicitly, order of fields is not important.
Decorate every field with the FieldOffsetAttribute attribute specifying the absolute position in bytes of the member from the start of the type.
The following example demonstrates how to marshal our SOME_CHARACTER union.
Listing 3.8 SOME_CHARACTER Managed Signature
// Unions require explicit memory layout
[StructLayout(LayoutKind.Explicit)]
//public class SOME_CHARACTER
public struct SOME_CHARACTER
{
// Both members located on the same
// position in the beginning of the union
// This is the continer it is 4 bytes
[FieldOffset(0)]
[MarshalAs(UnmanagedType.U4)]
public int i;
// This is only 1 byte. Therefore, it is contained
[FieldOffset(0)]
public char c;
}
public static void Main()
{
SOME_CHARACTER character = new SOME_CHARACTER();
// The code for letter 'A'
character.i = 65;
// Should prints 'A'
Console.WriteLine("c = {0}", character.c);
character.c = 'B';
// Should prints 66
Console.WriteLine("i = {0}", character.i);
}
From the last code, we learn that…
Unions are marshaled like structures, they can be marshaled as either managed structures or classes.
Setting StructLayoutAttribute.LayoutKind to LayoutKind.Explicit allows us to exactly control the memory location of our members.
We use the FieldOffsetAttribute to specify the starting location in bytes of the field into the type in memory.
To create the union between the fields, we set both the fields to the same memory location.
In the example, member i occupies byte 0 through byte 4, and member c occupies byte 0 through byte 1.
If we do not need the benefits of unions, we can omit member c because it is contained inside the range of member i. However, we cannot omit member c because it is the container.
When we change either one of the union variables, the other variable changes too because they share the same memory address.
Unions with Arrays
Another example of a union is as following:
Listing 3.9 UNION_WITH_ARRAY Unmanaged Signature
typedef union UNION_WITH_ARRAY
{
INT number;
CHAR charArray[128];
};
This union must be marshaled in a special way because managed code does not permit value types and reference types to overlap.
As a refresher, a value-type is the type stored in the memory stack; it inherits from System.ValueType. All primitive data types, structures, and enumerations are considered value-types. On the other hand, reference-types are those types stored in the memory heap; they inherit from System.Object. Most types in .NET are reference-types (except System.ValueType and its descendents of course.)
That is, all value-types inherit -directly or indirectly- from System.ValueType.
As a result, we cannot union both members of our example, because whether marshaling the second variable charArray as an array, a System.String, or as a System.Text.StringBuilder, it is still a reference-type. Therefore, we have to leave the benefits of unions and marshal only a single member. For our example, we will create two marshaling types for our union, one with the first member marshaled, and the other with the other member.
As we know, the layout and size of the type inside the memory is the most crucial. Therefore, we must preserve the layout and size of our union. This union has a 128 bytes array as a container and only one member contained, and this member is only 2-bytes. Therefore, we have two choices, to marshal the union with the container member, or to marshal it with the contained member but to extend it enough to be as large as the container. In this example, we will take the two approaches.
Try It Out!
The following are two code segments. The first demonstrates how to marshal only the second member which is the container, while the second demonstrates how to marshal the first member.
Listing 3.10 UNION_WITH_ARRAY Union Managed Signature
// Setting StructLayoutAttribute.CharSet
// ensures the correct encoding for all
// string members of the union in our example
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
//public struct UNION_WITH_ARRAY_1
public struct UNION_WITH_ARRAY_1
{
// As we know, character arrays can be marshaled
// as either an array or as a string
// Setting MarshalAsAttribute is required
// for the array and the string
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
//public char[] charArray;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string charArray;
}
// StructLayoutAttribute.Size determines
// the size -in bytes- of the type.
// If the size specified is larger than
// members' size, the last member will be extended
// Because this is only a single
// member, we laid it out sequentially.
[StructLayout(LayoutKind.Sequential, Size = 128)]
//public class UNION_WITH_ARRAY_2
public struct UNION_WITH_ARRAY_2
{
[MarshalAs(UnmanagedType.I2)]
public short number;
}
For more information about marshaling arrays, refer to the next chapter.
Value-Types and Reference-Types
In the realm of .NET, types are broken into two categories:
Value-Types:
These types are stored in the memory stack. They are destroyed when their scope ends, therefore, they are short-lived. Types of this category are all types inherit from System.ValueType (like all primitive data types, structures, and enumerations.)
Reference-Types:
These types are stored in the memory heap. They are controlled by the Garbage Collector (GC,) therefore, they may retain in memory for a long while. Reference-types are all types -directly or indirectly- inherit from System.Object (except System.ValueType and descendants of course.) All .NET classes fall in this category.
Stack and heap! Confused? Check chapter 6 for more details.
Talking about value-types and reference-types leads us to talk about the passing mechanism. And that is what the next section is devoted for.
Passing Mechanism
In the last chapter, we have talked about the passing mechanism with simple types and how it affects the call. Actually, all we have learnt is applied to the compound types too.
As a refresher, when a type passed by value, a copy of type passed to the function, not the value itself. Therefore, any changes to the type inside the function do not affect the original copy. On the other hand, passing a type by reference passes a pointer to the value to the function. In other words, the value itself is passed. Therefore, any changes to the type inside the function are seen by the caller.
Functions require the type passed to be passed either by value or by reference. Plus, they require the argument to be passed by reference only if the argument will be changed inside.
Moreover, an argument passed by reference can be passed either as Input/Output (In/Out) or Output (Out). In/Out arguments used by the function for receiving the input from the caller and posting the changes back to him. Therefore, In/Out arguments must be initialized before handing them to the function. On the other hand, output (Out) arguments are only used for returning output to the caller. Therefore, they do not require pre-initialization because the function will initialize them.
All of the information learnt from the last chapter is applied to this chapter too.
Compound types also can be passed by value or by reference. When passing by value, no changes need to be applied. On the other hand passing a type by reference requires some changes to the PInvoke method and the call itself.
If you are marshaling as a structure, you may add the ref modifier to the parameter. However, classes are -by default- reference-types. Thus, they are normally passed by reference and they cannot be passed by value. Therefore, they do not need the ref modifier.
On the other hand, if you are passing the type as output (Out,) you will need to add the out modifier whether it is a structure or a class.
As you know, you can decorate In/Out arguments with both InAttribute and OutAttribute attributes. For Out arguments, specify OutAttribute attribute only.
Notice that there is a big difference between managed and unmanaged classes. Unmanaged classes are -by default- value-types. Manager classes are reference-types.
The following example demonstrates the PInvoke method for the function GetVersionEx(). This function requires a single In/Out argument. That argument is of the type OSVERSIONINFO.
The function uses OSVERSIONINFO’s dwOSVersionInfoSize field as input from the caller for determining the type size, and it uses the remaining arguments as output for sending the version information back. Therefore, the function requires the argument to be passed by reference as In/Out.
Next is the definition of the function along with the structure:
In addition, this is the managed version with the text code:
Listing 3.12 Retrieving System Version Information Sample
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetVersionEx
([param: In, Out]
// If a class remove the "ref" keyword
ref OSVERSIONINFO lpVersionInfo);
[StructLayout(LayoutKind.Sequential)]
//public class OSVERSIONINFO
public struct OSVERSIONINFO
{
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwOSVersionInfoSize;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwMajorVersion;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwMinorVersion;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwBuildNumber;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwPlatformId;
// Can be marshaled as an array too
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
}
static void Main()
{
OSVERSIONINFO info = new OSVERSIONINFO();
info.dwOSVersionInfoSize = (uint)Marshal.SizeOf(info);
//GetVersionEx(info);
GetVersionEx(ref info);
Console.WriteLine("System Version: {0}.{1}",
info.dwMajorVersion, info.dwMinorVersion);
}
More about the passing mechanism in chapter 6.
Compound Types and Character Encoding
As you know, the size and layout of the marshaling type is the most important. If the compound type contains a textual data, sure special handling should be taken to ensure correct marshaling of the data.
You already know that the character encoding can be either ANSI or Unicode.
When a string is ANSI-encoded, every character reserves only a single byte of application memory. On the other hand, every character in a Unicode-encoded string reserves two bytes of the memory. Therefore, a string like €œC-Sharp€ with 7 characters reserves 7 bytes if ANSI-encoded and 14 bytes if Unicode-encoded.
You can determine the character encoding of the compound type by specifying the CharSet property of the StructLayoutAttribute attribute. This property can take one of several values:
CharSet.Auto (CLR Default):
Strings encoding varies based on operating system; it is Unicode-encoded on Windows NT and ANSI-encoded on other versions of Windows.
CharSet.Ansi (C# Default):
Strings are always 8-bit ANSI-encoded.
CharSet.Unicode:
Strings are always 16-bit Unicode-encoded.
CharSet.None:
Obsolete. Has the same behavior as CharSet.Ansi.
Take into consideration that if you have not set the CharSet property, CLR automatically sets it to CharSet.Auto. However, some languages override the default behavior. For example, C# defaults to CharSet.Ansi.
In addition, you can determine the character encoding at a granular level by specifying the CharSet property of the MarshalAsAttribute attribute applied to the member.
Real-World Examples
The DEVMODE Structure
Now, we are going to dig into real-world examples. In the first example, we are going to marshal one of the most complex compound structures in the Windows API, it is the DEVMODE structure.
If you have worked with GDI, you will be somewhat familiar with this structure. It encapsulates information about initialization and environment of a printer or a display device. It is required by many functions like EnumDisplaySettings(), ChangeDisplaySettings() and OpenPrinter().
The complexity of this structure comes because of few factors. Firstly, there are unions defined inside the structure. In addition, the definition of this structure defers from a platform to another. As we will see, the structure defines some members based on the operating system.
Here is the definition of DEVMODE structure along with the POINTL structure that is referenced by DEVMODE.
Listing 3.13 DEVMODE and POINTL Unmanaged Signature
typedef struct DEVMODE {
BCHAR dmDeviceName[CCHDEVICENAME];
WORD dmSpecVersion;
WORD dmDriverVersion;
WORD dmSize;
WORD dmDriverExtra;
DWORD dmFields;
union {
struct {
short dmOrientation;
short dmPaperSize;
short dmPaperLength;
short dmPaperWidth;
short dmScale;
short dmCopies;
short dmDefaultSource;
short dmPrintQuality;
};
POINTL dmPosition;
DWORD dmDisplayOrientation;
DWORD dmDisplayFixedOutput;
};
short dmColor;
short dmDuplex;
short dmYResolution;
short dmTTOption;
short dmCollate;
BYTE dmFormName[CCHFORMNAME];
WORD dmLogPixels;
DWORD dmBitsPerPel;
DWORD dmPelsWidth;
DWORD dmPelsHeight;
union {
DWORD dmDisplayFlags;
DWORD dmNup;
}
DWORD dmDisplayFrequency;
#if(WINVER >;= 0x0400)
DWORD dmICMMethod;
DWORD dmICMIntent;
DWORD dmMediaType;
DWORD dmDitherType;
DWORD dmReserved1;
DWORD dmReserved2;
#if (WINVER >;= 0x0500) || (_WIN32_WINNT >;= 0x0400)
DWORD dmPanningWidth;
DWORD dmPanningHeight;
#endif
#endif /* WINVER >;= 0x0400 */
};
typedef struct POINTL {
LONG x;
LONG y;
};
You might have noticed that two unions are defined inside the structure. In addition, a structure is defined inside the first union! Moreover, the last 8 members are not supported in Windows NT. Plus, the very last two members, dmPanningWidth and dmPanningHeight, are not supported in Windows 9x (95/98/ME.)
When working with Windows API, you should take care of operating system compatibility. Some functions, for instance, are not supported on certain operating systems (e.g. most Unicode versions are not supported on Win9x.) Other functions take arguments that vary based on the OS (i.e. EnumPrinters() function.) If your application tried to call a function, for instance, that is not supported by the current operating system, the call would fail.
If you need your application to be portable to every platform, you will need to create three versions of the structure, one for Windows ME and its ascendants, one for Windows NT, and the last for Windows 2000 and higher versions. In addition, you will need to create three overloads of every function require DEVMODE structure; three overloads for the three structures. For the sake of simplicity, we will assume that you are working with Windows 2000 or a higher version. Thus, we will marshal all members of the structure.
The following is the managed version of both DEVMODE and POINTL structures:
Listing 3.14 DEVMODE and POINTL Managed Signature
// Setting StructLayout.LayoutKind to LeyoutKind.Explicit to allow
// precisely choosing of member position. It is required for unions
// This structure is 156-bytes
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
//public class DEVMODE
public struct DEVMODE
{
// You can define the following constant
// BUT OUTSIDE THE STRUCTURE
// because you know that size and layout of the structure
// is very important
// CCHDEVICENAME = 32 = 0x50
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public Char[] dmDeviceName;
// In addition you can define the last character array
// as following:
//MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string dmDeviceName;
// After the 32-bytes array
[FieldOffset(32)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSpecVersion;
[FieldOffset(34)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverVersion;
[FieldOffset(36)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSize;
[FieldOffset(38)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverExtra;
[FieldOffset(40)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmFields;
// ************ Union Start ************
// Because DEVMODE_PRINT_SETTINGS is the hugest member and it is
// 16-bytes, it is the container for other members
// Remeber, you cannot emit the container
[FieldOffset(44)]
public DEVMODE_PRINT_SETTINGS dmSettings;
// Positioned within DEVMODE_PRINT_SETTINGS
// It is 8-bytes only
[FieldOffset(44)]
public POINTL dmPosition;
// Positioned within DEVMODE_PRINT_SETTINGS
[FieldOffset(44)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayOrientation;
// Positioned within DEVMODE_PRINT_SETTINGS
[FieldOffset(44)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFixedOutput;
// ************* Union End *************
// Because DEVMODE_PRINT_SETTINGS structure
// is 16-bytes, dmColor is positioned on byte 60
[FieldOffset(60)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmColor;
[FieldOffset(62)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmDuplex;
[FieldOffset(64)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmYResolution;
[FieldOffset(66)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmTTOption;
[FieldOffset(70)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmCollate;
// CCHDEVICENAME = 32 = 0x50
[FieldOffset(72)]
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 32,
ArraySubType = UnmanagedType.U1)]
public Byte[] dmFormName;
// After the 32-bytes array
[FieldOffset(102)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmLogPixels;
[FieldOffset(104)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmBitsPerPel;
[FieldOffset(108)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsWidth;
[FieldOffset(112)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsHeight;
// ************ Union Start ************
// Because both members are 4-bytes, the union is 4-bytes
// and its members are overlapped
// Again, you cannot emit the container
// Except if both are equal, you can emit anyone of them
[FieldOffset(116)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFlags;
[FieldOffset(116)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmNup;
// ************* Union End *************
[FieldOffset(120)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFrequency;
[FieldOffset(124)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMMethod;
[FieldOffset(128)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMIntent;
[FieldOffset(132)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmMediaType;
[FieldOffset(136)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDitherType;
[FieldOffset(140)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved1;
[FieldOffset(144)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved2;
[FieldOffset(148)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningWidth;
[FieldOffset(152)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningHeight;
}
// 16-bytes structure
[StructLayout(LayoutKind.Sequential)]
//public class DEVMODE_PRINT_SETTINGS
public struct DEVMODE_PRINT_SETTINGS
{
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
}
// 8-bytes structure
[StructLayout(LayoutKind.Sequential)]
//public class POINTL
public struct POINTL
{
public Int32 x;
public Int32 y;
}
Lengthy, isn’t it? DEVMODE is one of the lengthy and compound GDI structures. If you want to learn more about laying out structure into memory, refer to chapter 6 €œMemory Management.€
From the last code we learn that€¦
Whether the union defined as a single entity or inside a structure, you will need to lay-out the type explicitly into memory to allow defining two or more variables at the same memory location.
When setting the memory layout explicitly, we apply the FieldOffsetAttribute attribute to the variable specifying the location -in bytes- of the variable from the start of the type.
In the union that defines a structure inside, we marshaled the structure outside the union and referred it to be the container of other members. Chapter 6 demonstrates other techniques for laying-out structures into memory.
Working with Display Settings
The follows example shows how you can access and modify display settings programmatically using C# and Windows API. In this example we will create four functions, one retrieves current display settings, another enumerates available display modes, the third changes current display settings, and the last changes screen orientation (i.e. rotates the screen.)
For our example, we will use the DEVMODE and POINTL structures that we have marshaled previously. In addition, we will make use of two new Windows API functions, EnumDisplaySettings and ChangeDisplaySettings. The following is the unmanaged signature of both functions:
Listing 3.15 EnumDisplaySettings() and ChangeDisplaySettings() Unmanaged Signature
public static void GetCurrentSettings()
{
DEVMODE mode = new DEVMODE();
mode.dmSize = (ushort)Marshal.SizeOf(mode);
if (EnumDisplaySettings(null,
ENUM_CURRENT_SETTINGS, ref mode) == true) // Succeeded
{
Console.WriteLine("Current Mode:nt" +
"{0} by {1}, {2} bit, {3} degrees, {4} hertz",
mode.dmPelsWidth, mode.dmPelsHeight,
mode.dmBitsPerPel, mode.dmDisplayOrientation * 90,
mode.dmDisplayFrequency);
}
}
public static void EnumerateSupportedModes()
{
DEVMODE mode = new DEVMODE();
mode.dmSize = (ushort)Marshal.SizeOf(mode);
int modeIndex = 0; // 0 = The first mode
Console.WriteLine("Supported Modes:");
while (EnumDisplaySettings(null,
modeIndex, ref mode) == true) // Mode found
{
Console.WriteLine("t{0} by {1}, {2} bit, " +
"{3} degrees, " +
"{4} hertz",
mode.dmPelsWidth, mode.dmPelsHeight,
mode.dmBitsPerPel, mode.dmDisplayOrientation * 90,
mode.dmDisplayFrequency);
modeIndex++; // The next mode
}
}
public static void ChangeDisplaySettings
(int width, int height, int bitCount)
{
DEVMODE originalMode = new DEVMODE();
originalMode.dmSize = (ushort)Marshal.SizeOf(originalMode);
// Retrieving current settings to edit them
EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref originalMode);
// Making a copy of the current settings
// to allow reseting to the original mode
DEVMODE newMode = originalMode;
// Changing the settings
newMode.dmPelsWidth = (uint)width;
newMode.dmPelsHeight = (uint)height;
newMode.dmBitsPerPel = (uint)bitCount;
// Capturing the operation result
int result = ChangeDisplaySettings(ref newMode, 0);
if (result == DISP_CHANGE_SUCCESSFUL)
{
Console.WriteLine("Succeeded.n");
// Inspecting the new mode
GetCurrentSettings();
Console.WriteLine();
// Waiting for seeing the results
Console.ReadKey(true);
ChangeDisplaySettings(ref originalMode, 0);
}
else if (result == DISP_CHANGE_BADMODE)
Console.WriteLine("Mode not supported.");
else if (result == DISP_CHANGE_RESTART)
Console.WriteLine("Restart required.");
else
Console.WriteLine("Failed. Error code = {0}", result);
}
public static void RotateScreen(bool clockwise)
{
// Retrieving current settings
// ...
// Rotating the screen
if (clockwise)
if (newMode.dmDisplayOrientation <; DMDO_270)
newMode.dmDisplayOrientation++;
else
newMode.dmDisplayOrientation = DMDO_DEFAULT;
else
if (newMode.dmDisplayOrientation >; DMDO_DEFAULT)
newMode.dmDisplayOrientation--;
else
newMode.dmDisplayOrientation = DMDO_270;
// Swapping width and height;
uint temp = newMode.dmPelsWidth;
newMode.dmPelsWidth = newMode.dmPelsHeight;
newMode.dmPelsHeight = temp;
// Capturing the operation result
// ...
}
the Console Library
There are functionalities of console applications that are not accessible from the .NET Framework like clearing the console screen and moving a text around.
The following sample shows a tiny library for console applications. It contains some of the common functionalities of the console (like writing and reading data) along with new functionalities added.
Listing 3.18 The Console Library Sample
SafeNativeMethods.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
/// <summary>
/// Safe native functions
/// </summary>
internal static class SafeNativeMethods
{
/// <summary>
/// Standard input device.
/// </summary>
public const int STD_INPUT_HANDLE = -10;
/// <summary>
/// Standard output device.
/// </summary>
public const int STD_OUTPUT_HANDLE = -11;
/// <summary>
/// Standard error device (usually the output device.)
/// </summary>
public const int STD_ERROR_HANDLE = -12;
/// <summary>
/// White space character for clearing the screen.
/// </summary>
public const char WHITE_SPACE = ' ';
/// <summary>
/// Retrieves a handle for the console standard input, output, or error device.
/// </summary>
/// <param name="nStdHandle">The standard device of which to retrieve handle for.</param>
/// <returns>The handle for the standard device selected.
/// Or an invalid handle if the function failed.</returns>
[DllImport("Kernel32.dll")]
public static extern IntPtr GetStdHandle([param: MarshalAs(UnmanagedType.I4)] int nStdHandle);
/// <summary>
/// Writes a character string to the console buffer starting from the current cursor position.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="lpBuffer">The string of which to write.</param>
/// <param name="nNumberOfCharsToWrite">Number of characters to write.</param>
/// <param name="lpNumberOfCharsWritten">Outputs the number of characters written.</param>
/// <param name="lpReserved">Reserved.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WriteConsole
(IntPtr hConsoleOutput,
string lpBuffer,
[param: MarshalAs(UnmanagedType.U4)] uint nNumberOfCharsToWrite,
[param: MarshalAs(UnmanagedType.U4)] [Out] out uint lpNumberOfCharsWritten,
[param: MarshalAs(UnmanagedType.U4)]
uint lpReserved);
/// <summary>
/// Read a character string from the console buffer starting from the current cursor position.
/// </summary>
/// <param name="hConsoleInput">A handle for the opened input device.</param>
/// <param name="lpBuffer">The string read from the buffer.</param>
/// <param name="nNumberOfCharsToRead">The number of characters to read.</param>
/// <param name="lpNumberOfCharsRead">Outputs the number of characters read.</param>
/// <param name="lpReserved">Reserved.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadConsole(
IntPtr hConsoleInput,
StringBuilder lpBuffer,
[param: MarshalAs(UnmanagedType.U4)] uint nNumberOfCharsToRead,
[param: MarshalAs(UnmanagedType.U4)] [Out] out uint lpNumberOfCharsRead,
[param: MarshalAs(UnmanagedType.U4)] uint lpReserved);
/// <summary>
/// Retrieves information about the console cursor such as the size and visibility.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="lpConsoleCursorInfo">The cursor info.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetConsoleCursorInfo(
IntPtr hConsoleOutput,
[Out] out CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
/// <summary>
/// Sets the console cursor properties as the size and visibility.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="lpConsoleCursorInfo">The cursor info.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetConsoleCursorInfo(
IntPtr hConsoleOutput,
ref CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
/// <summary>
/// Moves a block of data in a screen buffer.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="lpScrollRectangle">The coordinates of the block to move.</param>
/// <param name="lpClipRectangle">The coordinates affected by the scrolling.</param>
/// <param name="dwDestinationOrigin">The coordinates represents
/// the new location of the block.</param>
/// <param name="lpFill">Specifies the character and color info for the cells
/// left empty after the move.</param>
/// <returns>True if succeeded, otherwise False.</returns>
/// <remarks>
/// Because we are going to set the <paramref name="lpClipRectangle"/> to NULL,
/// we marshaled it as IntPtr so we can set it to null using IntPtr.Zero.
/// If you do need to set its value, you can marshal it as SMALL_RECT.
/// </remarks>
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ScrollConsoleScreenBuffer(
IntPtr hConsoleOutput,
ref SMALL_RECT lpScrollRectangle,
IntPtr lpClipRectangle,
COORD dwDestinationOrigin,
ref CHAR_INFO lpFill);
/// <summary>
/// Retrieves information about the specified console screen buffer.
/// </summary>
/// <param name="hConsoleOutput">A handle for the device of which to get its
/// information.</param>
/// <param name="lpConsoleScreenBufferInfo">Outputs the information of the
/// specified screen buffer.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetConsoleScreenBufferInfo
(IntPtr hConsoleOutput,
[Out] out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
/// <summary>
/// Fills the console buffer with a specific character.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="cCharacter">The character of which to fill the buffer width.
/// Setting this character to a white space means clearing the cells.</param>
/// <param name="nLength">The number of cells to fill.</param>
/// <param name="dwWriteCoord">The location of which to start filling.</param>
/// <param name="lpNumberOfCharsWritten">Outputs the number of characters written.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FillConsoleOutputCharacter
(IntPtr hConsoleOutput,
char cCharacter,
[param: MarshalAs(UnmanagedType.U4)] uint nLength,
COORD dwWriteCoord,
[param: MarshalAs(UnmanagedType.U4)][Out] out uint lpNumberOfCharsWritten);
/// <summary>
/// Sets the console cursor to a specific position.
/// </summary>
/// <param name="hConsoleOutput">A handle for the opened output device.</param>
/// <param name="dwCursorPosition">The new cursor position inside the console buffer.</param>
/// <returns>True if succeeded, otherwise False.</returns>
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetConsoleCursorPosition
(IntPtr hConsoleOutput, COORD dwCursorPosition);
}
/// <summary>
/// Information about the screen buffer.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
/// <summary>
/// The size of the buffer.
/// </summary>
public COORD dwSize;
/// <summary>
/// The location of the cursor inside the buffer.
/// </summary>
public COORD dwCursorPosition;
/// <summary>
/// Additional attributes about the buffer write the fore color and back color.
/// </summary>
[MarshalAs(UnmanagedType.U2)]
public ushort wAttributes;
/// <summary>
/// The location and bounds of the window.
/// </summary>
public SMALL_RECT srWindow;
/// <summary>
/// The maximum size of the window.
/// </summary>
public COORD dwMaximumWindowSize;
}
/// <summary>
/// Coordinates (X, Y).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
/// <summary>
/// The location from the left (X).
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short X;
/// <summary>
/// The location from the top (Y).
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short Y;
}
/// <summary>
/// Defines the coordinates of the upper left and right bottom coordinates of a rectangle.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
/// <summary>
/// The X-coordinate of the upper left corner of the rectangle.
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short Left;
/// <summary>
/// The Y-coordinate of the upper left corner of the rectangle.
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short Top;
/// <summary>
/// The X-coordinate of the lower right corner of the rectangle.
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short Right;
/// <summary>
/// The Y-coordinate of the lower right corner of the rectangle.
/// </summary>
[MarshalAs(UnmanagedType.I2)]
public short Bottom;
}
/// <summary>
/// Defines the console cursor info.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_CURSOR_INFO
{
/// <summary>
/// The size of the cursor. Usually 0.25 of the cell.
/// </summary>
[MarshalAs(UnmanagedType.U4)]
public uint dwSize;
/// <summary>
/// If cursor is visible or not.
/// </summary>
[MarshalAs(UnmanagedType.Bool)]
public bool bVisible;
}
/// <summary>
/// Defines a character information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CHAR_INFO
{
/// <summary>
/// The character.
/// </summary>
public char Char;
/// <summary>
/// Additional attributes of the character like fore color and back color.
/// </summary>
[MarshalAs(UnmanagedType.U2)]
public ushort Attributes;
}
ConsoleLib.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
// Console horizontal text alignment.
public enum ConsoleTextAlignment
{
/// <summary>
/// Text is left aligned.
/// </summary>
Left,
/// <summary>
/// Text is right aligned.
/// </summary>
Right,
/// <summary>
/// Text is centered.
/// </summary>
Center
}
/// <summary>
/// Console standard devices.
/// </summary>
public enum ConsoleStandardDevice
{
/// <summary>
/// The input device.
/// </summary>
Input = SafeNativeMethods.STD_INPUT_HANDLE,
/// <summary>
/// The output device.
/// </summary>
Output = SafeNativeMethods.STD_OUTPUT_HANDLE,
/// <summary>
/// The error device (usually the output device.)
/// </summary>
Error = SafeNativeMethods.STD_ERROR_HANDLE
}
/// <summary>
/// Extension methods for the console.
/// </summary>
public static class ConsoleExtensions
{
/// <summary>
/// Clears the screen buffer.
/// </summary>
public static void ClearScreen()
{
// Clearing the screen starting from the first cell
COORD location = new COORD();
location.X = 0;
location.Y = 0;
ClearScreen(location);
}
/// <summary>
/// Clears the screen buffer starting from a specific location.
/// </summary>
/// <param name="location">The location of which to start clearing
/// the screen buffer.</param>
public static void ClearScreen(COORD location)
{
// Clearing the screen starting from the specified location
// Setting the character to a white space means clearing it
// Setting the count to 0 means clearing to the end, not a specific length
FillConsoleBuffer(location, 0, SafeNativeMethods.WHITE_SPACE);
}
/// <summary>
/// Fills a specific cells with a specific character starting from a specific location.
/// </summary>
/// <param name="location">The location of which to start filling.</param>
/// <param name="count">The number of cells starting
/// from the location to fill.</param>
/// <param name="character">The character to fill with.</param>
public static void FillConsoleBuffer(COORD location, uint count, char character)
{
// Getting the console output device handle
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
uint length;
// If count equals 0 then user require clearing all the screen
if (count == 0)
{
// Getting console screen buffer info
CONSOLE_SCREEN_BUFFER_INFO info = GetBufferInfo(ConsoleStandardDevice.Output);
// All the screen
length = (uint)(info.dwSize.X * info.dwSize.Y);
}
else
length = count;
// The number of written characters
uint numChars;
// Calling the Win32 API function
SafeNativeMethods.FillConsoleOutputCharacter(handle, character,
length, location, out numChars);
// Setting the console cursor position
SetCursorPosition(location);
}
/// <summary>
/// Rettrieves a handle for a specific device.
/// </summary>
/// <param name="device">The device of which to retrieve the handle for.</param>
/// <returns>The handle for the specified device.</returns>
public static IntPtr GetStandardDevice(ConsoleStandardDevice device)
{
// Calling the Win32 API function
return SafeNativeMethods.GetStdHandle((int)device);
}
/// <summary>
/// Writes an empty line to the console buffer on the current position of the cursor.
/// </summary>
public static void WriteLine()
{
WriteLine(string.Empty);
}
/// <summary>
/// Writes specific text followed by a line terminator to the console buffer on
/// the current position of the cursor.
/// </summary>
/// <param name="txt">The text to write.</param>
public static void WriteLine(string txt)
{
WriteLine(txt, ConsoleTextAlignment.Left);
}
/// <summary>
/// Writes specific text followed by a line terminator to the console buffer on the
/// current position of the cursor with the specified line alignemnt.
/// </summary>
/// <param name="txt">The text to write.</param>
/// <param name="alignment">The horizontal alignment of the text.</param>
public static void WriteLine(string txt, ConsoleTextAlignment alignment)
{
Write(txt + Environment.NewLine, alignment);
}
/// <summary>
/// Writes specific text to the console buffer on the current position of the cursor.
/// </summary>
/// <param name="txt">The text to write.</param>
public static void Write(string txt)
{
Write(txt, ConsoleTextAlignment.Left);
}
/// <summary>
/// Writes specific text to the console buffer on the current position of the cursor
/// with the specified line alignment.
/// </summary>
/// <param name="txt">The text to write.</param>
/// <param name="alignment">The horizontal alignment of the text.</param>
public static void Write(string txt, ConsoleTextAlignment alignment)
{
if (alignment == ConsoleTextAlignment.Left)
InternalWrite(txt);
else
{
// Determining the location of which to begin writing
CONSOLE_SCREEN_BUFFER_INFO info = GetBufferInfo(ConsoleStandardDevice.Output);
COORD pos = new COORD();
if (alignment == ConsoleTextAlignment.Right)
pos.X = (short)(info.dwSize.X - txt.Length);
else // Center
pos.X = (short)((info.dwSize.X - txt.Length) / 2);
pos.Y = info.dwCursorPosition.Y;
// Changing the cursor position
SetCursorPosition(pos);
// Now writing on the current position
InternalWrite(txt);
}
}
/// <summary>
/// Writing a specific text to the console output buffer starting from the
/// current cursor position.
/// </summary>
/// <param name="txt">The text to write.</param>
private static void InternalWrite(string txt)
{
// Required for the WriteConsole() function
// It is the number of characters written
uint count;
// Getting the output handle
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
// Calling the Win32 API function
SafeNativeMethods.WriteConsole(handle, txt, (uint)txt.Length, out count, 0);
}
/// <summary>
/// Shows or hides the cursor.
/// </summary>
/// <param name="show">Specifies whether to show the cursor or not.</param>
public static void ShowCursor(bool show)
{
CONSOLE_CURSOR_INFO info;
// Getting the output device
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
// Getting the cursor info
SafeNativeMethods.GetConsoleCursorInfo(handle, out info);
// Determining the visibility of the cursor
info.bVisible = show;
// Setting the cursor info
SafeNativeMethods.SetConsoleCursorInfo(handle, ref info);
}
/// <summary>
/// Read the next line from the input device.
/// </summary>
/// <returns></returns>
public static string ReadText()
{
// The buffer
// Maximum number of characters is 256
StringBuilder buffer = new StringBuilder(256);
// Required for the function call
uint count;
// Getting the input device that's used for receiving user input
SafeNativeMethods.ReadConsole(GetStandardDevice(ConsoleStandardDevice.Input), buffer,
(uint)buffer.Capacity, out count, 0);
// Returning the user input cutting up the line terminator
return buffer.ToString().Substring(0, (int)(count - Environment.NewLine.Length));
}
/// <summary>
/// Retrieves the buffer info of the specified device.
/// </summary>
/// <param name="device">The device of which to retrieve its information.</param>
/// <returns>The buffer info of the specified device.</returns>
public static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(ConsoleStandardDevice device)
{
// Returning the handle for the selected device
IntPtr handle = GetStandardDevice(device);
// Getting console screen buffer information
CONSOLE_SCREEN_BUFFER_INFO info;
SafeNativeMethods.GetConsoleScreenBufferInfo(handle, out info);
return info;
}
/// <summary>
/// Sets the cursor position in the buffer.
/// </summary>
/// <param name="pos">The coordinates of which to move the cursor to.</param>
public static void SetCursorPosition(COORD pos)
{
// Getting the console output device handle
IntPtr handle = SafeNativeMethods.GetStdHandle(SafeNativeMethods.STD_OUTPUT_HANDLE);
// Moving the cursor to the new location
SafeNativeMethods.SetConsoleCursorPosition(handle, pos);
}
/// <summary>
/// Writes the buffer information to the screen.
/// </summary>
/// <param name="info">The information of which to write.</param>
public static void WriteBufferInfo(CONSOLE_SCREEN_BUFFER_INFO info)
{
// Discovering console screen buffer information
WriteLine("Console Buffer Info:");
WriteLine("--------------------");
WriteLine("Cursor Position:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "t{0}, {1}",
info.dwCursorPosition.X, info.dwCursorPosition.Y));
// Is this information right?
WriteLine("Maximum Window Size:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "t{0}, {1}",
info.dwMaximumWindowSize.X,
info.dwMaximumWindowSize.Y));
// Is this information right?
WriteLine("Screen Buffer Size:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "t{0}, {1}",
info.dwSize.X, info.dwSize.Y));
WriteLine("Screen Buffer Bounds:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"t{0}, {1}, {2}, {3}",
info.srWindow.Left, info.srWindow.Top,
info.srWindow.Right, info.srWindow.Bottom));
WriteLine("--------------------");
}
/// <summary>
/// Writes the specific text followed by a line terminator to the left and moves
/// it to the far right.
/// </summary>
/// <param name="txt">The text of which to write.</param>
public static void MoveText(string txt)
{
// First, writing the text
WriteLine(txt);
// Getting the handle for the output device
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
// Getting the screen buffer info for the output device
CONSOLE_SCREEN_BUFFER_INFO screenInfo = GetBufferInfo(ConsoleStandardDevice.Output);
// Selecting the text to be moved
SMALL_RECT rect = new SMALL_RECT();
rect.Left = 0; // The 1st cell
rect.Top = (short)(screenInfo.dwCursorPosition.Y - 1); // The row of the text
rect.Bottom = (short)(rect.Top); // Only a single line
while (true)
{
// Moving to the right
rect.Right = (short)(rect.Left + (txt.Length - 1));
// Do not move it nore if we are in the far right of the buffer
if (rect.Right == (screenInfo.dwSize.X - 1))
break;
// The character to fill the empty cells created after the move with
CHAR_INFO charInfo = new CHAR_INFO();
charInfo.Char = SafeNativeMethods.WHITE_SPACE; // For clearing the cells
// Calling the API function
SafeNativeMethods.ScrollConsoleScreenBuffer(handle, ref rect, IntPtr.Zero,
new COORD() { X = (short)(rect.Left + 1), Y = rect.Top }, ref charInfo);
// Blocking the thread for the user to see the effect
System.Threading.Thread.Sleep(100);
// Moving the rectangle
rect.Left++;
}
}
}
Summary
After all, you learned that compound types are unmanaged structures and unions, and they called compound because they consisted of other types.
You learned that compound types can be marshaled as either a managed structure or a class. In addition, you learned how to lay-out the type into memory.
Again and again, the memory layout and size of the type is very crucial.
After that, you have worked with unions and learned that unions are simply a group of multiple variables share the same memory. In fact, it is the same memory location that is shared by one or more variables. Therefore, bits are represents in several ways.
Now it is the time for arrays. The next chapter discusses what arrays are and how to marshal them.