Give your C# class Initializer support0 Reacties

Would you like a to boost your class which hosts a collection, and utilize a cool C# language feature, which is collection initializer support?

For instance var myList = new List() {1,3,4} where {1,3,4} is the initializer.

The basic idea is to implement IEnumerable, and IEnumerator and the class must have an ‘Add’ method with the correct signature.

I made the class generic so you can initialize any array (ok, not for structs and simple types, but you modify a few things and that works too)

// our sample class for iteration feeding

public class Address
   {
       public string Street { get; set; }
       public string StreetNumber { get; set; }
       public string ZipCode { get; set; }
       public string City { get; set; }
   }
   public static class Program
   {
       static void Main()
       {
           var t = new ClassWithInitializerSupport

{
               new Address()
               {
                   Street = "Mainstr.",
                   StreetNumber = "1",
                   City ="London"
               },
              new Address()
               {
                   Street = "De Dam",
                   StreetNumber = "1",
                   City ="Amsterdam"
               },
               new Address()
               {
                   Street = "Mangostreet",
                   StreetNumber = "123",
                   City ="New York"
               }
           };
           foreach(var a in t)
           {
               Console.WriteLine("Street {0}, City {1}", a.Street, a.City);
           }

}

public class ClassWithInitializerSupport : IEnumerable, IEnumerator where T : class
   {
       public ClassWithInitializerSupport()
       {
           arr = (T[])Array.CreateInstance(typeof(T), 0);
           pos = -1;
       }
       private T[] arr;
       private int pos;
       public T Current => arr[pos];

       object IEnumerator.Current => arr[pos];

       public void Dispose()
       {

       }

       public IEnumerator GetEnumerator()
       {
           return this;
       }
       public void Add(T v)
       {
           var p = arr.Length;
           Array.Resize(ref arr, p + 1);
           arr[p] = v;
       }
       public bool MoveNext()
       {
           if (pos + 1 < arr.Length)
           {
               pos++;
               return true;
           }
           return false;
       }

       public void Reset()
       {
           pos = -1;
       }

       IEnumerator IEnumerable.GetEnumerator()
       {
           return this;
       }
   }

Howto loop through each element on a SafeArray with an unknown number of Dimensions?0 Reacties

I thought that the subject ‘SafeArrays’ were really from the previous age and there was really nothing new about them, I could tell you , that was not already known.

So sit back and listen, what I am going to tell you, has been decided, in a dark hole, were some Microsoft Nerds Surprise must have thought that our programmers' live was too easy. So they modified the way the old variant arrays from the COM era were stored in memory.

Which Windows editions are involved in this modification? I don't know exactly. Anyway, Windows 64 bit edition (x64) really is subject to this, while Windows 2003 x86 (32 bit) is not. And don't worry too much, if you only use Ole Automation API calls, and not like I did, directly manipulated the SAFEARRAY storage area, your hard working code at your customers site, still should work Smile

So, let’s figure what has changed by looking at the code below which very common.

SAFEARRAYBOUND bounds[1];

bounds.cElements = 10;
bounds.lLbound = 0;

SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 1, &bounds);

This creates a variant array, that is compatible with scripting, similar to
Redim myArray(9) ' 0 to 9 is ten elements.

But before the array storage memory layout modification that I'm talking about,

LONG elSize = SafeArrayGetElementSize(psa); would return 16 (since sizeof(VARIANT) obviously equals that) but on Windows x64, this returns 24. Possibly, the alignment still is 16, but this makes me suspicious. Why did Microsoft change this? So I want to be sure it works independently of the Windows version or future features. 

// code below gives an impression how to loop through an ‘unknonn dimension sized Safe Array, in this case the variant type is VT_VARIANT. Don’t use it. It’s faulty now on modern Windows systems.

LONG lElements  = 0; 

for (LONG x = 0; x < cDims; x++)

{

      SafeArrayGetLBound(psa, x + 1, &lbound);

      SafeArrayGetUBound(psa, x + 1, &ubound);

      lElements += (ubound - lbound + 1);

 } 

hr = SafeArrayAccessData(psa, &psadata);
if (hr == S_OK)
{
                VARIANT *myvarray = static_cast<VARIANT *>(psadata);
                for (LONG els = 0; els < lElements && hr == S_OK; els++)
                {              // do something easy with the element
                               VARTYPE vt = myvarray[els].vt;
                }
                SafeArrayUnaccessData(psa);
}
The code above, would work to go through all elements for –any type of-SafeArray, VT_UI1, VT_BSTR, VT_VARIANT, because we assumed, for instance, that a VT_UI1, would be one byte long, isn’t it? And a BSTR Safe Array, would be a memory block, of BSTR pointers. And a VARIANT array, would have been a block of elements, consisting of VARIANTS.

So the code above, just worked, and looks in fact, pretty simple, fast and elegant.
Why so? Because it avoided the task to construct the indices one by one, and to fetch the element by using SafeArrayGetElement(psa, rgindices, outputvalue).


This one surely works, but has two important disadvantages,
1) when you deal with a known 3D or 2D sized-array, it's easy to fill the rgIndices in a loop, but if the dimensions are unknown, it would be required to iterate through the elements by other means. So  you’re up to do some nasa stuff!
J
.
2)  SafeArrayGetElement is relatively slow, since it copies the out value and you need to release the output (such as variants and strings). It’s like pumping around lots of memory, especially when the array is huge.

To get this working, I had to refresh my knowledge of some informatics school I did in the past, about simple math, about carry over on signed numbers. Since SafeArrays have different lowerbounds and upperbound ranges per dimension, you cannot simply multiply by a known row size, to initialize it. But anyway, to make a longer story shorter, and I might lack the correct choice of words, for things that I technically just ‘see’, I wrote the code for you. It’s pretty cool, I could not find such code through Google. J
Some tips. I decided to avoid reading the pointer to the safearray descriptor at all, because I saw some unexplainable swapping of rgsaBounds. Better use the API if there is one!

