Cook Computing

« August 2002 »

Nested Classes

I've convinced myself to use nested classes as mentioned in the previous entry here and put up with a large source file. The benefits are reducing pollution of the namespace in which the outer class is defined, restricting the visibility of the nested classes to the class which uses them, and providing access to the private members of the outer class.

The first point allows me to define other classes in the same namespace which will use similarly named but different "helper" classes. I could handle this by giving the helper classes unique names but I think its better to handle this with a proper namespace mechanism rather than some arbitrary naming scheme.

The second point is achieved by making the nested classes private. This restricts visibility of the nested classes to their outer class (and incidentally a nested class is the only case where a class can be marked as private).

Finally, the nested classes need priviledged access to some members of their outer class. I can mark these members are private yet the nested classes will still be able to access them. Note that although a nested class has access to the private members of the outer class, the converse does not apply: the outer class cannot access the private or protected members of the nested class.

To illustrate this with some code:

class Outer
{
  private void X() { }

  private class Nested
  {
    public void A() { }
    private void B() { }
  }
}

Code within class Outer can instantiate class Nested but can only call method A. Code outside class Outer cannot instantiate or access class Nested. Code within class Nested can call method X of class Outer even though this method is private.

The large source file does make coding more difficult and so I'm experimenting for the first time with code regions and the outlining functionality of Visual Studio. But I would much prefer to be using class augmentation.

Posted by Charles Cook at 06:28 PM. Permalink. View Comments.

Class Augmentation

I'm working on a project in which the use of nested classes within a single parent class makes for a very large source file. I'd like to be able to spread the parent class across several source files, one for each of the nested classes. Unfortunately C# does not support class augmentation (as this capability is called in chapter six of Inside Microsoft .NET IL Assembler). Once a class declaration in C# has been closed it cannot be re-opened. For example the following is not allowed:

class A
{
  public void Foo() { }
}

class A
{
  public void Bar() { }
}

Class augmentation is supported by ILAsm and would look something like this:

.class A
{
  .method public void Foo()
  {
    ret
  }
}
// following possibly in another source file
.class A
{
  .method public void Bar()
  {
    ret
  }
}
Posted by Charles Cook at 08:49 PM. Permalink. View Comments.

CritSect Bug

After looking at Jeff Richter's Optex version of a critical section I realized there is a bug in the Leave function of my CritSect class: the curtid member must be set to 0 whenever a thread leaves the CS and before the event is signalled. This avoids the following race condition:

  1. thread 0xaa leaves the CS with curtid set to 0xaa
  2. thread 0xbb enters the CS by performing the InterlockedIncrement but is pre-empted before setting curtid
  3. thread 0xaa attempts to enter the CS and finds curtid still set to 0xaa and so just increments depth - WRONG

The modified code is:

void CritSect::Leave()
{
  if (depth)
  {
    depth--;
    InterlockedDecrement(&cntr);
  }
  else
  {
    InterlockedExchange(&curtid, 0);
    if (InterlockedDecrement(&cntr) > 0)
      SetEvent(hevt);
  }
}

The faulty version of Leave passed my tests which just goes to demonstrate why the XP approach of writing tests before coding doesn't apply to multi-threaded code.

In the Optex class Richter doesn't use InterlockedCompareExchange to test whether a thread owns the CS:

if (m_pSharedInfo->m_dwThreadId == dwThreadId) { 

At first sight this seems wrong to me. If this operation is atomic then surely setting the value of m_dwThreadId is also atomic, yet he uses InterlockedExchange for that? I suspect the use of InterlockedExchange ensures that it is safe to make the comparision because InterlockedExchange prevents a read from occuring while the exchange is taking place?

Posted by Charles Cook at 08:18 AM. Permalink. View Comments.

Cassini and FAQ Update

I downloaded Cassini yesterday to check that it supported XML-RPC.NET services. Everything works fine and in fact if you're using Visual Studio debugging it is much easier to run Cassini for each debug run instead of attaching to the ASP.NET process. I added a section describing Cassini to the XML-RPC.NET FAQ (see Can I run XML-RPC.NET services with other web servers?).

I also added a couple of questions about debugging: How do I monitor XML-RPC calls across the network? and How do I debug an XML-RPC.NET service?.

Posted by Charles Cook at 12:36 PM. Permalink. View Comments.

Critical Section

Discussing critical sections the other day with some colleagues I was surprised to discover that several of them did not know that a WIN32 critical section is designed to avoid a switch into kernel mode when there is no contention for ownership (and so can be considerably faster than using a mutex). However I realized the implementation is not obvious and so I set myself the task of implementing a critical section without doing any research into how others have achieved this.

I quickly devised a solution but this contained the major flaw of not supporting re-entrancy. The full solution took a bit longer but I think the code below does the job. I've done some testing and as well as working correctly it appears to have the same performance as the WIN32 critical section. As ever with multi-threaded code it is difficult to determine that the code is bug-free, a good reason for avoiding multi-threaded code wherever possible.

class CritSect
{
public:
CritSect::CritSect()
{
  curtid = 0;
  cntr = 0;
  depth = 0;
  hevt = CreateEvent(NULL, 
                     FALSE, // auto-reset
                     FALSE, // initially unsignalled
                     NULL);
}

CritSect::~CritSect()
{
  CloseHandle(hevt);
}

void CritSect::Enter()
{
  DWORD tid = GetCurrentThreadId();
  if (InterlockedIncrement(&cntr) > 1)
  {
    if (InterlockedCompareExchange(&curtid, tid, tid) == tid)
      depth++;
    else
    {
      WaitForSingleObject(hevt, INFINITE);  
      InterlockedExchange(&curtid, tid);
    }
  }
  else
    InterlockedExchange(&curtid, tid);
}

void CritSect::Leave()
{
  if (InterlockedDecrement(&cntr) > 0)
  {
    if (depth)
      depth--;
    else    
      SetEvent(hevt);
  }
}

private:
  LONG cntr;
  LONG curtid;
  LONG depth;
  HANDLE hevt;
};
Posted by Charles Cook at 12:11 PM. Permalink. View Comments.

Invoke on non-existent proxy method

I've updated the XML-RPC.NET FAQ with an answer to the question "Why does my client throw exception "Invoke on non-existent proxy method"?

I've had a number of queries about this issue and I'm plannning to add the functionality to generate an assembly containing a proxy class or dynamically generate the proxy class at runtime. The former would be useful where a proxy was going to be used in a restricted security environment becase the second approach would only work in a full trust scenario.

After reading the interesting article Generating Code at Run Time With Reflection.Emit I did some experimenting with the sample code to see how easy it would be to generate XML-RPC.NET proxies. It doesn't look too difficult to create a utility which, when given an assembly containing an interface representing the XML-RPC endpoint, would create an assembly containing a proxy class deriving from XmlRpcClientProtocol. The same code could also be used to dynamically generate the proxy for the second scenario mentioned above.

Posted by Charles Cook at 07:30 AM. Permalink. View Comments.

Too busy LDAPing

I've been too busy to blog recently. I've been getting to grips with LDAP and haven't had the time or inclination to do much out of working hours.

I started using ADSI then switched to the Microsoft implementation of the LDAP API for the richer functionality, in particular the ability to use a wider range of authentication.

The LDAP API is fairly low level and after working on .NET code its been strange to go back to an API which involves allocating C structs and arrays of strings and remembering to free up memory allocated by my code and that allocated by the API. It makes me appreciate yet again what a huge step forward .NET represents in improved productivity. In the end I wrote a C++ class to encapsulate the API and speed up the rest of my coding.

One quirk of the LDAP API is that if you want to set an attribute to an empty value, you have to delete it (I suppose the attribute remains in the directory but the API doesn't let you see it once its "deleted"). However the deletion fails if the attribute is already set to an empty value. Therefore if you want to modify several attributes in one call, setting one or more of them to empty values, the whole call fails if one or more of these attributes is already empty. So you have to first make a call to determine which attributes are already empty. This involves another round-trip and processing of a call on the server. Not very efficient. [If I've got this wrong and I'm showing hideous naievety about LDAP please add a comment to this item to correct me.]

Posted by Charles Cook at 07:05 PM. Permalink. View Comments.