// assume psa to be of SAFEARRAY and this code is using ATL version 8

LONG cDims = SafeArrayGetDim(psa);

//we ‘rebuild’ the bounds by using the API, not by reading the psa structure directly to avoid unclear behaviour
CTempBuffer<SAFEARRAYBOUND> psaBound(cDims);

CTempBuffer<LONG> rgIndices(cDims);

LONG dimPointer = 0; // our dimension pointer, we go from left to right to build up the rgIndices

LONG currentEl = 0, ubound, lbound;

for (LONG x = 0; x < cDims; x++)

{

      SafeArrayGetLBound(psa, x + 1, &lbound);

      SafeArrayGetUBound(psa, x + 1, &ubound);

      psaBound[x].cElements = ubound - lbound + 1;

      psaBound[x].lLbound = rgIndices[x] = lbound;

}

// locking is not optional, it is needed.

SafeArrayLock(psa);

for(;;) //termination is decided within the loop

{

      if (rgIndices[dimPointer] <

            (LONG)psaBound[dimPointer].cElements + psaBound[dimPointer].lLbound)

      {

            VARIANT* pVar ;

            // use the fast version instead of SafeArrayGetElement!

            hr = SafeArrayPtrOfIndex(psa, rgIndices, (void**)&pVar);

            if (FAILED(hr))

                  MYBAILOUT(hr);

            rgIndices[dimPointer]++;

            //this terminates the for as soon as we reached the last array element

            if (++currentEl == lElements)

                  break;

      }

      // our carry on overflow stuff goes from left to right

      else

      {

            while(rgIndices[++dimPointer]++ ==

            (LONG)psaBound[dimPointer].cElements + psaBound[dimPointer].lLbound)

            {

            }

            //reset previous cols to initial lowerbound from left to

            // most right carry position

            for (LONG z = 0; z < dimPointer; z++)

                  rgIndicesPerson = psaBoundPerson.lLbound;

            // if carry has been done, we start counting on left again

            dimPointer= 0;              

      }

}

To visualise this, I think it’s usefull to dry test this using a matrix.

Imagine, we have a script, that was created by your customer, in VBSCript and youre superpower, martian CPP needs to do something with this data.

So, the Customer code could be:

Redim MyArray(3,2)

How would our dynamic rgIndice be iterated?

rgIndices would follow this pattern.

Left dimension

Right dimension

Absolute element position

0

0

0

1

0

1

2

0

2

3

0

3

0

1

4

1

1

5

2

1

6

HttpWebRequest on DotNet 2.0 driving you crazy for a B2B request? Disable ServicePointManager.Expect100Continue first!0 Reacties

Imagine, you have written a succesfull gateway to say, the European Community to check the validity of a VAT number.

It always works, but suddenly it does not work anymore. What's up? Could it be that some service pack modified the behaviour of the HttpWebRequest (including DotNet 2.0?) Sure  it is! see http://support.microsoft.com/kb/915599

This posting might spare you several hours. The idea of the Expect100Continue setting is, that postdata is sent to the webserver in a separate post sequence package. But not all web servers like IIS (sure I'm MVP Big Smile) are full featured so in this case, this feature was hard to detect, only by using Netmon, I could figure what the HttpWebRequest  was doing under the .NET runtime hood.

The Uri to the European VAT check is http://ec.europa.eu/taxation_customs/vies/

And it does a POST request to viesquer.do
(
the previous obsolete URL was  http://europa.eu.int/comm/taxation_customs/vies/cgi-bin/viesquer)

It had worked before because it just dit a GET request instead of a POST.

The steps that I walk through normally to write such a gateway,

  1. opening my browser
  2. Fill the form
  3. Start Netmon 2.1 (or higher)
  4. Post the data
  5. Stop Netmon and filter on the HTTP protocol.

Now it's up to you to emulate the postdata and be sure that the character encoding is according to the supported one. In my case, I assume that UTF-8 is the worldstandard (it should work for most sites).

The code below is technically correct for the mentioned purpose but it performs no validation checking.
If you wish more flexibility some more work has to be done.

static bool doVatCheck(string vat, string state)

        {

            string urlQuery = @"http://ec.europa.eu/taxation_customs/vies/viesquer.do";

            //important! The WebLogic Server 8.1 SP3 clearly does not support IETF RFC 2616 Section 10.1.1.

// so we disable the expect100 header and HTTP behaviour

            ServicePointManager.Expect100Continue = false;

            HttpWebRequest req;

            StreamReader read=null;

          

            Encoding enc = System.Text.Encoding.UTF8;

            MemoryStream sw = new MemoryStream();

            advancePos(enc, sw, "ms"); //member state

            advancePos(sw, '=');

            advancePos(enc, sw, state);

            advancePos(sw, '&');

            advancePos(enc, sw, "iso"); //same as member state, ISO country code

            advancePos(sw, '=');

            advancePos(enc, sw, state);

            advancePos(sw, '&');

            advancePos(enc, sw, "vat"); // 12no VATno. Do not submit dots or spaces

            advancePos(sw, '=');

            advancePos(enc, sw, vat);

            req = (HttpWebRequest)HttpWebRequest.Create(urlQuery);

            req.Method = "POST";

            req.ContentType = "application/x-www-form-urlencoded";

// optional setting, UserAgent. A firewall could block if no browser is detected    

            req.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)";

            long contLen = req.ContentLength = sw.Position;

            Stream str = req.GetRequestStream();

            str.Write(sw.GetBuffer(), 0, (int)contLen);

            str.Flush();

           try

           {

                read = new StreamReader(req.GetResponse().GetResponseStream(), enc);

                string buf = read.ReadToEnd();

                read.Close();

                return buf.IndexOf("Yes, valid VAT number") >= 0;           

            }

            catch (WebException ex)

            {

                //ex.st

                string errr = (urlQuery + ex.Message);

            }

            return false;

        }

static void advancePos(Encoding enc, MemoryStream sw, string val)

        {

            byte[] buf = HttpUtility.UrlEncodeToBytes(val, enc);

            sw.Write(bufje, 0, buf.Length);

        }

        static void advancePos(MemoryStream sw, char val)

        {

            sw.WriteByte((byte)val);

        }

 

ATL: Tiny but good Improvements on Visual Studio Sevice Pack 1 for ATL.0 Reacties

I'm glad to see, that MS still did not get rid of ATL and continued to support us, that's those guys, who think that .NET is really awful, but not the holy grail :)

If you want to see it yourselfs, get a copy of windiff.exe or just run it, and compare your directories. It's a very handy tool, to compare source codes! 

So, I compared some features on ATL. Quickly I found some differences in atlcomcli.h (I did not check everything however) and the improvements are OK!

Another part of the ATL source code, seems related to HTML and internet (ISAPI). Lots of checks and code has been added to make sure your precious HTML / encoding etc is kept in tact. 

Well, which improvements? As you might know that ATL is all, mainly about writing non-UI unmanaged code, such as COM components or using powerfull small sized wrappers around kernel-items like security, string management, etc.

At first, finally, I found that MS recognized that SafeArrayGetVartype is flawed, since it does not really fetch the VARTYPE of the array in all situations. So they wrote AtlSafeArrayGetActualVartype.

Another things is CComVariant::Clear() which was a dangerous function to use! Why and when? Because the function it wraps, is VariantClear and variantClear does only set vt to VT_EMPTY, but it does not 'zero' the rest of the VARIANT struct. Some programs, do expect in/out variables, and in the case that it is not always testing for the vt (variant type) and reallocates the CComVariant instance, it might erronously reallocate 'freed' or 'zombie'-pointers.

So my advise is to never use CComVariant::Clear() but use CComVariant::ClearToZero() now to avoid a case of reallocating zombie data!

Another handy improvements is a new overloaded method on the WriteToStream member function.
It is CComVariant::WriteToStream(IStream *pstr, VARTYPE vt);

So, if you write the current variant to a certain stream, you can convert it to a BSTR (for instance) using this extra new parameter..

Then we got some important bugfixes in atldb.h and atldbcli.h some annotation and datatype and even template fixes.

Lets see atlutil.h. Lots of new helper functions have been added. AtlStrToNum and functions that help to convert and to support the conversion to and from __int64 (LONGLONG) datatype. Normally, I always had to lookup for such functions into the VC function library and to add some wrappers around them.

Once I checked this all, and I recompiled one of my CPP, ATL projects, the size became just slightly bigger. But who cares? If this is all about more robust and safe code, I'm not disappointed by the Sp1 for VC on Visual Studio 2005.

 

(I was disappointed, when MS deprecated a lot of syntax without any announcements since of Visual Studio 2003)

HOWTO: Associate a custom WKGUID (well known guid) to a custom Container0 Reacties

Active Directory and ADSI (ldap in fact) have many unutilized features that, if the APIs would explain better be used much better!

Let me draw a situation for you.

Imagine you have an app, that is distributed and sold among international customers (congrats for that by the way J). Now there could happen 2 things, or 3 if nothing happens is a count as well).

1)      Your company suddenly got sold out to a rich guy or girl and now everything gets renamed including your ‘custom company container’.
So cn=ourcompany data,cn=Program Data,dn=nwtraders,dc=msft
Must become (because your boss asks you to do so!)
cn=thenewcompany data,cn=Program data,dn=nwtraders,dc=msft

2)      Some dn (distinguished Name) is subject to translation. Eg:
cn=Program Information,cn=etc blah blah
must be translated (because your boss asks you to do so
J ) to
cn=Information du programme,cn=etc blah blah

In both situations 1 and 2 you’ll have to keep a list of paths per language and many companies of course, understand this is a difficult and risky task, so they might choose to keep up with a single international English name.

Note: The WKGUID protects against renaming or translating, but it is not meant to ‘protect’ against moving your container within Active Directory)

Why should you accept this risk if Active Directory supports immunity against renaming this out of the box if you associate a WKGUID?

However there is no obvious documentation about this and you just should know that setting this property more or less is hidden through usage of IDirectoryObject while ADSI /scripting/vb6 environments were not able to use this interface. 

Worse, the sample at the MSDN might be working but goes lowest by using the ldap_connect API within a ADSI call (that’s rather dirty) instead of using CLSID_DNWithBinary and the sample is flawed with leaks but let’s not bash the writer, I’m not intending to do so).

(update) b.t.w. in the attachment temp.zip you'll find full source code for the sample. Note that Active Directory has a bug that makes custom WKGUIDs useless. It simply cannot find your custom WKGUID. I reported this bug to Microsoft.

How could you ever figure that WKGUID and it’s query syntax is related to the following attribute: ‘otherWellKnownObjects’?J.

This sort of documentation, it's complexity in Active Directory on LDAP makes it hard to see any utilization for this feature.

I hope to support MS-es Active Directory (that I love, just as IIS and SQL server) usage and ease in practice by posting the following sample code.

Note that this code is not ‘well-formed’ and ‘error-proof’ but it works and gives you an idea.

The logging (debug feature) is done through the logging module posted just before this posting.

Now how do we profit from this feature?

Imagine you have created through an LDIF or through code a container object

cn=our CompanyData,cn=Program Data,dc=nwtraders,dc=msft

1)      Now we associate ‘our CompanyData’ with the ‘otherWellKnownObjects’ property using a custom hexadecimal guid  81a3469a653959419309b33e27bdd2c9”!

2)      Eg: WriteADSAttributeDnWithString([ourcompnayCN], L”cn=our Company Data,cn=Program Data,dc=nwtraders,dc=msft”, ourGUID, ADS_ATTR_APPEND

3)      Now you can query your company data like this, no matter your company is being renamed in futureJ, through the following syntax: LDAP://<WKGUID=8a3469a653959419309b33e27bdd2c9,cn=Program Data,dc=nwtraders,dc=msft>

Normally the dn for Program Data is WKGUID-ed by Microsoft as well! The Program Data container should be reliably formatted as L"LDAP://<WKGUID=%s,%s>", GUID_PROGRAM_DATA_CONTAINER_W, L”dc=nwtraders,dc=msft”)

Note: After you usage of the WKGUID or the GUID- syntax, you must resolve the fully qualified dn by using the ‘distinguishedName’ property and refetching your Actife Directory object before you go on with it.

 


Note: You would wish you could set the objectGUID attribute at creation of your object instead of the WKGUID. Unfortunately, this value is generated by Active Directory itself.)

STDMETHODIMP WriteADSAttributeDnWithString(

IADs *piADs,

PCWSTR dn,

PCWSTR guid)

{

      CComVariant v;

     CComPtr<IADsDNWithBinary> dnWithBin;

      CComBSTR assocDn(dn);

     dnWithBin.CoCreateInstance(CLSID_DNWithBinary);

      v.vt = VT_UI1 | VT_ARRAY;

hr = ConvertHexGuidToArray(guid, &v.parray);

      hr = dnWithBin->put_BinaryValue(v);

      hr = dnWithBin->put_DNString(assocDn);

CComBSTR attribute( L"otherWellKnownObjects");

     

v.Clear();

      v.vt = VT_DISPATCH;          

      dnWithBin.QueryInterface(&v.pdispVal);

      hr = pADs->Put(attribute, v);

      hr = pADs->SetInfo();

     

      return hr;

}

// converts hexadecimal string to a octet encoded byte array

STDMETHODIMP ConvertHexGuidToArray(PCWSTR hexGuid, LPSAFEARRAY *sa)

{

      if (hexGuid == NULL) return E_INVALIDARG;

      int hexLen = (int)wcslen(hexGuid);

      HRESULT hr = S_OK;

      CTempBuffer<CHAR> sHex(hexLen);

      int loopLen = hexLen;

      while(loopLen-- != 0)  

            sHex[loopLen] = (CHAR) hexGuid[loopLen];

      SAFEARRAY *temp= SafeArrayCreateVector(VT_UI1, 0, hexLen / 2);

      if (temp == NULL)

            return E_OUTOFMEMORY;

      PBYTE dst ;

      hr = SafeArrayAccessData(temp, (void**)&dst);

      int dstLen = hexLen /2;

      if (hr == S_OK)

      {

            AtlHexDecode(sHex, hexLen, dst, &dstLen );

            hr = SafeArrayUnaccessData(temp);

            *sa = temp; //return the array

      }

      return hr;

}

[ATL, CPP] How to log to a machine global logfile from different threads and processes.0 Reacties

Each self-respecting programmer and software team uses logging features.

In my current job on ASP.NET, each click, every navigation and message is logged. Very handy indeed because you would like to support your customers.

 For a classic C++ project this is not so easy to do. You can use ATLTRACE2 or AtlTrace and these macro's have the advantage that at release time, they won't stuff your code with unnecessary statements.

However, in case you have a support incident on your code that runs with a client, you'll have to add tools or to deliver windbg.exe to read those statements and to hand over a debug dll or exe. And you have to instruct your customers to save the log to a file. I'm not happy at this of course, I might be mistaken and some C++ expert tells me how easy this could be done in fact. ;).

So I wrote such a class. It is tested on several processes (as asp can do, several w3wp.exe processes can load your COM dll) that write to a single log file.

So what do we need to care for?

  1. Log to a single file from one or more processes that load my specific COM dll
  2. Log to 1) Debug Output 2) a file 3) both, 4) none
  3. If logging is switched off, it must not cause performance degration.
  4. If we write to the file, we must synchronize, we use a Mutex.
  5. Our code that utilizes this class, must (preferable) access a singleton instance of our class per DLL

Here comes the source...

class LoggingModule

{

public:

      LoggingModule() ;

      ~LoggingModule();

      //releases resources

      void Close(bool deleteLogFile = false);

      void Write(PCWSTR pszFormat, ...);

      void set_Logging(int enable);

      // 0 = %windir%\Temp, 1=%userprofile%\Local Settings\Temp

      void set_TempLocation(int location);

      int get_Logging();

private:

      CAtlFile m_file;

      //0 disabled 1 = to file, 2 = to Debug 3 = both

      int m_LoggingEnabled;

      int m_tempLocation;

      HANDLE m_hMutex; 

      CComBSTR m_MutexName ;

      //member variable, so we enable BSTR caching. A lot more efficient

      CComBSTR m_bstrTrace, m_logFileName, m_fmt;

};
extern LoggingModule logModule;

// implementation of LoggingModule

LoggingModule::LoggingModule(void) throw() :

      m_LoggingEnabled(0),

      m_hMutex(NULL)

{

}

void LoggingModule::set_TempLocation(int location) throw()

{

      m_tempLocation = location;

}

void LoggingModule::set_Logging(int enable) throw()

{

      m_LoggingEnabled = enable;

      if ((m_LoggingEnabled & 1) == 1)

      {    

            m_logFileName.SetLength(MAX_PATH);

     

            DWORD bufLen = GetModuleFileNameW(_AtlBaseModule.GetModuleInstance(), m_logFileName, MAX_PATH);

            PathStripPathW(m_logFileName);

            PathRenameExtensionW(m_logFileName, L".LOG");

            //strip right length

            m_logFileName.SetLength((unsigned int)wcslen(m_logFileName));

            m_MutexName = L"Global\\";

            m_MutexName += m_logFileName;

            CComBSTR buf(MAX_PATH);

            if (m_tempLocation == 0)

                  bufLen = GetSystemWindowsDirectoryW(buf, MAX_PATH);

            else

                  bufLen = GetTempPathW(MAX_PATH, buf);

            if (bufLen == 0)

            {

                  buf.Format(L"error %d\n", GetLastError());

                  OutputDebugStringW(buf);

                  m_logFileName.Empty();

                  m_LoggingEnabled ^= 1; //disable file logging

                  return;

            }

            else

            {

                  //strip right length again

                  buf.SetLength(bufLen);

                  buf += L"\\TEMP\\";

                  buf += m_logFileName;

                  // now m_LogFileName looks like C:\Windows\Temp\myDll.Log

                  m_logFileName.Attach(buf.Detach());

            }

            //Create EveryOne in a language independend way

            SID_IDENTIFIER_AUTHORITY sia = {SECURITY_WORLD_SID_AUTHORITY};

            CSid everyone(sia, 1, SECURITY_WORLD_RID);

            // we must use a Security Descriptor, otherwise other w3wp.exe processes cannot

            // inherit the mutex!

            CSecurityDescriptor secDescr;

            // ATL does not initialize this object at construction??

            HRESULT hr = secDescr.Initialize();      

            if (hr == S_OK) hr = secDescr.Allow((PSID)everyone.GetPSID(), MUTANT_ALL_ACCESS);     

            if (FAILED(hr))

            {

                  buf.Format(L"secDescr.Allow failed with %x\n",hr);

                  OutputDebugStringW(buf);

                  m_LoggingEnabled ^= 1;

                  return;

            }

            SECURITY_ATTRIBUTES SecAttrs = {sizeof(SecAttrs), secDescr, TRUE};

            // create the mutex without owning it.

            // if another process (w3wp) already did the creation

            // we just ignore the error and m_hMutex stays nill

            m_hMutex = CreateMutexW(&SecAttrs, FALSE, m_MutexName);

            if (m_file == NULL  || m_logFileName.IsEmpty())

            {

                  hr = m_file.Create(m_logFileName,

                        FILE_GENERIC_WRITE,

                        FILE_SHARE_READ | FILE_SHARE_WRITE,

                        OPEN_ALWAYS);

                 

                  if (SUCCEEDED(hr))

                  {

                        ULONGLONG sz;

                        if (SUCCEEDED(m_file.GetSize(sz)))

                        {

                              //it's a unicode-8 logfile

                              if (sz == 0)

                              {

                                    BYTE PreAmble[] = {0xEF, 0xBB, 0xBF};

                                    m_file.Write(PreAmble, sizeof(PreAmble));                  

                              }

                              //TODO: finish

                              else if (sz > 10000)

                              {

                                    //truncate first 5000 lines and restart

                              }

                              m_file.Seek(sz);

                        }

                  }

Kerberos Negotiate authentication on IIS sucks0 Reacties

I just had to say  it! Yes, I was frustrated.

Negotiate (through kerberos) on IIS sucks!
I love free speech, especially if something said is simply true (Joe Kaplan agreed with me)! Of course, this is not an excuse to be rude to MS employees :), but they never would improve things, if we don't ask for it right?

Why is Kerberos on IIS such a pain?
I need an Intranet site, running ASPX to utilize integrated security. That's nice. So, I don't have to maintain another user database (bleeh, not again). Secondly, for my AD solutions, I just needed full security based on AD, and (again) not my own security mechanism.
I hear you say: "What this fuzz about? It just works? Yes, it just works -as long as the credential on the current browser-IIS session- does not need to go any further than a page. For SQL Server, most developers, still would use standard security (and not pass-through authentication, which needs kerberos).
But for Exchange-, ADSI-, or Active Directory enabled pages, pages that -really do something- with Active Directory, IIS and Kerberos really need a professor (Joe Kaplan, but his fee is too high for me :) ) in Windows-kerberos-science.

Well, euh, it's easy, just 3 things to check!

there is

  1. AD (Active Directory) support (does AD trust IIS+host for delegation?)
  2. IIS - support (does IIS support Negotiate?)
  3. Browser support (do IE support Negotiate and integrated logon?)

And between the lines, you need to make sure (such as using setspn.exe) that it all is going to work. To get this done and configured, you'd better not be a third party intranet supplier!

Microsoft, please, fix this. I mean, make it more easy for us.

Let me just enumerate some knowledgebase articles that give some checklists. (See knowledge base articles below, note there are much more articles about this subject!)

For a script, yummy for you, you might not have found this script before, because I'm just good :). What it does, is checking (enabling) the IIS server delegation. Note, using it is for your own risk!
(ps: My setup scripts are preferable VBS because that just works without any version messup).


What it does is actually shown in the picture. It checks the middle option.
Delegation for IIS needed for Negotiate work.

update at 14 sep 2006: Some companies, have disabled netbios features and computer browsing. So they solely have DNS and AD, but this makes IIS fail to use negotiate as well. It just seems that IIS has a lot of legacy network component dependencies.

' this checks this IIS server, to be trusted

' otherwise, Negotiate (Wdigest) through kerberos will -not- work!

' if you don't like all protocols to be trusted, you should manually correct this property

'  for this IIS server.

Sub SetThisIISServerTrusted()

    Dim nt, ds, ldapHost, info, accInfo, dnMe

    Const ADS_NAME_INITTYPE_SERVER = 2, ADS_NAME_TYPE_NT4 = 3, _

        ADS_NAME_TYPE_1779 = 1, ADS_UF_TRUSTED_FOR_DELEGATION = &H80000   

   

    Set nt = CreateObject("NameTranslate")

    Set info = CreateObject("WinNTSystemInfo")

    Set ds = GetObject("LDAP://rootDSE")

    ' the AD server that is serving us must be authorative enough

    ldapHost = ds.Get("dnsHostName")

   

   nt.Init ADS_NAME_INITTYPE_SERVER, ldapHost

   ' computer name is by definition the netbiosname plus a $

   nt.Set ADS_NAME_TYPE_NT4, info.DomainName + "\" + info.ComputerName + "$"

  

   dnMe = nt.Get(ADS_NAME_TYPE_1779)

   Set ds = GetObject("LDAP://" + dnMe)

   accInfo = ds.Get("userAccountControl")

   accInfo = accInfo Or ADS_UF_TRUSTED_FOR_DELEGATION

   ds.Put "userAccountControl", accInfo

   On Error Resume Next

   ds.SetInfo

   If Err.Number > 0 Then

       WScript.Echo " Error at SetThisIISServerTrusted:" + Err.Description + ":" + Err.Source + ":" + Hex(Err.Number)

   End If

   On Error Goto 0

   Set ds = Nothing

End Sub

 

PRB: "Access Denied" Error Message When Using ServerXMLHTTP to Access an Authenticated Site
ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.KB.v10.en/enu_kbmsxmlkb/msxmlkb/291008.htm

Enable negotiate on IE6
ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.KB.v10.en/enu_kbie/ie/299838.htm

Troubleshoot IIS negiotiate 326985
http://www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/security/tkerbdel.mspx


Enable negotiate on IIS
ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.KB.v10.en/enu_kbiis/iis/215383.htm

Enable negotiate using XMLHttp
ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.KB.v10.en/enu_kbmsxmlkb/msxmlkb/314404.htm

http://support.microsoft.com/kb/326985/

 

 

Boost BSTR performance for free, by 3000%0 Reacties

Sometimes, I am willing to disclose :) some secrets of real performance. Even in the .NET world, we can't avoid BSTR allocation, and in the unmanaged coding world, automation and COM will be at our path once more.

In my honest opinion, many, many programmers make or made the mistake, in the unmanaged world, of not reallocating resources, instead, they just destroy the resource, and allocate it again. MS tried to fix this 'bug' by caching allocations, as much as possible. I find this decision, to start caching allocations, I mean for BSTR allocation, not a good decision. This must be one of the reasons, that COM in a multitasked, MPS environment sometimes, simply cannot scale!

I'll explain why. In a single user environment, caching data for a thread is a good idea, since say MS Word, and scripts like in VBA and VBS, might reuse data/allocations. But as soon as our ASP/COM server environment starts to do this, and the code is reentrant, caching is useless, since threads that allocated data, might not be allowed to reuse zombie-data (if a caching-pattern is used) from another thread.

It could have been solved so easily! (Now, I might sound presumptuous to say that, I agree) How? Just don't cache but reallocate!

VB6 and automation clients for instance, uses the BSTR datatype all over the place, and ATL when used in a COM environement, does as well. If you look at the compiled code that programmers deliver, they never reallocate (only the runtime does sometimes). So for instance myString = "Hello" and myString = "bye" could have been compiled internally by:
myString = SysAllocString(L"Hello");
SysReAllocString(myString, L"bye");

And that's really all!
SysReAllocString(Len) internally uses CoTaskMemRealloc and that function tries to enlarge the memory allocation in-place, and this at its turn minimizes RAM-synchronization in MPS systems on the CPU.

So far, my theory. Am I just filling up your internet-html disk-cache and chit-chatting because I'm just idle for an hour? No.

Let's just try this out!

I've rewritten CComBSTR (from the ATL namespace) and you can find this in the platform SDK at \PlatSDK\Include\atl\atlbase.h (in case you have Visual Studio 2005, don't use this location, but use the most recent header files).

This silly little program does nothing but appending random (sort of) wide strings to a BSTR allocation. Let's rewrite CComBSTR and measure it!

I assume that you can get the headers right to get the program below compile and run.

int _tmain()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
{

_tmain()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
{

CComBSTR appendPlay;
DWORD timer = GetTickCount();
// Ethan Winer, an Assembly coding specialist, once thought me that loops counting down to zero are faster, this still is the case!, just a silly fact.

for(int xy = 10000; xy > 0; xy--)
{
   PWSTR zy = xy % 2 == 0 ? L"hiya" : L"bye";

      appendPlay.Append(zy);
}
wprintf(L"speed %d\n", GetTickCount() - timer);
}
CoUninitialize();
}

Now run the code and on my AMD 3200+ system, this takes 578 time ticks. This is even with the OLE BSTR cache enabled! (When caching is disabled, this takes 520 time ticks).

Let's improve the Append part of CComBSTR (make sure you keep the original atlbase.h intact). In my case, I just redefined CComBSTR to CComBSTR2 and copy-pasted all of it and rewrote the slow parts.

The slow original code is using the 'delete' 'allocate' sequence.
// very slow original
HRESULT __stdcall Append(LPCOLESTR lpsz, int nLen) throw()

{

if(lpsz == NULL)

      {

if(nLen != 0)

return E_INVALIDARG;

else

return S_OK;

}

                      

int n1 = Length();

if (n1+nLen < n1)

return E_OUTOFMEMORY;

BSTR b;

b = ::SysAllocStringLen(NULL, n1+nLen);

if (b == NULL)

return E_OUTOFMEMORY;

if(m_str != NULL)

memcpy(b, m_str, n1*sizeof(OLECHAR));

                      

memcpy(b+n1, lpsz, nLen*sizeof(OLECHAR));

b[n1+nLen] = NULL;

SysFreeString(m_str);

m_str = b;

return S_OK;

}

And here goes the improved code. It has the Automation runtime resize the BSTR while the string in most cases remains at the same memory address. This is how the original BSTR programmers have designed for performance, while nobody is utilizing it! But we instead, do use it, as you understand.

HRESULT __stdcall Append(LPCOLESTR lpsz, int nLen) throw()

{

      if (lpsz == NULL || (m_str != NULL && nLen == 0))

            return S_OK;

      int n1 = Length();

      HRESULT hr = SetLength(n1 + nLen);

      if ( SUCCEEDED(hr) )       

         memcpy(m_str+n1, lpsz, nLen*sizeof(OLECHAR));

      return hr;

}


We need to append the _SetLength function, which is a static wrapper for SysReAllocStringLen(..)

// Cuts the length to specified but does not clear contents

HRESULT __stdcall SetLength(unsigned int length) throw()

{

      return _SetLength(&m_str, length);

}

static HRESULT __stdcall  _SetLength(BSTR * str, unsigned int length) throw()

{

      return ::SysReAllocStringLen(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;

}

I've included the full 99% compatible CComBSTR2 replacement for you, as a handy dowload so bother about that later. :)

Now, get me to the results please, how much faster would this code run now?

Yes, it takes a whopping 15 milliseconds! And figure that, against 578 milliseconds, which makes the improvement 3800%

// Cuts the length to specified but does not clear contents

HRESULT __stdcall SetLength(unsigned int length) throw()

{

      return _SetLength(&m_str, length);

}

static HRESULT __stdcall  _SetLength(BSTR * str, unsigned int length) throw()

{

      return ::SysReAllocStringLen(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;

}

I've included the full 99% compatible CComBSTR2 replacement for you, as a handy dowload so bother about that later. :)

Now, get me to the results please, how much faster would this code run now?

Yes, it takes a whopping 15 milliseconds! And figure that, against 578 milliseconds, which makes the improvement 3800%

Now you might understand why .NET had to be invented by MS :) they figured that the maximum scalability limit was hit on real MPS systems, and that COM never could perform the task of being scalable just because the BSTR sucks in performance! And now, you know that, I'm just now on the conspiracy path, and I'm lying :-).

Anyway, the conclusion is, that current COM clients, and applications and servers, could, if they would like to, improve a lot for free, by just removing the BSTR reallocation barrier and take advantage of the maximum 'unmanaged code'  speed possible on a Windows (r) System.

The non-conspiracy theory is that caching was made in times, when Microsoft did not play a big role in server environments, and that computers were relativily slow. A conclusion would be that if MS would like to improve an old car (COM) for free, they'd just remove the caching and implement the idea that I've proven to be very good. This would be good for classic ASP pages as well that are still very popular on the internet.

Just a final question. Why bother? If you are an Automation developer, creating services that depend heavily on BSTRs?
The answer is, I bothered once a while ago, just because of some artistic feeling (good programmers are artists, not scientists :) ) that I could improve the enormous BSTR stress on my product here
Isp Session Manager. Of course, the biggest part of such managed COM server (as COM+ was called in the past!) is doing talking to a DBMS. But after implementing the CComBSTR replacement, the performance on a MPS server, suddenly got very easy and the pages per second throughput went up and showed a flat line (that's the wet dream of each webmaster). Before, without using the CComBSTR replacement the throughtput was erratic, so this again proved my point, that not-reallocating BSTRs causes a huge demand on RAM synchronization and makes scalability limited because of wrong usage of the COM runtime.

Here, you got the CComBSTR whopper for your own downloads. Please do not forget to deploy it after download...

[edit, nov 2008] It seems that attached links on this BLOG are not supported anymore.

Therefore, the sourcecode inline.

#pragma once

#ifndef CComBSTR

#define CComBSTR CComBSTR2

#endif

#define CComVariant CComVariant2

namespace ATL

{

//typedef  HRESULT  (__stdcall*HASHDATA2) (LPBYTE, DWORD, LPBYTE, DWORD);

/////////////////////////////////////////////////////////////////////////////

// CComBSTR2

class CComBSTR2

{

public:

      BSTR m_str;

      CComBSTR2() throw()

      {

            m_str = NULL;

      }

      CComBSTR2(_In_ int nSize)

      {

            //if (nSize == 0) //BUG it should be possible to assign a L"" string

            m_str = NULL;

            HRESULT hr = SetLength(nSize);

            if (FAILED(hr))

                  AtlThrow(hr);

            ZeroMemory(m_str, nSize * sizeof(wchar_t));

           

      }

      CComBSTR2(_In_ int nSize, _In_opt_count_(nSize) LPCOLESTR sz)

      {

            if (nSize == 0)

                  m_str = NULL;

            else

            {

                  m_str = ::SysAllocStringLen(sz, nSize);

                  if (m_str == NULL)

                        AtlThrow(E_OUTOFMEMORY);

            }

      }

      CComBSTR2(_In_opt_ LPCOLESTR pSrc)

      {

            if (pSrc == NULL)

                  m_str = NULL;

            else

            {

                  m_str = ::SysAllocString(pSrc);

                  if (m_str == NULL)

                        AtlThrow(E_OUTOFMEMORY);

            }

      }

Howto: Dynamically Create Gradient Buttons using ASPX and .NET0 Reacties

If you ever might want cute buttons, independently of your clients browser and theme, you might create buttons by using some dynamic GDI based code.

 

I found this solution, which costed just 2 hours programming not bad at all, and you might profit from it as well.

 

This code, automatically wraps text, all using .NET power. It even can be improved, to support hover as well. B.t.w. on Windows Vista XAML supports this but we’re still on the internet man!

So why not make our own buttons.

 

Paste code below, onto an empty ASPX page (for instance button.aspx), and call use it through the following arguments (other tag arguments omitted for simplicity).

 

<asp:ImageButton id=”myButton” ImageUrl=Button.aspx?t=Hello%20World&w=150&h=35” runat=”server”/>

 

Syntax below is based on C# 2.0 

 

The output might look like this(appConfig must contain the buttoncolors)

gradientButton

 

public partial class button : System.Web.UI.Page

{

    private static Color parsRgb(string rgbColor)

    {

        int red, green, blue;

 

        red = int.Parse(rgbColor.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);

        green = int.Parse(rgbColor.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);

        blue = int.Parse(rgbColor.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);

 

        return Color.FromArgb(red, green, blue);

    }

    protected void Page_Load(object sender, EventArgs e)

    {

        System.Collections.Specialized.NameValueCollection req= Request.QueryString;

       

        //get height

        string h=req["h"];

        //get width

        string w = req["w"];

        // get text

        string t = req["t"];

        if (t == null || t.Length == 0)

            throw new HttpException("QueryString  t not supplied");

        int hashCode = t.GetHashCode();

        string checkEtag = Request.Headers["ETag"];

        if (checkEtag != null && checkEtag.Length != 0)

        {

            if (checkEtag == hashCode.ToString())

            {

                Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;

                Response.StatusDescription = "Not modified";

                Response.SuppressContent = true;

                return;

            }

        }

     

        req= System.Web.Configuration.WebConfigurationManager.AppSettings;

 

        string colrRadiantStart = req["btnBgRadiantStart"];

        string fontSize = req["btnFontSize"];

        string FontName = req["btnFontName"];

        string colrRadiantEnd = req["btnBgRadiantEnd"];

        string FontColor = req["btnFontColor"];

       

        System.Drawing.Color ocolorEnd, colorStart, colorFont ;

 

        if (colrRadiantStart != null && colrRadiantStart.Length == 6)

            colorStart = parsRgb(colrRadiantStart);

        else

            throw new HttpException("You must provide a valid btnBgColor hex rgb-code in appConfig");

 

        if (colrRadiantEnd == null || colrRadiantEnd.Length != 6)

            throw new HttpException("You must provide a valid btnBgColor hex rgb-code in appConfig");

        else

            ocolorEnd = parsRgb(colrRadiantEnd);

        if (FontColor == null || FontColor.Length != 6)

            colorFont = Color.Black; //default

        else

            colorFont = parsRgb(FontColor);

 

        int iH = int.Parse(h);

        int iW = int.Parse(w);

        Bitmap oBmp1 = new Bitmap(iW, iH);

        Graphics oGrp1 = Graphics.FromImage(oBmp1);

       

        // seems not to have effect

        oGrp1.CompositingQuality = CompositingQuality.HighQuality;

        oGrp1.InterpolationMode = InterpolationMode.HighQualityBilinear;

 

        LinearGradientBrush lgb = new LinearGradientBrush(new Rectangle(0, 0, iW, iH), colorStart, ocolorEnd, LinearGradientMode.Vertical);

        oGrp1.FillRectangle(lgb, 0, 0, iW, iH);

 

        System.Drawing.FontFamily fntFam = new FontFamily(FontName);

 

        Font fnt = new Font(fntFam, float.Parse(fontSize));

        StringFormat stringFormat = StringFormat.GenericDefault;

         //we must set this value, other wise text will default to left.

        stringFormat.Alignment = StringAlignment.Center;

        SizeF szf = oGrp1.MeasureString(t, fnt, new SizeF(iW, iH), stringFormat);

        //center

        PointF FPoint = new PointF((iW - szf.Width) / 2, (iH - szf.Height) / 2);

 

        oGrp1.DrawString(t, fnt, new SolidBrush(colorFont), new RectangleF(FPoint.X, FPoint.Y, szf.Width, szf.Height), stringFormat);

 

 

 

        Response.ContentType = "image/jpeg";

        Response.Cache.SetETag(hashCode.ToString());

        //Response.Cache.SetLastModified(DateTime.Now);

        Response.Cache.SetCacheability(HttpCacheability.Public);

        oBmp1.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);               

        Response.End();

 

}

}

Howto: detect whether or not IIS (any version) is installed?0 Reacties

There are several tricks but I've not seen this one so I would like to mention this trick as well.

The idea is that IIS can be managed through ADSI interfaces right? You would use (in vbscript)

Sest myIIS = GetObject("IIS:") and now you have the namespace. But if IIS is not installed, it would return 'invalid syntax'. Not really a good error.
A cleaner method would be
Set myIIS = CreateObject("IIS")

This way, you use the progid that the IIS namespace, normally is fetched by a url moniker and GetObject().

so complete code:
Dim myIIS, isIISInstalled
On Error Resume Next
Set myIIS = CreateObject("IIS")
'ActiveX component cannot create object: 'IIS'
isIISInstalled = Err.Number <> &H800A01AD
On Error GoTo 0

You can apply the concept above to -any- language, including javascript, VB6, C# and VB.NET.

 


© 2018 - 2019 Adc Cure