Technolog

Blogging over technologie.
Welcome to Technolog Sign in | Join | Help

Front Page News

  • Howto: Create a custom numeric pager for the ASP.NET Gridview Control

    Figure 1: Our custom pager in action!

    I never have liked the concept of storing all the data in whatever form (DataTable/Lists of records/etc.) to the ASP.NET gridview control and having it automatically manage paging for me. This could however be improved using Visual Studio 2008 wizards. However, this requires writing stored procedures.

    I’ve got a concept for you, which works without a lot of extra work. The concept is:

    1. Inherit from asp:GridView and override the PageCount and the PageIndex properties
    2. Create in instance of my CustomPager class at DataBinding time.

    The result is as shown in figure 1: It adheres to PageButtonCount, to any styles that you have applied to the GridView and it features a ‘jump to’ page input box.
    The ‘native’ event handling in your ASPX, still can be maintained by this code since it emulates the PageIndexChanging event.

    Other solutions, implement an AJAX updatepanel per row. This really minimizes unnecessary refreshing of grid data.
    However, I don’t mind if say, 30 rows are being pulled from a DB and bound to a GridView. If we do so, we also get the best of two worlds, in one world, we get all data and have a fresh update of the real table rows, and in the other world, we have just the active row being fetched and we could end up having outdated data on our screen because of (for instance) co-writers/typers who updated rows at the database which are not reflected at our screen in grid (which is displayed using a gridview).

    So, in other words, I like this pager control since it is a balanced solution! It has been tested on 10,000 records. WHen I would page to for instance, page 900, it really is a matter of fractions of a second to get a response. (For paging solutions  on SQL server data tables which contain say millions of rows, we would need a more sophisticated approach).

    Here is the control source (it’s called gridhelper.cs)! (In theory, it should work for .NET 2.0 up to 3.5)

    // if you use this code, please leave the original author
    //author: Egbert Nierop
    using System;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    namespace adccure.tools
    {
        public sealed class GridView2 : GridView
        {
            public GridView2()
                : base()
            {
            }
            private int _pageCount;

            public override int PageIndex
            {
                get
                {
                    object o = ViewState["pgi"];
                    return o == null ? 0 : (int)o;
                }
                set
                {
                    ViewState["pgi"] = value;
                }
            }

            public override int PageCount
            {
                get
                {
                    return _pageCount;
                }

            }
            public void PageCountSet(int pageCount)
            {
                _pageCount = pageCount;
            }
        }

        public sealed class CustomPager : ITemplate
        {
            readonly Table tbl;
            readonly GridView2 _grid;

            //readonly int _totalPages;
            public CustomPager(GridView2 grid, int totalPages)
            {
                tbl = new Table();
                grid.PageCountSet(totalPages);
                tbl.Width = Unit.Percentage(100);
                _grid = grid;
            }
            void ITemplate.InstantiateIn(Control container)
            {
                container.Controls.Add(tbl);

                int pageSize = _grid.PageSize;
                int pageIndex = _grid.PageIndex;
                int pageButtonCount = _grid.PagerSettings.PageButtonCount;
                int pageCount = _grid.PageCount;
                ClientScriptManager cs = _grid.Page.ClientScript;
                _grid.PagerSettings.Visible = true;
                var trow = new TableRow();
                var trowpagePosition = new TableRow();
                tbl.Rows.Add(trow);
                tbl.Rows.Add(trowpagePosition);
                TextBox tb = new TextBox();
                tb.ID = "txtJumpPage";
                tb.MaxLength = 4;
                tb.Width = Unit.Pixel(40);
                tb.Text = (pageIndex + 1).ToString();
                //avoid bubble up by return false
                tb.Attributes["onkeydown"] = string.Format("if (event.keyCode==13) {__doPostBack('{0}', 'Page$' + this.value ); return false;}", _grid.UniqueID);

                LiteralControl lit = new LiteralControl(string.Format(" of {0}", (pageCount + 1).ToString()));
                TableCell posCaption = new TableCell();
                trowpagePosition.Cells.Add(posCaption);
                posCaption.Controls.Add(tb);
                posCaption.Controls.Add(lit);
                int cellspan = 0;
                if (pageIndex > 0)
                {
                    var cellText = new TableCell();
                    trow.Cells.Add(cellText);
                    cellspan++;
                    cellText.Controls.Add(new HyperLink()
                    {
                        NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                        string.Format("Page${0}", pageIndex - pageButtonCount >= 0 ? (pageIndex - pageButtonCount) + 1 : 1), false),
                        Text = "<"
                    });
                }
                for (int x = pageIndex; x < pageIndex + pageButtonCount && x <= pageCount; x++)
                {
                    var cellText = new TableCell();
                    cellspan++;
                    trow.Cells.Add(cellText);
                    cellText.Controls.Add(new HyperLink()
                    {
                        NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                            string.Format("Page${0}", x + 1), false),
                        Text = (x + 1).ToString(),
                    });

                }
                if (pageIndex + pageButtonCount < pageCount)
                {
                    var cellText = new TableCell();
                    cellspan++;
                    trow.Cells.Add(cellText);

                    cellText.Controls.Add(new HyperLink()
                    {
                        NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                        string.Format("Page${0}", (pageIndex + pageButtonCount) + 1), false),
                        Text = ">"
                    });
                }
                tbl.Visible = true;
                posCaption.HorizontalAlign = HorizontalAlign.Center;
                posCaption.ColumnSpan = cellspan;
            }
        }
    }

    Now, I don’t publish the code to read from sample data (such as northwind), it would be a yadda, yadda and you know the drill. (In my code, it is just a silly HttpReferrer table having all the columns which allow you research this specific statistical interest of your web site)
    But in my datalayer, I have things like shown below. It is a great solution for those tables, for say, less than 100,000 records. SQL server is able to deal with these types of queries pretty well and we still avoid pumping around lots of redundant data on the network and gridview control.

    public IList<HttpReferrer> getHttpReferrers(int pPage, int pPageSize, HttpReferrerSortOrder sortOrder,
                    SortDirection sortDirection,
                    out int totalRecords)
            {
                totalRecords = dcd.HttpReferrers.Count();
                IQueryable<HttpReferrer> retVal = null;
                if (sortDirection ==  SortDirection.Ascending)
                {
                    switch (sortOrder)
                    {
                        case HttpReferrerSortOrder.Referer:
                            retVal = dcd.HttpReferrers.OrderBy(t => t.Referrer);
                            break;
                        case HttpReferrerSortOrder.IP:
                            retVal = dcd.HttpReferrers.OrderBy(t => t.IP_Address);
                            break;
                        case HttpReferrerSortOrder.Page:
                            retVal = dcd.HttpReferrers.OrderBy(t => t.page);
                            break;
                        default:
                            retVal = dcd.HttpReferrers.OrderBy(t => t.ts);
                            break;
                    }
                }
                else
                {
                    switch (sortOrder)
                    {
    case HttpReferrerSortOrder.Referer:
                            retVal = dcd.HttpReferrers.OrderByDescending(t => t.Referrer);
                            break;
                        case HttpReferrerSortOrder.IP:
                            retVal = dcd.HttpReferrers.OrderByDescending(t => t.IP_Address);
                            break;
                        case HttpReferrerSortOrder.Page:
                            retVal = dcd.HttpReferrers.OrderByDescending(t => t.page);
                            break;
                        default:
                            retVal = dcd.HttpReferrers.OrderByDescending(t => t.ts);
                            break;
                    }
                }
                return retVal.Skip(pPage * pPageSize).Take(pPageSize).ToList();
            }

    So, our Grid, can sort and it can page.

    How do we deal with the paging event at the code behind the aspx?
    It’s so simple!

    void gridHttpReferrers_PageIndexChanging(object sender, GridViewPageEventArgs e) 

    gridHttpReferrers.PageIndex = e.NewPageIndex; 
    gridHttpReferrers.DataBind(); 
    }

    void gridHttpReferrers_DataBinding(object sender, EventArgs e)

    {   int totalRecords; 
       int pageSize = gridHttpReferrers.PageSize; 
       int pageIndex = gridHttpReferrers.PageIndex; 
      gridHttpReferrers.DataSource = datadal.getHttpReferrers(pageIndex, pageSize, SortOrder, SortDir, out totalRecords); 
       gridHttpReferrers.PagerTemplate = new CustomPager(gridHttpReferrers, totalRecords); 
       gridHttpReferrers.PageCountSet (totalRecords / pageSize);
    }

    What did I do to get the new grid behavior inside the ASPX?
    Just rename the tag from asp:GridView to ctrl:GridView2 and create the reference (or in web.config)

    <%@ Register TagPrefix="ctrl" Namespace="adccure.tools" %>

    <ctrl:GridView2 runat="server" emptydatatext="No data available."  ID="gridHttpReferrers" AllowPaging="true" AllowSorting="True"  Width ="100%" AutoGenerateColumns="false" PageSize="20" DataKeyNames="ID"><PagerSettings Position="Top" PageButtonCount="20" />
    <
    Columns> ETC.

    So, I hope this code was quite enlightning for you and you can play with it and have fun.

    10-28-2009, 21:51 by eprogrammer to Egbert Nierop technolog
    Filed under: , ,
  • Howto: Check the case sensitive existence of a file

    Sometimes, especially for files running on external FTP servers, where file names are case sensitive, a file named myFILE.html, is not the same file as myfile.html in the same path!

    This function, can be used on an NTFS path for that purpose where File.Exists would fail, because it is case insensitive. (However, it does not enable you to have two files with just a different case in the same path)

    /// <summary>
    /// Checks existance of file using a case sensitive compare
    /// </summary>
    /// <param name="file">must be full filename</param>
    /// <returns></returns>
    static bool FileExists(string file)

    {

    string pathCheck = Path.GetDirectoryName(file);

    string filePart = Path.GetFileName(file);

          if (string.IsNullOrEmpty(pathCheck))

          {

          throw new ArgumentException("The file must include a full path", file);

          }

          string[] checkFiles = Directory.GetFiles(pathCheck, filePart, SearchOption.TopDirectoryOnly);

          if (checkFiles != null && checkFiles.Length > 0)

          {

                //must be a binary compare

                return Path.GetFileName(checkFiles[0]) == filePart;

           }

           return false;

    }

     

    07-06-2009, 11:03 by eprogrammer to Egbert Nierop technolog
    Filed under: , ,
  • Howto: Properly use the AccessCheck API for the current user

    I've seen people pulling the hair out for not getting this API workign for them.

    The API, even if impersonating the current user, returns error 1309. "An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client."

    The clue is that this API, (and this is not clearly documented on the MSDN) needs a duplicated handle.

    Anyway, spare your hair, have fun with the code. B.t.w. You can hire me for smart code and research on components etc..

    http://www.adccure.nl for contact.

        [StructLayout(LayoutKind.Sequential)]

        internal struct GENERIC_MAPPING

        {

            internal uint GenericRead;

            internal uint GenericWrite;

            internal uint GenericExecute;

            internal uint GenericAll;

        }

        [DllImport("advapi32.dll", SetLastError = false)]

        static extern void MapGenericMask([In, MarshalAs(UnmanagedType.U4)] ref TokenAccessLevels AccessMask,

            [In] ref GENERIC_MAPPING map);

       

        [DllImport("advapi32.dll", SetLastError = true)]

        [return: MarshalAs(UnmanagedType.Bool)]

        public static extern bool DuplicateToken(IntPtr ExistingTokenHandle,

                [MarshalAs(UnmanagedType.U4)] TokenImpersonationLevel level,

                out int DuplicateTokenHandle);

        [DllImport("advapi32.dll", SetLastError = true)]       

         [return: MarshalAs(UnmanagedType.Bool)] static extern bool AccessCheck(

          [MarshalAs(UnmanagedType.LPArray)]

            byte[] pSecurityDescriptor, 

          IntPtr ClientToken,

          [MarshalAs(UnmanagedType.U4)]

            TokenAccessLevels accessmask,

          [In] ref GENERIC_MAPPING GenericMapping,

          IntPtr PrivilegeSet,

          ref int PrivilegeSetLength,

          out uint GrantedAccess,

          [MarshalAs(UnmanagedType.Bool)]

          out bool AccessStatus);

        [DllImport("kernel32")]

        static extern void CloseHandle(IntPtr ptr);

      internal static bool hasReadAccess(string path)

        {

            // Obtain the authenticated user's Identity

           

            WindowsIdentity winId = WindowsIdentity.GetCurrent(TokenAccessLevels.Duplicate | TokenAccessLevels.Query);

           

            WindowsImpersonationContext ctx = null;

            int statError = 0;

            IntPtr dupToken = IntPtr.Zero;

            try

            {

                // Start impersonating

                //ctx = winId.Impersonate(); works but AccessCheck does not like this

               

                int outPtr;

                //AccessCheck needs a duplicated token!

                DuplicateToken(winId.Token, TokenImpersonationLevel.Impersonation, out outPtr);

               

                dupToken = new IntPtr(outPtr);

                ctx = WindowsIdentity.Impersonate(dupToken);                

                Folder.GENERIC_MAPPING map = new Folder.GENERIC_MAPPING();

                map.GenericRead = 0x80000000;

                map.GenericWrite = 0x40000000;

                map.GenericExecute = 0x20000000;

                map.GenericAll = 0x10000000;

                TokenAccessLevels required = TokenAccessLevels.Query | TokenAccessLevels.Read | TokenAccessLevels.AssignPrimary | (TokenAccessLevels)0x00100000; // add synchronization

                MapGenericMask(ref required, ref map);

               

               

                uint status = 0;

                bool accesStatus = false;

                // dummy area the size should be 20 we don't do anything with it

                int sizeps = 20;

                IntPtr ps = Marshal.AllocCoTaskMem(sizeps);

               

                //AccessControlSections.Owner | AccessControlSections.Group MUST be included,

                //otherwise the descriptor would be seen with ERROR 1338

                var ACE = Directory.GetAccessControl(path,

                    AccessControlSections.Access | AccessControlSections.Owner |

                        AccessControlSections.Group);

               

                bool success = AccessCheck(ACE.GetSecurityDescriptorBinaryForm(), dupToken, required, ref map,

                        ps, ref sizeps, out status, out accesStatus);

                Marshal.FreeCoTaskMem(ps);

                if (!success)

                {

                    statError = Marshal.GetLastWin32Error();

                }

                else

                {

                    return accesStatus;

                }

            }

            // Prevent exceptions from propagating

            catch (Exception ex)

            {

                Trace.Write(ex.Message);

            }

            finally

            {

                // Revert impersonation

               

                if (ctx != null)

                    ctx.Undo();

                CloseHandle(dupToken);

            }

            if (statError != 0)

            {

                throw new Win32Exception(statError);

            }

           

            return false;

        }

    This code is just a cut and paste. You can make it pretty.

    06-18-2009, 23:50 by eprogrammer to Egbert Nierop technolog
    Filed under: , , ,
  • Fill a dropdownlist using the current UICulture in (ASP.NET)

    Just for educational purpose (as for myself as well :) ) I post this code.

    Using this, a programmer can use a good practise, that is to use the culture information which is built in, into .NET instead of making that data him/herself.

    based upon the following element in web.Config, the list will fill using the correct number of monthnames. It also keeps track of calendars, that have 13 months in some cultures.

    <globalization uiCulture="nl-nl"/>

    using System;

    using System.Collections.Generic;

    using System.Web;

    using System.Linq;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Threading;

    using System.Globalization;

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

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            int monthNumber = 0;

            CultureInfo ci = Thread.CurrentThread.CurrentUICulture;

            var myMonthnames = ci.DateTimeFormat.MonthNames

                .Take(ci.Calendar.GetMonthsInYear(DateTime.Today.Year)).Select(p => new { monthNo = ++monthNumber, monthName = p });

            ddlMonthnames.DataTextField = "monthName";

            ddlMonthnames.DataValueField = "monthNo";

            ddlMonthnames.DataSource = myMonthnames;

            ddlMonthnames.DataBind();

        }

    }

    <select name="ddlMonthnames" id="ddlMonthnames">

    <option value="1">januari</option>

    <option value="2">februari</option>

    <option value="3">maart</option>

    <option value="4">april</option>

    <option value="5">mei</option>

    <option value="6">juni</option>

    <option value="7">juli</option>

    <option value="8">augustus</option>

    <option value="9">september</option>

    <option value="10">oktober</option>

    <option value="11">november</option>

    <option value="12">december</option>

    </select>

    06-18-2009, 12:05 by eprogrammer to Egbert Nierop technolog
    Filed under: , ,
  • Performance and the future of LINQ expressions

    I was wondering if writing well known C# (or VB.NET if you wish) code flow statements, such as for .. and foreach etc. are faster or slower compared to a generic expression.

    The results are refreshing  Big Smile. At least using this simple array iteration. Array Iterations on millions of elements, of course, are not the the real life CPU eaters for an average ASP.NET website (eg), but consider this code.

    There are three loops, doing the same.  (n.b.: I run a dual core i7200 CPU machine on Vista x64)

    int ctr = 0;

    var values = new string[1000000].

            Select(p => p = (ctr++).

            ToString()).

            ToArray();

    List<int> intList;

    intList = values.Select(p => int.Parse(p)).ToList();

    int[] test1, test2, test3;

     // loop 10 times and calculate the average

    test1 = new int[10];

    test2 = new int[10];

    test3 = new int[10];

    for (int zz = 0; zz < 10; zz++)

    {

        // our millisecond counter

        // it's ok to run this test several times to get an average score

        int start = Environment.TickCount;

        // convert the numeric array back to an int array

        intList = values.Select(p => int.Parse(p)).ToList();

        test1[zz] = Environment.TickCount - start;

        //now do the same but using a foreach iteration

        start = Environment.TickCount;

        intList = new List<int>();

        foreach (var p in values)

        {

            intList.Add(int.Parse(p));

        }

        test2[zz] = Environment.TickCount - start;

       //do it a last time, but with a for{} iteration

        // theoretically a this should save us an enumerator.

        start = Environment.TickCount;

        intList = new List<int>();

        int z = values.Length; 

        for (int x = 0; x < z; x++)

        {

            intList.Add(int.Parse(values[x]));

        }

        test3[zz] = Environment.TickCount - start;

        Console.WriteLine("{0}, {1}, {2}", test1[zz], test2[zz], test3[zz]);

       

    }

    Console.WriteLine("{0}, {1}, {2}", test1.Average(), test2.Average(), test3.Average());

    Console.ReadKey();

    To test this, run this code in release mode (plus Ctrl-F5, in Visual Studio).

    x64 CPU platform results:
    Test 1) 175ms
    Test 2) 154 ms
    Test 3) 155 ms

    x86 CPU platform results:
    Test 1) 198 ms
    Test 2) 161 ms
    Test 3) 169 ms

    Test 1 uses an generic expression to 'cast'  a numeric string array to a List of type Int32.
    Cute line, isn't it?
    But unfortunately, as has been said, the LINQ expression, still is a bit slower than the non-linq versions.

    How much slower when we deal with integer math and avoid any parsing overhead?

    Now if we replace the int.Parse() statement by a silly integer operation, such as
    intList = intvalues.Select(p => p -1 ).ToList(); we have to increase the loopcount to 10,000,000 to get the workload to any significance. Now we measure plain LINQ performance.

    x64 results:
    Test 1) 269 ms
    Test 2) 125 ms
    Test 3) 128 ms

    x86 results:
    Test 1) 276 ms
    Test 2) 126 ms
    Test 3) 121 ms

    Conclusion:
    I expect over time that compilers become more and more smarter and can optimize even better LINQ expressions. 
    However, as we saw in the first example, int.Parse already flattened the results and the relative slowness of LINQ greatly.
    Parsing and converting data in loops, is something we constantly do when we deal with XML and databases. So when the workload within the loop increases, the overhead of LINQ expressions quickly become no important factor. 

    So, for shorter code, I would not hesitate to use those expressions as in Test 1.
    In a real life business application, the performance of loops really does not determine the final user experience. It is (e.g.) how we access a database or other resources, such as XML.

    It would be another story, if we e.g. were doing 3D math animations, where C++ would be an obvious choice.

     

    06-18-2009, 1:23 by eprogrammer to Egbert Nierop technolog
    Filed under: , , ,
  • Howto program a LINQ expression which effectively does an aggregate subquery with optional cardinal relationship

    LINQ! Yes, Also I fell in love with linq. 

    So here is my first try. And as you fans know, I like to dig into subjects.

    Maybe, the title is a little incorrect, but I wanted a query that returns one record, using a subquery in one statement!

    Using SQL syntax, this would look like the query just below.: Yes, It seems I can dream SQL, but it was shocking to see how I underestimated the LINQ syntax which took some extra hears from my head.

    The query returns events, that have not already been booked full. (There are still some places left)

    SELECT [t0].[id], [t0].[maxNumberofParticipants], [t0].[OwerId], [t0].[Description], [t0].[StartTime], [t0].[EndTime],
    [t0].[orderId] FROM [dbo].[Activity] AS [t0]
    WHERE ([t0].[OwerId] = @p0) AND ([t0].[maxNumberofParticipants] > @p1) AND ([t0].[maxNumberofParticipants] > (ISNULL((
        SELECT SUM([t2].[value])
        FROM (
            SELECT [t1].[countOfPersons] AS [value], [t1].[activityId]
            FROM [dbo].[ActivityParticipant] AS [t1]
            ) AS [t2]
        WHERE [t2].[activityId] = [t0].[id]
        ),0)))

    I could make this a stored proc, and execute this easily. But that was in my previous life.

    So, how to express this using a LINQ query?

    The trick with the ISNULL function is to -cast- the countOfPersons field, to a nullable type! Since the GetValueOrDefault() is available only to nullable data types we must cast it to an int? datatype.
    The LINQ provider, will translate it finally, when it sends the actual SQL to SQL Server using TSQL function 'COALESC'.
    bt.w., it will not use the ISNULL function. 

    public IEnumerable<Activity> getActivityParticipableByOwnerWith(Guid ownerGuid)
    {

    var query = from a in Activities
    where a.OwerId == ownerGuid && a.maxNumberofParticipants > 0 && a.maxNumberofParticipants >
    ActivityParticipants.Where(aid => aid.activityId == a.id).Sum(aid => (int?)aid.countOfPersons).GetValueOrDefault(0)
    select a ;
     

    return query;
    }

    Kuddo's to Khaled Moawad, who very patiently helped me to improve the syntax.  b.t.w. guys/girls. Always try it hard yourself, before you ask the global community for support. That makes your brains retain better :)

    My next goal is to make LINQ like a natural language for me, like SQL was :)

    03-18-2008, 23:02 by eprogrammer to Egbert Nierop technolog
    Filed under: , , , , ,
  • Howto loop through each element on a SafeArray with an unknown number of Dimensions?

    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

    3

    1

    7

    0

    2

    8

    1

    2

    9

    2

    2

    10

    3

    2

    11

    Of course, the algorithm, would go through any array, with any dimension.

    Now have much fun with this code, if you needed it in your code. Don’t forget to add error handling which I left out to keep it short. And sure, you could write some C# stuff, for your COM interop to deal with array marshaling as well.

    If this code was very useful for you, don’t forget to pay a visit to a component that uses this handy trick at http://www.nieropwebconsult.nl/asp_session_manager.htm

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

    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.

    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)

    12-25-2006, 11:35 by eprogrammer to Egbert Nierop technolog
  • HOWTO: Associate a custom WKGUID (well known guid) to a custom Container

    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.

    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);

                            }

                      }

                      else

                      {

                            buf.Format(L"Could not create %s, error %x\n", m_logFileName, hr);

                            OutputDebugStringW(buf);

                            // do not bother to write to the logfile

                            m_LoggingEnabled ^= 1;

                      }

                }          

          }

          else

                Close();

         

    }

    int LoggingModule::get_Logging() throw()

    {

          return m_LoggingEnabled;

    }

    LoggingModule::~LoggingModule(void) throw()

    {

          this->Close();

    }

    ///<sumarray> releases resources</summary>

    void LoggingModule::Close(bool deleteLogFile) throw()

    {

          m_file.Close();

          m_bstrTrace.Empty();

          if (m_hMutex  != NULL)

          {

                CloseHandle(m_hMutex);

                m_hMutex = NULL;

          }

          if (deleteLogFile && !m_logFileName.IsEmpty())

                DeleteFileW(m_logFileName);

          m_logFileName.Empty();

          m_fmt.Empty();

    }

    // this function writes the debugging information to the logfile

    // it synchronizes on a mutex so all processes go through this bottleneck

    // and will 'fight' for file access

    // be sure not to enable such a thing in production

    void LoggingModule::Write(PCWSTR pszFormat, ...) throw()

    {

          bool noFileAccess = false;

          HANDLE mutResult = NULL;

          if (m_LoggingEnabled == 0)

                return;

          else if ((m_LoggingEnabled & 1) == 1)

          {

                mutResult = OpenMutexW(MUTANT_ALL_ACCESS, FALSE, MUTEXNAME);

                if (mutResult != NULL)

                {

                      DWORD dwWaitResult = WaitForSingleObject(

                                  mutResult,   // handle to mutex

                                  5000L);   // five-second time-out interval

           

                      switch (dwWaitResult)

                      {

                            // The thread got mutex ownership.

                      case WAIT_OBJECT_0: break; //ok

                      // Cannot get mutex ownership due to time-out.

                      case WAIT_TIMEOUT:

                      // Got ownership of the abandoned mutex object.

                      case WAIT_ABANDONED:

                            OutputDebugStringW(L"error on LoggingModule::Write OpenMutex\n");

                            noFileAccess = true;

                            break;

                      }

                }

                else

                {

                      m_fmt.Format(L"coult not create mutex to %s err(%d)\n", m_logFileName, GetLastError());

                      OutputDebugStringW(m_fmt);

                      noFileAccess = true;

                }

          }

          va_list ptr;

          va_start(ptr, pszFormat);    

          m_bstrTrace.Format(pszFormat, ptr);

          va_end(ptr);

          SYSTEMTIME st ;

          GetLocalTime(&st);

          //prepend debugstring with thread and time.

          // eg 1040{tab}10:40:50:0599{tab}msg

          m_fmt.Format(L"%d\t%02d:%02d:%02d:%04d\t", GetCurrentThreadId(),

                      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);       

         

          bool appendCrLf = !m_bstrTrace.EndsWith(L"\n");

          m_bstrTrace.Insert(0, m_fmt);

          if ((m_LoggingEnabled & 2) == 2)

          {

                OutputDebugStringW(m_bstrTrace);

                if (appendCrLf)

                      OutputDebugStringW(L"\n");

          }

          // write only to the file if we have access

          if (m_file != NULL && !noFileAccess && (m_LoggingEnabled & 1) == 1)

          {

                ULONGLONG nCurLen;

                m_file.GetSize(nCurLen);

                m_file.Seek(nCurLen, FILE_BEGIN);

                if (appendCrLf)              

                      m_bstrTrace.Append(L"\r\n", 2);                      

               

                m_fmt.Attach(m_bstrTrace.ToByteString());

                m_file.Write(m_fmt, m_fmt.ByteLength());                 

                //clear buffer so we can be sure the buffer advances

                m_file.Flush();

                if (mutResult != NULL)

                {

                      //release ownership

                      ReleaseMutex(mutResult);

                      //close handle (because it is a duplicate!)

                      CloseHandle(mutResult);

                }

          }

    }

    // in your main CPP file you create -one instance- of this class.

    LoggingModule logModule;

    Remarks about the code.

    1. the CComBSTR code uses method that are not available by default. To use the super-power-CComBSTR replacement, see my other Blog posting about this class (expect over 3000% bstr performance improvements)
    2. This class minimizes file access by prebuffering the debug string.
    3. It will log to \%windir%\temp\nameofyourdll.log
    4. It writes the log as in utf-8 format.
    5. Fans of my code Smile already know that I have abandoned win9x compatibility. This code runs only WinNT and higher.
    6. It uses ATL. So if you use MFC for instance, you'll have to replace the CSecurityDescriptor class with an alternative or direct Win API equivalents (which I dislike for being so complex).
    7. dependencies: <atlfile.h>, CComBStr2.h, <atlsecurity.h>, <security.h>
  • Kerberos Negotiate authentication on IIS sucks

    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/

     

     

    08-23-2006, 0:23 from EProgrammer Blog to Egbert Nierop technolog
  • Boost BSTR performance for free, by 3000%

    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);

                }

          }

          CComBSTR2(_In_ const CComBSTR& src)

          {

                m_str = src.Copy();

                if (!!src && m_str == NULL)

                      AtlThrow(E_OUTOFMEMORY);

          }

          CComBSTR2(_In_ REFGUID guid)

          {

                wchar_t szGUID[64];

               

                m_str = ::SysAllocStringLen(szGUID,

                      ::StringFromGUID2(guid, szGUID, 64)

                      );

                if (m_str == NULL)

                      AtlThrow(E_OUTOFMEMORY);

          }

          CComBSTR2& operator=(_In_ const CComBSTR& src)

          {

                if (m_str != src.m_str)

                {

                      if (::SysReAllocStringLen(&m_str, src, src.Length()) == FALSE)              

                            AtlThrow(E_OUTOFMEMORY);

                }

                return *this;

          }

          CComBSTR2& operator=(_In_opt_ LPCOLESTR pSrc)

          {

                if (pSrc != m_str)

                {

                      if (pSrc != NULL)

                      {

                            if (::SysReAllocString(&m_str, pSrc) == FALSE)                  

                                 AtlThrow(E_OUTOFMEMORY);

                      }

                      else             

                            Empty();

                     

                }

                return *this;

          }

    //???

          inline ~CComBSTR2() throw()

          {

                ::SysFreeString(m_str);

          }

          static ULONG GetStreamSize(BSTR bstr)

          {

                ULONG ulSize=sizeof(ULONG);

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                if (bstr != NULL)

    #pragma prefast(pop)

    #pragma warning(pop)

                {

                      ulSize += SysStringByteLen(bstr) + sizeof(wchar_t);

                }

                return ulSize;

          }

          unsigned int __stdcall Length() const throw()

          {

                return ::SysStringLen(m_str);

          }

          unsigned int __stdcall ByteLength() const throw()

          {

                return ::SysStringByteLen(m_str);

          }

          operator BSTR() const throw()

          {

                return m_str;

          }

    #ifndef ATL_CCOMBSTR_ADDRESS_OF_ASSERT

    // Temp disable CComBSTR::operator& Assert

    #define ATL_NO_CCOMBSTR_ADDRESS_OF_ASSERT

    #endif

          BSTR* operator&() throw()

          {

    #ifndef ATL_NO_CCOMBSTR_ADDRESS_OF_ASSERT

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "We are deliberately checking if this has already been allocated")

                ATLASSERT(!*this);

    #pragma prefast(pop)

    #pragma warning(pop)

    #endif

                return &m_str;

          }

          BSTR __stdcall Copy() const throw()

          {

                if (*this == NULL)

                      return NULL;

                else

                {

                      unsigned int copyLen = ByteLength();

                      BSTR retVal = ::SysAllocStringByteLen(NULL, copyLen);

                      if (copyLen > 0 && retVal != NULL)

                      {

                            memcpy_s(retVal, copyLen, m_str, copyLen);

                      }

                      return retVal;

                }

         

          }

          //modified by E.N. pbstr is now in/out!

          // you must care for it that it properly gets initialized

          _Check_return_ STDMETHODIMP CopyTo(__inout_opt BSTR* pbstr) const throw()

          {

                ATLASSERT(pbstr != NULL);

                HRESULT hr;

                if (pbstr == NULL)

                      hr = E_POINTER;        

                else

                {

    #ifdef SysReAllocStringByteLen

                      unsigned int copyLen = ByteLength();

                      hr = SysReAllocStringByteLen(pbstr, NULL, copyLen) == FALSE ? E_OUTOFMEMORY : S_OK;

                      if (hr == S_OK && copyLen > 0)

                      {

                            memcpy_s(*pbstr, copyLen, m_str, copyLen);

                      }

    #else

                *pbstr = Copy();

                #pragma warning(push)

                #pragma warning(disable:4068)

                #pragma prefast(push)

                #pragma prefast(disable:325, "We are checking allocation semantics here")

                if ((*pbstr == NULL) && (m_str != NULL))

            {

                      return E_OUTOFMEMORY;

            }

                #pragma prefast(pop)

                #pragma warning(pop)

    #endif

                }

                return hr;

          }

          // *** returns true if length equals zero characters or when unallocated(null pointer)

          bool __stdcall IsEmpty (void) throw()

          {

                return m_str == NULL || Length() == 0;

          }

          /* added by may 2005 e.n. needs #include 'wchar.h'*/

          HRESULT __stdcall Format(__in PCWSTR pszFormat, __in va_list args) throw()

          {

                size_t len = _vscwprintf( pszFormat, args );

               

                HRESULT hr = SetLength((UINT)len) ;

                if(SUCCEEDED(hr))

                      if (vswprintf( m_str, len + 1, pszFormat, args ) < 0)

                            hr = E_INVALIDARG;

               

                return hr;

          }

          /* added by may 2005 e.n. needs #include 'wchar.h'*/

          HRESULT __cdecl Format(__in PCWSTR pszFormat, ...) throw()

          {

                va_list args;               

                va_start( args, pszFormat );      

                HRESULT hr = Format(pszFormat, args);

                va_end(args);

               

                return hr;

          }

    private:

          HRESULT __stdcall Insert(__in unsigned int atPosition, __in_opt LPCOLESTR lpsz, __in unsigned int nLen) throw()

          {

                unsigned int curLen = Length();

                HRESULT hr = S_OK;

                if (atPosition > curLen || lpsz == NULL)

                      hr = E_INVALIDARG;

                else

                      hr = SetLength(curLen + nLen);

                if (SUCCEEDED(hr) && curLen != 0 && nLen != 0)

                {

                      wmemmove(&m_str[atPosition + nLen], &m_str[atPosition],

                            curLen - atPosition);

                      wmemcpy_s(&m_str[atPosition], curLen + nLen - atPosition, lpsz, nLen );

                }

                return hr;

          }

    public:

          HRESULT __stdcall  Insert(__in unsigned int atPosition, __in_opt PCWSTR value) throw()

          {

                return Insert(atPosition, value, (unsigned int)wcslen(value));

          }

          HRESULT __stdcall Insert(__in unsigned int atPosition, __in const CComBSTR& value) throw()

          {

                return Insert(atPosition, value.m_str, value.Length());

          }

          HRESULT TrimStart(__in_opt PCWSTR trimwhat = NULL) throw()

          {

                PCWSTR trim = trimwhat == NULL? L" ": trimwhat;

                if (IsEmpty()) return S_OK;

                unsigned int trimLen = (unsigned int)wcslen(trim);

                while(StartsWith(trim))

                      Remove(0, trimLen);

                return S_OK;

          }

          HRESULT TrimEnd(__in_opt PCWSTR trimwhat = NULL) throw()

          {

                PCWSTR trim = trimwhat == NULL? L" ": trimwhat;

                if (IsEmpty()) return S_OK;

                unsigned int trimLen = (unsigned int)wcslen(trim);

                while(EndsWith(trim))

                      SetLength(Length() - trimLen);

                return S_OK;

          }

          //** removes in-place characters from this BSTR

          HRESULT __stdcall Remove(

                //** zero based starting position where you start to remove characters

                //** if this number is outside valid bounds, E_INVALIDARG is returned

                __in unsigned int startIndex,

                //** the number of characters, you want to remove

                //** if this number is outside valid bounds, it is corrected

                __in unsigned int count) throw()

          {

                unsigned int maxIdx = Length();

                // avoid buffer overflow

                if (count + startIndex > maxIdx) count = maxIdx - startIndex;

                HRESULT hr = S_OK;

                if (startIndex < maxIdx)                      

                {

                      if (maxIdx - startIndex - count != 0)               

                            //copy back, overlapped memory

                            wmemmove_s(&m_str[startIndex], maxIdx - startIndex, &m_str[startIndex + count] ,

                                 maxIdx - startIndex - count);

                      // shrink to new length

                     

                      hr = SetLength(maxIdx - count);

                }

                else

                      hr = E_INVALIDARG;

               

                return hr;

          }

          // original string john smith

          // merge with west at position 6

          // result john westh

          // won't extend string length!

          void __stdcall MergeString(__in unsigned int startIndex, __in const BSTR value) throw()

          {

                unsigned int maxIdx = Length();

                if (startIndex > maxIdx || value == NULL) return; // illegal operation

                unsigned int mergeLen = SysStringLen(value);

                if (mergeLen + startIndex > maxIdx)

                      mergeLen = maxIdx - startIndex;

                wmemcpy_s(&m_str[startIndex], maxIdx - startIndex, value, mergeLen);

          }

          BSTR __stdcall Substring(__in unsigned int startIndex) throw()

          {

                unsigned int maxIdx = Length();

                if (m_str != NULL && startIndex >= 0 && startIndex <= maxIdx)

                {

                      return ::SysAllocStringLen(m_str + startIndex, maxIdx - startIndex);

                }

                else

                      return NULL;

          }

         

          HRESULT __stdcall SetByteLength(__in unsigned int length) throw()

          {

                return _SetByteLength(&m_str, length);

          }

         

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

          HRESULT __stdcall SetLength(__in unsigned int length) throw()

          {

                return _SetLength(&m_str, length);

          }

    private:

          static HRESULT __stdcall _SetByteLength(_Inout_ BSTR *str, __in unsigned int length) throw()

          {

                #ifdef SysReAllocStringByteLen

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

                #else

                      BSTR Copy = NULL;

                      UINT origLen = SysStringByteLen(*str);

                      if (origLen != 0)

                      {

                            Copy = SysAllocStringByteLen((LPCSTR)*str, length);

                            SysFreeString(*str);

                            *str = Copy;

                      }

                      return *str == NULL  ? E_OUTOFMEMORY : S_OK;

                #endif

          }

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

          {

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

          }

    public:

          int __stdcall TokenCount(__in PCWSTR find, __in bool caseInsensitive) throw()

          {

                int strLen = (int)Length();

                int tokenCount = 0;

                int findLen = (int)wcslen(find);

                for(int x = 0;

                      (x = IndexOf((const PWSTR)find, x, caseInsensitive)) >= 0;)

                {

                      x += findLen;

                      tokenCount++;

                }

                return tokenCount;

          }

          /// <summary>

          /// Replaces a find token with a specified token

          /// By E.N.

          /// </summary>   

          /// <param name="find">token to be replace</param>

          /// <param name="replace">token that will replace</param>

          /// <param name="caseInsensitive">if true, will do a case insensitive replacement</param>

          /// <example>

          /// CComBSTR2 myReplace(L"the dog jumps over the");

          /// myReplace(CComBSTR2(L"the"), CComBSTR2(L"big"));

          /// </example>

          /// <returns>The example would modify the string to "big dog jumps over big" </returns>     

          HRESULT __stdcall Replace(__in_opt BSTR find, __in_opt BSTR replace, __in bool caseInsensitive) throw()

          {

                HRESULT hr = S_OK;

                if (m_str == NULL || find == NULL || replace == NULL)

                      hr = E_INVALIDARG;

                else

                {

                      int countTokens = TokenCount(find, caseInsensitive);

                      if (countTokens == 0) return hr;

                      int findLen = SysStringLen(find);

                      int replaceLen = SysStringLen(replace);

                      int lengthDiff = replaceLen - findLen;

                      int oldLen = (int)Length() ;

                      int newLen = oldLen + (countTokens * lengthDiff);

                      if (lengthDiff > 0)

                      {

                            hr = SetLength(newLen);

                            if (FAILED(hr)) return hr;

                      }

                     

                      for(int x = 0;

                            (x = IndexOf(find, x, caseInsensitive)) >= 0                    

                            ; )

                      {

                            int offset = x + findLen;

                            if (lengthDiff != 0)

                            {

                                 wmemmove_s(&m_str[offset + lengthDiff],

                                       (lengthDiff < 0 ? oldLen : newLen) - offset,

                                       &m_str[offset],

                                       oldLen - offset);

                                 oldLen += lengthDiff;

                            }                                                         

                            if (replaceLen > 0)

                            {

                                 MergeString(x, replace);

                                 x += replaceLen;            

                            }

                      }

                      if (lengthDiff < 0)

                      {

                            hr = SetLength(newLen);                 

                      }

                }

                return hr;

          }

          /// <summary>

          /// Replaces a find token with a specified token

          /// <param name="find">token to be replace</param>

          /// <param name="replace">token that will replace</param>

          /// </summary>   

          /// <example>

          /// CComBSTR2 myReplace(L"the dog jumps over the");

          /// myReplace(CComBSTR2(L"the"), CComBSTR2(L"big"));

          /// </example>

          /// <returns>The example would modify the string to "big dog jumps over big" </returns>     

          HRESULT __stdcall Replace(__in_opt const BSTR find, __in_opt const BSTR replace) throw()

          {

                return Replace(find, replace, false);

          }

          SAFEARRAY* __stdcall Split(__in_opt PCWSTR expression, __in const bool caseInsenstive) throw()

          {

                return Split(CComBSTR(expression), caseInsenstive);

          }

          /// <summary>

          /// Split and copies this instance of CComBSTR2 into a SAFEARRAY* of VTYPE = VT_BSTR

          /// </summary>   

          /// <example>

          /// CComSafeArray<BSTR> myArray;

          /// CComBSTR2 joined(L"John|Smith");

          /// myArray.Attach(joined.Split(CComBSTR2(L"|")))

          /// </example>

          /// <returns>The example would return a safearray with 2 VT_BSTR elements containing "John" and "Smith"</returns>

          SAFEARRAY* __stdcall Split(__in const CComBSTR& expression, __in const bool caseInsensitive)

          {

                SAFEARRAY* retval = NULL;

                HRESULT hr = S_OK;

                if (m_str == NULL)

                      AtlThrow(E_POINTER);

                else

                {

                      unsigned int exprLen = expression.Length();

                      unsigned int mLen = Length();

                     

                      int x = 0;

                      //contains the number of found expression tokens

                      unsigned int found = mLen == 0 ? -1 : 0;                        

                      //find until no more...

                      if (expression != NULL && found >= 0)

                      {

                            for (;;)

                            {

                                 x = IndexOf(expression, x, caseInsensitive);

                                 if (x == -1) break;                     

                                 found++;

                                 x += exprLen;                     

                            }                      

                      }                

                      SAFEARRAYBOUND rgsa = {found + 1, 0};               

                      retval = ::SafeArrayCreate(VT_BSTR, 1, &rgsa);

                      if (retval == NULL)

                            return retval;

                     

                      else if (mLen > 0)

                      {                      

                            BSTR* paBSTR  ;//(BSTR*)retval->pvData;

                            hr = SafeArrayAccessData(retval, (void**)&paBSTR);                    

                            int prevPos = 0;

                            x = 0;

                            for (unsigned int curEl = 0; curEl <= found; curEl++)

                            {

                                 x = IndexOf(expression, x, caseInsensitive);                          

                                 paBSTR[curEl] = x < 0 ? Substring(prevPos) : Substring(prevPos, x - prevPos);

                                 if (paBSTR[curEl] == NULL)

                                 {

                                       hr = E_OUTOFMEMORY;

                                       break;

                                 }

                                 x += exprLen;    

                                 prevPos = x;                            

                            }                      

                            SafeArrayUnaccessData(retval);                            

                      }

                      if (FAILED(hr))

                      {

                            AtlTrace(L"general split fail %x", hr);

                            if (retval != NULL)

                                 ::SafeArrayDestroy(retval);

                            retval = NULL;

                            AtlThrow(hr);

                      }                      

                }

                return retval;

          }

          SAFEARRAY* __stdcall Split(__in_opt PCWSTR expression) throw()

          {

                return Split(CComBSTR(expression), false);

          }

          /// <summary>

          /// Added by E.N.

          /// Joins a SAFEARRAY to a single BSTR

          /// </summary>   

          /// <example>

          /// CComSafeArray<BSTR> myArray;  

          /// myArray.Add(CComBSTR2(L"John"));

          /// myArray.Add(CComBSTR2(L"Smith"));

          /// CComBSTR2 joined;

          /// joined.Attach(CComBSTR2::Join(myArray.m_psa, CComBSTR2(L"|") ) );  ///

          /// </example>

          /// <returns>The example would return "John|Smith"</returns>    

          static BSTR __stdcall Join(__in_opt SAFEARRAY *psa, __in const BSTR delimiter) //throw()

          {

                BSTR retval = NULL;

                HRESULT hr = S_OK;

                if (psa != NULL && psa != NULL)

                {

                      VARTYPE vt = VT_EMPTY;

                      unsigned int delLen = ::SysStringLen(delimiter);

                      hr = ::SafeArrayGetVartype(psa, &vt);

                      if (vt != VT_BSTR || psa->cDims != 1)

                            AtlThrow(E_INVALIDARG);

                      SAFEARRAYBOUND *rgsa = psa->rgsabound;

                      ULONG elements = rgsa->cElements - rgsa->lLbound;

                      unsigned int totalLen = 0;

                      BSTR* paBSTR = (BSTR*) psa->pvData;           

                     

                      ULONG els = elements;

                      while(els != 0)

                      {

                            if (paBSTR[--els] != NULL)

                                 totalLen += ::SysStringLen(paBSTR[els]);

                            if (els != 0)

                                 totalLen += delLen;

                      }

                      hr = _SetLength(&retval, totalLen);

                      if (FAILED(hr))

                            AtlThrow(hr);

                      else

                      {

                            totalLen = 0;

                            els = elements;

                            ULONG curel = 0;

                            while (els != 0)

                            {

                                 els--;

                                 if (paBSTR[curel] != NULL)

                                 {

                                       unsigned int curLen = ::SysStringLen(paBSTR[curel]);

                                       wmemcpy_s(retval + totalLen, curLen, paBSTR[curel], curLen);

                                       totalLen += curLen;

                                 }

                                 curel++;

                                 if (els != 0 && delLen != 0)

                                 {

                                       wmemcpy_s(retval + totalLen, delLen, delimiter, delLen);

                                       totalLen += delLen;

                                 }                           

                            }

                      } //success allocate string                   

                     

                }

                return retval;

          }

          /// <summary>

          /// Added by E.N.

          /// Returns a new instance of a BSTR which is a Substring starting at the 'startindex' character

          /// </summary>   

          /// <example>

          /// CComBSTR2 john(L"John Smith"), subbed;

          /// subbed.Attach(john.Substring(5));

          /// </example>

          /// <returns>The example would return "Smith"</returns>   

          BSTR __stdcall Substring(__in const unsigned int startIndex, __in unsigned int length) throw()

          {

                unsigned int maxIdx = Length();

                //if (length < 0) length = 0;

                if (startIndex + length > maxIdx)

                      length = maxIdx - startIndex;

                if (m_str != NULL && startIndex >= 0 && startIndex <= maxIdx)

                {

                      return ::SysAllocStringLen(m_str + startIndex, length);

                }

                else

                      return NULL;

          }

    private:

          bool __stdcall local_Test(__in_opt PCWSTR src, __in unsigned int startIndex) throw()

          {

                bool retval = false;

                if (src != NULL)

                {

                      unsigned int compLen = lstrlenW(src);

                      unsigned int thisLen = Length();

                      if (compLen <= thisLen)

                      {

                            DWORD dwCompFlags = 0;

                            retval = ::CompareStringW(::GetThreadLocale(), dwCompFlags,

                                 &m_str[thisLen - startIndex], compLen, src, compLen) == CSTR_EQUAL;

                      }

                }

                return retval;

          }

    public:

          bool __stdcall EndsWith(__in_opt PCWSTR src) throw()

          {

                return local_Test(src, lstrlenW(src));

          }

          bool __stdcall StartsWith(__in_opt PCWSTR src) throw()

          {

                return local_Test(src, Length());

               

          }

          int __stdcall LastIndexOf(__in const wchar_t src, __in_opt const unsigned int startIndex = 0, __in const bool caseInsensitive = false) throw()

          {

                wchar_t  src2[] = {src, NULL}; //create zero terminated PWSTR

                return LastIndexOf(src2, startIndex, caseInsensitive);

          }

          int __stdcall LastIndexOf(__in_opt const wchar_t *src, __in const unsigned int startIndex = 0, __in const bool caseInsensitive = false, unsigned int count = 0) throw()

          {

                int result = -1;

                if (m_str != NULL && src != NULL)

                {

                      LCID lcid = ::GetThreadLocale();

                      DWORD dwCmpFlags = caseInsensitive ? NORM_IGNORECASE : 0;

               

                      unsigned int compLen = (unsigned int)wcslen(src);

                      unsigned int maxLen = Length();

                      unsigned int examinedChars = 0;

                      if (compLen <= maxLen)

                      {

                            for(unsigned int x = maxLen - compLen; x >= startIndex; )

                            {

                                 bool doCompare = caseInsensitive ? true : m_str[x] == src[0];

                                 if (doCompare)

                                       doCompare=  ::CompareStringW(lcid, dwCmpFlags, &m_str[x],

                                                   compLen, src, compLen) == CSTR_EQUAL     ;

                                 if (doCompare)

                                 {

                                       result = x;

                                       break;

                                 }

                                 if (x-- == 0 || (examinedChars++ == count && count != 0)) break;                            

                            }

                      }

                }

                return result;

          }

          int __stdcall IndexOf(__in const wchar_t src, __in const unsigned int startIndex = 0, __in const bool caseInsensitive = false, unsigned int count = 0) throw()

          {

                wchar_t  src2[] = {src, NULL}; //create zero terminated PWSTR

                return IndexOf(src2, startIndex, caseInsensitive, count);

          }

          //

          // Addded by E.N.

          //

          int __stdcall IndexOf(__in_opt const PWSTR src, __in const unsigned int startIndex = 0, __in const bool caseInsensitive = false, unsigned int count = 0) throw()

          {

                int result = -1;

               

                if (m_str != NULL && src != NULL)

                {                

                      LCID lcid = ::GetThreadLocale();

                      DWORD dwCmpFlags = caseInsensitive ? NORM_IGNORECASE : 0;

                      unsigned int maxLen = Length();

                      unsigned int compLen = (unsigned int)wcslen(src);

                      unsigned int examinedChars = 0;

                      if (compLen <= maxLen && compLen > 0)

                      {

                            for(int x = startIndex; (x + compLen <= maxLen) || (examinedChars++ == count && count != 0); x++)

                            {

                                 bool doCompare = caseInsensitive ? true :

                                       m_str[x] == src[0];

                                  if (doCompare)

                                       doCompare=  ::CompareStringW(lcid, dwCmpFlags, &m_str[x],

                                                   compLen, src, compLen) == CSTR_EQUAL     ;

                                 if (doCompare)                    

                                 {

                                       result = x;

                                       break;

                                 }                                 

                            }

                      }                           

                }

                return result;

          }

          // copy BSTR to VARIANT

          _Check_return_ STDMETHODIMP __stdcall CopyTo(__out VARIANT *pvarDest) throw()

          {

                ATLASSERT(pvarDest != NULL);

                HRESULT hRes = E_POINTER;

                if (pvarDest != NULL)

                {

                      pvarDest->vt = VT_BSTR;

                      pvarDest->bstrVal = Copy();

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "We are checking allocation semantics here")

                      if (pvarDest->bstrVal == NULL && m_str != NULL)

                {

                            hRes = E_OUTOFMEMORY;

                }

    #pragma prefast(pop)

    #pragma warning(pop)

                      else

                {

                            hRes = S_OK;

                }

                }

                return hRes;

          }

          void __stdcall Attach(__in_opt BSTR src) throw()

          {

                if (m_str != src)

                {

                      SysFreeString(m_str);

                      m_str = src;

                }

          }

          BSTR Detach() throw()

          {

                BSTR s = m_str;

                m_str = NULL;

                return s;

          }

          void Empty() throw()

          {

                ::SysFreeString(m_str);

                m_str = NULL;

          }

          bool __stdcall operator!() const throw()

          {

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                return (m_str == NULL);

    #pragma prefast(pop)

    #pragma warning(pop)

          }

         

          HRESULT __stdcall Append(__in const CComBSTR2& bstrSrc) throw()

          {

                return AppendBSTR(bstrSrc.m_str);

          }

          /*HRESULT Append(const CComVariant2& pVarSrc) throw()

          {

                return Append(pVarSrc.pvarVal);

          }*/

          //Added by E.N.

          HRESULT __stdcall Append(__in const VARIANT& pVar) throw()

          {

                HRESULT hr = S_OK;

                if (pVar.vt != VT_BSTR)

                {

                      VARIANT v = {0};

                      hr = ::VariantChangeTypeEx(&v,(VARIANT*)&pVar,

                            ::GetThreadLocale(), 0, VT_BSTR);

                      if (hr == S_OK) hr = AppendBSTR(v.bstrVal);

                      VariantClear(&v);

                }

                else

                      hr = AppendBSTR(pVar.bstrVal);

                return hr;

          }

          HRESULT __stdcall Append(__in_opt  LPCOLESTR lpsz) throw()

          {

                return Append(lpsz, (UINT)wcslen(lpsz));

          }

          // a BSTR is just a LPCOLESTR so we need a special version to signify

          // that we are appending a BSTR

          HRESULT __stdcall AppendBSTR(__in_opt BSTR p) throw()

          {

                return Append((LPCOLESTR)p, ::SysStringLen(p));

          }

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

          {

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

                      return S_OK;

                int n1 = Length();

                HRESULT hr = SetLength(n1 + nLen);

                if ( FAILED(hr))       

                      return hr; 

                wmemcpy_s(m_str+n1, (n1 + nLen), lpsz, nLen);

                return S_OK;

          }

          HRESULT __stdcall Append(__in char ch) throw()

          {

                OLECHAR chO = ch;

                return( Append( &chO, 1 ) );

          }

          HRESULT __stdcall Append(__in wchar_t ch) throw()

          {

                return( Append( &ch, 1 ) );

          }

          HRESULT __stdcall AppendBytes(__in_ecount_opt(nLen) const char* lpsz, __in int nLen) throw()

          {

                if (lpsz == NULL || nLen == 0)

                      return S_OK;

                int n1 = ByteLength();

                BSTR b;

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

                if (b == NULL)

                      return E_OUTOFMEMORY;

                memcpy_s(b, n1+nLen, m_str, n1);

                memcpy_s(((char*)b)+n1, nLen, lpsz, nLen);

                *((OLECHAR*)(((char*)b)+n1+nLen)) = NULL;

                Empty();

                m_str = b;

                return S_OK;

          }

          static int __stdcall CountChar(__in const BSTR a, __in wchar_t theChar) throw()

          {

                int retval = 0;

                if (a != NULL)

                {

                      unsigned int x  = ::SysStringLen(a);                            

                      while (x != 0)              

                            if (a[--x] == theChar) retval++;        

                }

                return retval;

          }

          //returns -1 for lesser (<); 0 for equality and 1 for GT (>)

          static int __stdcall Compare(__in const BSTR a, __in const BSTR b, __in bool ignoreCase, __in bool ignoreDiacritics, bool ignoreSymbols) throw()

          {

                int retval = 0;

                DWORD compareFlags = ignoreCase ? NORM_IGNORECASE : 0;

                if (ignoreDiacritics) compareFlags |= NORM_IGNORENONSPACE;

                if (ignoreSymbols) compareFlags |= NORM_IGNORESYMBOLS;

                if (a == NULL && b == NULL)

                      retval = 0;

                else if (a == NULL)

                      retval = -1;

                else if (b == NULL)

                      retval = 1;

                else

                {

                      switch(::VarBstrCmp(a, b, ::GetThreadLocale(), compareFlags))

                      {

                      case VARCMP_LT:

                            retval = -1;

                            break;

                      case VARCMP_EQ:

                            retval = 0;

                            break;

                      case VARCMP_GT:

                            retval = 1;

                            break;           

                      }

                }

                return retval;

          }

         

          int __stdcall CompareTo(__in const BSTR otherBstr) throw()

          {

                return Compare(m_str, otherBstr, false, false, false);

          }

          int __stdcall CompareTo(__in const BSTR otherBstr, __in bool ignoreCase) throw()

          {

                return Compare(m_str, otherBstr, ignoreCase, false, false);

          }

          int __stdcall CompareTo(__in const BSTR otherBstr, __in bool ignoreCase, __in bool ignoreDiacritics) throw()

          {

                return Compare(m_str, otherBstr, ignoreCase, ignoreDiacritics, false);

          }

          int __stdcall CompareTo(__in const BSTR otherBstr, __in bool ignoreCase, __in bool ignoreDiacritics, bool ignoreSymbols) throw()

          {

                return Compare(m_str, otherBstr, ignoreCase, ignoreDiacritics, ignoreSymbols);

          }

         

          unsigned long GetHashCode() throw()

          {

                return  LHashValOfName(::GetThreadLocale(), m_str);           

          }

          // added by e.n.

          static void __stdcall StringReverse(__in_opt const  BSTR s) throw()

          {

                //CComBSTR2 temp;

                //HRESULT  hr = temp.AssignBSTR(s); //create a copy

                UINT slen = SysStringLen(s), y = 0;

                while(y < slen--)

                {

                      WCHAR swp = s[slen];

                      s[slen] = sYes;

                      s[y++] = swp;

                }

          }

          void __stdcall Reverse() throw()

          {

                StringReverse(m_str);

          }

          GUID __stdcall ToGuid()

          {

                GUID retval = GUID_NULL;

                HRESULT hr = IIDFromString(m_str, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

         

          DATE __stdcall ToDate()

          {

                DATE retval = 0;

                HRESULT hr = ::VarDateFromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          _Check_return_ STDMETHODIMP AssignDate(__in const DATE date) throw()

          {

                Empty();

                return ::VarBstrFromDate(date, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

          }

          _Check_return_ STDMETHODIMP AssignBSTR(__in const BSTR bstrSrc) throw()

          {

                HRESULT hr = S_OK;

    #ifndef SysReAllocStringByteLen

                if (m_str != bstrSrc)

                {

                      Empty();

                      if (bstrSrc != NULL)              

                      {

                            m_str = ::SysAllocStringByteLen((PCSTR)bstrSrc, ::SysStringByteLen(bstrSrc)) ;

                            if (*this == NULL)

                                 hr = E_OUTOFMEMORY;

                      }

                      else           

                            Empty();

               

                }

    #else

                if (m_str != bstrSrc)

                {

                      if (bstrSrc == NULL)

                            Empty();

                      else

                      {

                            UINT ilen = ::SysStringByteLen(bstrSrc);

                            if (SysReAllocStringByteLen(&m_str, NULL,  ilen) == FALSE)

                                 hr = E_OUTOFMEMORY;

                            if (ilen > 0) //fix since SysReAllocStringByteLen measures in lstrlenA

                                 memcpy(m_str, bstrSrc, ilen);

                      }

                }

    #endif

                return hr;

          }

          //creates a copy to a byteencoded string

          // optimal usage, attach to it...

          BSTR ToByteString(__in UINT codePage=CP_UTF8)

          {

                if (!IsEmpty())

                {

                      UINT len = Length();

                      UINT neededLen = len * 4;

                      BSTR pszA = SysAllocStringByteLen(NULL, neededLen);

                      int _convert = WideCharToMultiByte(codePage, 0, m_str, len, (PSTR)pszA, neededLen, NULL, NULL);

                      if (_convert == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)

                      {

                            _convert = WideCharToMultiByte(codePage, 0, m_str, len, NULL, 0, NULL, NULL);

                            _SetByteLength(&pszA, _convert);

                            _convert = WideCharToMultiByte(codePage, 0, m_str, len, (PSTR)pszA, _convert, NULL, NULL); 

                      }

                      if (_convert == 0 && GetLastError() > 0)

                            AtlThrowLastWin32();

                      _SetByteLength(&pszA, _convert);

                      return pszA;

                }

                else

                      return NULL;

          }

          //creates a copy to a wide-string

          // optimal usage, attach to it...

          BSTR ToWideString(__in UINT codePage=CP_UTF8) throw()

          {

                UINT len = ByteLength();

                int _convert = MultiByteToWideChar(codePage, 0, (PSTR) m_str, len, NULL, 0);

                BSTR pszW = SysAllocStringLen(NULL, _convert);

                if (pszW != NULL)

                {

                      MultiByteToWideChar(codePage, 0, (PSTR)m_str, len, pszW, _convert);

                }

                return pszW;

          }

          HRESULT ToLower() throw()

          {

                if (m_str != NULL)

                {

    #ifdef _UNICODE

                      // Convert in place

                      CharLowerBuff(m_str, Length());

    #else

                      // Cannot use conversion macros due to possible embedded NULLs

                      UINT _acp = _AtlGetConversionACP();

                      int _convert = WideCharToMultiByte(_acp, 0, m_str, Length(), NULL, 0, NULL, NULL);

                      CTempBuffer<char> pszA;

                      ATLTRY(pszA.Allocate(_convert));

                      if (pszA == NULL)

                            return E_OUTOFMEMORY;

                      int nRet = WideCharToMultiByte(_acp, 0, m_str, Length(), pszA, _convert, NULL, NULL);

                      if (nRet == 0)

                      {

                            ATLASSERT(0);

                            return AtlHresultFromLastError();

                      }

                      CharLowerBuff(pszA, nRet);

                      _convert = MultiByteToWideChar(_acp, 0, pszA, nRet, NULL, 0);

                      CTempBuffer<WCHAR> pszW;

                      ATLTRY(pszW.Allocate(_convert));

                      if (pszW == NULL)

                            return E_OUTOFMEMORY;

                      nRet = MultiByteToWideChar(_acp, 0, pszA, nRet, pszW, _convert);

                      if (nRet <= 0)

                      {

                            ATLASSERT(0);

                            return AtlHresultFromLastError();

                      }

                      UINT nBytes=0;   

                      HRESULT hr=S_OK;

                      if( FAILED(hr=::ATL::AtlMultiply(&nBytes, static_cast<UINT>(nRet), static_cast<UINT>(sizeof(OLECHAR)))))

                      {

                            return hr;

                      }

                      BSTR b = ::SysAllocStringByteLen((LPCSTR) (LPWSTR) pszW, nBytes);

                      if (b == NULL)

                            return E_OUTOFMEMORY;

                      SysFreeString(m_str);

                      m_str = b;

    #endif

                }

                return S_OK;

          }

          HRESULT __stdcall ToUpper() throw()

          {

                if (m_str != NULL)

                {

    #ifdef _UNICODE

                      // Convert in place

                      CharUpperBuff(m_str, Length());

    #else

                // Cannot use conversion macros due to possible embedded NULLs

                      UINT _acp = _AtlGetConversionACP();

                      int _convert = WideCharToMultiByte(_acp, 0, m_str, Length(), NULL, 0, NULL, NULL);

                      CTempBuffer<char> pszA;

                      ATLTRY(pszA.Allocate(_convert));

                      if (pszA == NULL)

                            return E_OUTOFMEMORY;

                      int nRet = WideCharToMultiByte(_acp, 0, m_str, Length(), pszA, _convert, NULL, NULL);

                      if (nRet == 0)

                      {

                            ATLASSERT(0);

                            return AtlHresultFromLastError();

                      }

                      CharUpperBuff(pszA, nRet);

                      _convert = MultiByteToWideChar(_acp, 0, pszA, nRet, NULL, 0);

                      CTempBuffer<WCHAR> pszW;

                      ATLTRY(pszW.Allocate(_convert));

                      if (pszW == NULL)

                            return E_OUTOFMEMORY;

                      nRet = MultiByteToWideChar(_acp, 0, pszA, nRet, pszW, _convert);

                      if (nRet <= 0)

                      {

                            ATLASSERT(0);

                            return AtlHresultFromLastError();

                      }

                      UINT nBytes=0;

                      HRESULT hr=S_OK;

                      if( FAILED(hr=::ATL::AtlMultiply(&nBytes, static_cast<UINT>(nRet), static_cast<UINT>(sizeof(OLECHAR)))))

                      {          

                            return hr;

                      }

                      BSTR b = ::SysAllocStringByteLen((LPCSTR) (LPWSTR) pszW, nBytes);

                      if (b == NULL)

                            return E_OUTOFMEMORY;

                      SysFreeString(m_str);

                      m_str = b;

    #endif

                }

                return S_OK;

          }

          bool __cdecl LoadNString(__in UINT uID,  ...) throw()

          {

                va_list args;

                va_start(args, uID);

                bool res = _LoadNString(uID, _AtlBaseModule.GetResourceInstance(), args);

                va_end(args);

                return res;

          }

          bool __cdecl LoadNString2(__in UINT uID, __in HINSTANCE hInst, ...) throw()

          {

                va_list args;

                va_start(args, hInst);

                bool res = _LoadNString(uID, hInst, args);

                va_end(args);

                return res;

          }

    private:

          bool __cdecl _LoadNString(__in UINT uID, __in HINSTANCE hInst, __in va_list args) throw()

          {

               

                bool result = true;               

                PWSTR myMessage = NULL;

                LANGID langid = LANGIDFROMLCID(::GetThreadLocale());

                LANGID id= PRIMARYLANGID(langid);

                int trycnt = 0;

                UINT bufSize =0;

                DWORD dwerr;

                ::SetLastError(ERROR_SUCCESS); //fix

                do

                {

                      trycnt++;

                      bufSize = ::FormatMessageW(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER,

                            hInst, uID, id, (PWSTR) &myMessage, 0, &args);

                      id= SUBLANGID(langid);

                       dwerr = ::GetLastError();

                } while (dwerr == ERROR_RESOURCE_LANG_NOT_FOUND && trycnt == 1);

                if (dwerr != ERROR_SUCCESS)

                {

                      AtlTrace(L"_LoadNString %d", dwerr);

                      result = false;

                }

                else

                {

                      Empty();

                      if (bufSize > 0) bufSize -= 2;

                      m_str = ::SysAllocStringLen(myMessage, bufSize);

                }

                if (myMessage != NULL)

                      ::GlobalFree(myMessage);

         

                return result;

       }

    public:

          bool __stdcall LoadString(__in HINSTANCE hInst, __in UINT nID) throw()

          {

                Empty();

                return LoadStringResource(hInst, nID, m_str);

          }

          bool __stdcall LoadString(__in UINT nID) throw()

          {

                Empty();

                return LoadStringResource(nID, m_str);

          }

          CComBSTR2& __stdcall operator+=(__in const CComBSTR2& bstrSrc)

          {

                HRESULT hr;

                hr = AppendBSTR(bstrSrc.m_str);

                if (FAILED(hr))

                      AtlThrow(hr);

                return *this;

          }

          CComBSTR2& __stdcall operator+=(__in const VARIANT& pVar)

          {

                HRESULT hr = Append(pVar);

                if (FAILED(hr))

                      AtlThrow(hr);

                return *this;

          }

          CComBSTR2& __stdcall operator+=(__in_opt LPCOLESTR pszSrc)

          {

                HRESULT hr = Append(pszSrc);

                if (FAILED(hr))

                      AtlThrow(hr);

                return *this;

          }

         

          bool __stdcall operator<(__in const CComBSTR2& bstrSrc) const throw()

          {

                return Compare(m_str, bstrSrc.m_str, true, true, true) == -1;

          }

          bool __stdcall operator<(LPCOLESTR pszSrc) const

          {

                CComBSTR2 bstr2(pszSrc);

                return operator<(bstr2);

          }

          bool __stdcall operator<(__in_opt LPOLESTR pszSrc) const throw()

          {

                return operator<((LPCOLESTR)pszSrc);

          }

          bool __stdcall operator>(__in const CComBSTR2& bstrSrc) const throw()

          {

                return Compare(m_str, bstrSrc.m_str, true, true, true) == -1;

          }

          bool operator>(__in_opt LPCOLESTR pszSrc) const

          {

                CComBSTR2 bstr2(pszSrc);

                return operator>(bstr2);

          }

          bool __stdcall operator>(__in_opt LPOLESTR pszSrc) const throw()

          {

                return operator>((LPCOLESTR)pszSrc);

          }

         

          bool __stdcall operator!=(__in const CComBSTR2& bstrSrc) const throw()

          {

                return !operator==(bstrSrc);

          }

          bool __stdcall operator!=(__in_opt LPCOLESTR pszSrc) const throw()

          {

                return !operator==(pszSrc);

          }

          bool operator!=(__in int nNull) const throw()

          {

                return !operator==(nNull);

          }

          bool __stdcall operator!=(__in_opt  LPOLESTR pszSrc) const throw()

          {

                return operator!=((LPCOLESTR)pszSrc);

          }

          bool __stdcall operator==(__in const CComBSTR2& bstrSrc) const throw()

          {

                return (Compare(m_str, bstrSrc.m_str, true, true, true) == 0);

          }

          bool __stdcall operator==(__in_opt LPCOLESTR pszSrc) const throw()

          {

                CComBSTR2 bstr2(pszSrc);

                return operator==(bstr2);

          }

          bool __stdcall operator==(__in_opt LPOLESTR pszSrc) const throw()

          {

                return operator==((LPCOLESTR)pszSrc);

          }

         

          bool __stdcall operator==(__in int nNull) const throw()

          {

                ATLASSERT(nNull == NULL);

                (void)nNull;

                return (m_str == NULL);

          }

          CComBSTR2(_In_opt_ LPCSTR pSrc)

          {

                if (pSrc != NULL)

                {

                      m_str = A2WBSTR(pSrc);

                      if (m_str == NULL)

                            AtlThrow(E_OUTOFMEMORY);

                }

                else

                      m_str = NULL;

          }

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

          {

                if (nSize != 0 && sz == NULL)

                {

                      HRESULT hr = SetLength(nSize);

                      if (FAILED(hr))

                            AtlThrow(hr);

                }

                else

                {

                      m_str = A2WBSTR(sz, nSize);

                      if (m_str == NULL && nSize != 0)

                            AtlThrow(E_OUTOFMEMORY);

                }

          }

          HRESULT __stdcall Append(_In_opt_ LPCSTR lpsz) throw()

          {

                if (lpsz == NULL)

                      return S_OK;

                CComBSTR2 bstrTemp;

                ATLTRY(bstrTemp = lpsz);

                if (bstrTemp.m_str == NULL)

                      return E_OUTOFMEMORY;

                return Append(bstrTemp);

          }

         

         

          CComBSTR2& __stdcall operator=(__in ULONG ulong)

          {

                Empty();

                HRESULT hr = ::VarBstrFromUI4(ulong, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          // Define assignment operator.

          //A& A::operator=( const A& );

          ULONG __stdcall ToULong()

          {

                ULONG retval = 0;

                HRESULT hr = ::VarUI4FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          LONG __stdcall ToLong()

          {

                LONG retval = 0;

                HRESULT hr = ::VarI4FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          HRESULT __stdcall AssignWordHex(__in WORD pVal, __in bool fixedLen = true) throw()

          {

                PCWSTR fmt =  fixedLen ? L"%04x" : L"%x";

                return Format(fmt, pVal);

          }

          HRESULT __stdcall AssignLongHex(__in LONG pVal, __in bool fixedLen = true) throw()

          {

                PCWSTR fmt = fixedLen ?      L"%08x" : L"%x";

                return Format(fmt, pVal) ;        

          }

          CComBSTR2& __stdcall operator=(__in LONG pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromI4(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          CComBSTR2& __stdcall operator=(__in USHORT pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromUI2(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          SHORT __stdcall ToShort()

          {

                SHORT retval = 0;

                HRESULT hr = ::VarI2FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          CComBSTR2& __stdcall operator=(__in SHORT pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromI2(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          BYTE __stdcall ToByte()

          {

                BYTE retval = 0;

                HRESULT hr = ::VarUI1FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          CComBSTR2& __stdcall operator=(__in BYTE pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromUI1(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

    #if (_WIN32_WINNT >= 0x0501) || defined(_ATL_SUPPORT_VT_I8)

          CComBSTR2& operator =(LONG64 pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromI8(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          LONG64 ToVlong()

          {

                LONG64 retval = 0;

                HRESULT hr = ::VarI8FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr)) AtlThrow(hr);

                return retval;

          }

    #endif

          FLOAT __stdcall ToFloat()

          {

                FLOAT retval = 0;

                HRESULT hr = ::VarR4FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          CComBSTR2& __stdcall operator=(__in FLOAT pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromR4(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }

          DOUBLE __stdcall ToDouble()

          {

                DOUBLE retval = 0;

                HRESULT hr = ::VarR8FromStr(m_str, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &retval);

                if (FAILED(hr))

                      AtlThrow(hr);

                return retval;

          }

          CComBSTR2& __stdcall operator=(__in DOUBLE pSrc)

          {

                Empty();

                HRESULT hr = ::VarBstrFromR8(pSrc, ::GetThreadLocale(), LOCALE_NOUSEROVERRIDE, &m_str);

                if (FAILED(hr)) AtlThrow(hr);

                return *this;

          }    

          CComBSTR2& __stdcall operator=(__in_opt LPCSTR pSrc)

          {

                Empty();

                m_str = A2WBSTR(pSrc);

                if (m_str == NULL && pSrc != NULL)

                      AtlThrow(E_OUTOFMEMORY);

                return *this;

          }

          bool __stdcall operator<(__in_opt LPCSTR pszSrc) const

          {

                CComBSTR2 bstr2(pszSrc);

                return operator<(bstr2);

          }

          bool __stdcall operator>(__in_opt LPCSTR pszSrc) const

          {

                CComBSTR2 bstr2(pszSrc);

                return operator>(bstr2);

          }

          bool __stdcall operator!=(__in_opt LPCSTR pszSrc) const

          {

                return !operator==(pszSrc);

          }

          bool __stdcall operator==(__in_opt LPCSTR pszSrc) const

          {

                CComBSTR2 bstr2(pszSrc);

                return operator==(bstr2);

          }

          _Check_return_ STDMETHODIMP WriteToStream(_Inout_ IStream* pStream) throw()

          {

                ATLASSERT(pStream != NULL);

                if(pStream == NULL)

                      return E_INVALIDARG;

                     

                ULONG cb;

                ULONG cbStrLen = CComBSTR::GetStreamSize(m_str) - sizeof(ULONG);

                HRESULT hr = pStream->Write((void*) &cbStrLen, sizeof(cbStrLen), &cb);

                if (FAILED(hr))

                      return hr;

                return cbStrLen ? pStream->Write((void*) m_str, cbStrLen, &cb) : S_OK;

          }

          _Check_return_ STDMETHODIMP ReadFromStream(_Inout_ IStream* pStream) throw()

          {

                ATLASSERT(pStream != NULL);

                if(pStream == NULL)

                {

                      return E_INVALIDARG;

                }

                     

                ATLASSERT(!*this); // should be empty

                Empty();

               

                HRESULT hrSeek;

                ULARGE_INTEGER nBegOffset;

                {

                      LARGE_INTEGER nZeroOffset;

                      nZeroOffset.QuadPart = 0L;

                      hrSeek = pStream->Seek(nZeroOffset, STREAM_SEEK_CUR, &nBegOffset);

                }

                ULONG cbRead = 0;

                ULONG cbStrLen = 0;

                HRESULT hr = pStream->Read(reinterpret_cast<void*>(&cbStrLen), sizeof(cbStrLen), &cbRead);

                if (SUCCEEDED(hr))

                {

                      // invalid data size

                      if (sizeof(cbStrLen) != cbRead)

                      {

                            ATLTRACE(atlTraceCOM, 0, _T("Input stream is corrupted."));

                            hr = E_FAIL;

                      }

                      // read NULL string

                      else if (cbStrLen == 0)

                      {                      

                      }

                      // invalid data length 

                      else if (cbStrLen < sizeof(OLECHAR))

                      {

                            ATLTRACE(atlTraceCOM, 0, _T("Input stream is corrupted."));

                            hr = E_FAIL;

                      }

                      // security checks when system hang for huge stream of data

                      else if (cbStrLen > _ATL_STREAM_MAX_SIZE)

                      {

                            ATLTRACE(atlTraceCOM, 0, _T("String exceeded the maximum allowed size see _ATL_STREAM_MAX_SIZE."));

                            hr = E_ACCESSDENIED;

                      }

                      else

                      {

                            //subtract size for terminating NULL which we wrote out

                            cbStrLen -= sizeof(OLECHAR);

                            m_str = ::SysAllocStringByteLen(NULL, cbStrLen);

                            if (!*this)

                            {

                                 hr = E_OUTOFMEMORY;

                            }

                            else

                            {

                                 hr = pStream->Read(reinterpret_cast<void*>(m_str), cbStrLen, &cbRead);

                                 if (SUCCEEDED(hr))

                                 {

                                       if (cbRead != cbStrLen)

                                       {

                                             ATLTRACE(atlTraceCOM, 0, _T("Length of string data is different than expected."));

                                             hr = E_FAIL;

                                       }

                                       else

                                       {

                                             OLECHAR ch;

                                             hr = pStream->Read(reinterpret_cast<void*>(&ch), sizeof(OLECHAR), &cbRead);  

                                             if (SUCCEEDED(hr))

                                             {                                             

                                                   if (cbRead != sizeof(OLECHAR))

                                                   {

                                                         ATLTRACE(atlTraceCOM, 0, _T("Cannot read NULL terminator from stream."));

                                                         hr = E_FAIL;                                                   

                                                   }

                                                   else

                                                   {

                                                         //check if string is properly terminated with NULL

                                                         ATLASSERT(ch == L'\0');

                                                   }

                                             }

                                       }

                                 }                

                                      

                                 if (FAILED(hr))

                                 {

                                       ::SysFreeString(m_str);

                                       m_str = NULL;

                                 }

                            }

                      }

                }

               

                // If SysAllocStringByteLen or IStream::Read failed, reset seek

                // pointer to start of BSTR size.

                if (FAILED(hr) && SUCCEEDED(hrSeek))

                {

                      LARGE_INTEGER nOffset;

                      nOffset.QuadPart = static_cast<LONGLONG>(nBegOffset.QuadPart);

                      pStream->Seek(nOffset, STREAM_SEEK_SET, NULL);                  

                }

         

                return hr;

          }

          static bool __stdcall LoadStringResource(_In_ HINSTANCE hInstance, _In_ UINT uID, _Inout_ _Deref_post_opt_valid_ BSTR& bstrText) throw()

          {    

                const ATLSTRINGRESOURCEIMAGE* pImage;

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                ATLASSERT(bstrText == NULL);

    #pragma prefast(pop)

    #pragma warning(pop)

                pImage = AtlGetStringResourceImage(hInstance, uID);

                if (pImage != NULL)

                {

                      bstrText = ::SysAllocStringLen(pImage->achString, pImage->nLength);

                }

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                return (bstrText != NULL) ? true : false;

    #pragma prefast(pop)

    #pragma warning(pop)

          }

          static bool __stdcall LoadStringResource(_In_ UINT uID, _Inout_ _Deref_post_opt_valid_ BSTR& bstrText) throw()

          {

                const ATLSTRINGRESOURCEIMAGE* pImage;

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                ATLASSERT(bstrText == NULL);

    #pragma prefast(pop)

    #pragma warning(pop)

                pImage = AtlGetStringResourceImage(uID);

                if (pImage != NULL)

                {

                      bstrText = ::SysAllocStringLen(pImage->achString, pImage->nLength);

                }

    #pragma warning(push)

    #pragma warning(disable:4068)

    #pragma prefast(push)

    #pragma prefast(disable:325, "The semantics of this function are about allocation, not content")

                return (bstrText != NULL) ? true : false;

    #pragma prefast(pop)

    #pragma warning(pop)

          }

          // each character in BSTR is copied to each element in SAFEARRAY

          _Success_(SUCCEEDED(return)) HRESULT BSTRToArray(_Deref_out_ LPSAFEARRAY *ppArray) throw()

          {

                return VectorFromBstr(m_str, ppArray);

          }

          // first character of each element in SAFEARRAY is copied to BSTR

          _Check_return_ HRESULT ArrayToBSTR(_In_ const SAFEARRAY *pSrc) throw()

          {

                ::SysFreeString(m_str);

                return BstrFromVector((LPSAFEARRAY)pSrc, &m_str);

          }

    };

    //bstr alternative avoiding any BSTR cache 

    #define MAXBSTRLEN unsigned int(~0)

    MAXBSTRLEN unsigned int(~0)

    #define m_align 16L

    m_align 16L

    #define MEMALIGN_16(cb) cb % m_align != 0 ? ((cb / m_align) + 1) * m_align : cb

    MEMALIGN_16(cb) cb % m_align != 0 ? ((cb / m_align) + 1) * m_align : cb

    // compile this with option 'not using precompiled headers'

    #include "bstrnocache.h"

    // see license in bstrnocache.h

    void __stdcall FreeString(BSTR * theString) throw()

    {

          if (theString != NULL && *theString != NULL )

          {

                SysFreeString(*theString);

                *theString = NULL;

          }

    }

    int __stdcall SysReAllocStringByteLen2(BSTR * bstr, PCSTR input, UINT cch) throw()

    {

    //    ATLASSERT(bstr != NULL);

          UINT_PTR copyIntoLen = input != NULL ?  strlen(input): 0;

          if (copyIntoLen > cch)

                copyIntoLen = cch;

          UINT_PTR newlen = cch + sizeof(OLECHAR) + sizeof(UINT_PTR);

          if (newlen > MAXBSTRLEN)

                return FALSE;    

          PSTR temp = (PSTR)::CoTaskMemRealloc(

                                                         *bstr == NULL ? NULL : (PSTR)(*bstr) - sizeof(UINT_PTR)

                                                   , MEMALIGN_16(newlen));

          if (temp != NULL)

          {

                UINT_PTR* plen = (UINT_PTR*)temp;

                *bstr = (BSTR)&plen[1];

                UINT* plen2 = (UINT*)temp;

                plen2[sizePtrCorrection] = cch ; //asign bytelength for BSTR

                if (copyIntoLen > 0)    //copy values

                #if _MSC_VER >= 1310

                      strncpy_s((PSTR)*bstr, cch + 1, input, copyIntoLen);

                #else

                      strncpy((PSTR)*bstr, input, copyIntoLen);

                #endif

                temp[sizeof(UINT_PTR) + cch ] = 0; //terminate LPWSTR with a wide_string

                temp[sizeof(UINT_PTR) + cch + 1] = 0;         

          }

         

          return *bstr == NULL ? FALSE : TRUE;

    }

    int __stdcall SysReAllocStringLen(BSTR * bstr, const OLECHAR* input, UINT cch) throw()

    {

          //ATLASSERT(bstr != NULL);

          UINT_PTR copyIntoLen = input != NULL ?  wcslen(input): 0;

          if (copyIntoLen > cch)

                copyIntoLen = cch;

          UINT_PTR newlen = cch * sizeof(OLECHAR) + sizeof(OLECHAR) + sizeof(UINT_PTR);

          if (newlen > MAXBSTRLEN)

                return FALSE;

         

          PWSTR temp = (PWSTR)::CoTaskMemRealloc(*bstr == NULL ? NULL : *bstr - sizeof(UINT_PTR) / sizeof(OLECHAR),

                                       MEMALIGN_16(newlen));  

          if (temp != NULL)

          {

                UINT_PTR* plen = (UINT_PTR*)temp;

                *bstr = (BSTR)&plen[1];

                UINT* plen2 = (UINT*)temp;

                plen2[sizePtrCorrection] = cch * sizeof(OLECHAR); //asign bytelength for BSTR

                if (copyIntoLen > 0)    //copy values

                #if _MSC_VER >= 1310

                      wcsncpy_s(*bstr, cch + 1, input, copyIntoLen );

                #else

                      wcsncpy(*bstr, input, copyIntoLen );

                #endif

                temp[sizeof(UINT_PTR) / sizeof(OLECHAR) + cch ] = 0; //terminate LPWSTR

          }

          return *bstr == NULL ? FALSE : TRUE;

    }

    ///<summary>

    ///

    ///</summary>

    BSTR __stdcall SysAllocStringLen(const OLECHAR * input, UINT cch)  throw()

    {

          UINT_PTR copyIntoLen = input != NULL ?  wcslen(input) : 0;

          if (copyIntoLen > cch)

                copyIntoLen = cch;

          BSTR retval = NULL;

          UINT_PTR newlen = cch * sizeof(OLECHAR) + sizeof(OLECHAR) + sizeof(UINT_PTR);

          if (newlen > MAXBSTRLEN)

                return NULL;

          PWSTR temp = (PWSTR)::CoTaskMemAlloc(MEMALIGN_16(newlen));

          if (temp != NULL)

          {

                UINT_PTR* plen = (UINT_PTR*)temp;

                retval = (BSTR)&plen[1];

                UINT* plen2 = (UINT*)temp;        

                plen2[0] =0;

                plen2[sizePtrCorrection] = cch * sizeof(OLECHAR);

                if (copyIntoLen > 0)   

                #if _MSC_VER >= 1310

                      wcsncpy_s(retval, cch + 1, input, copyIntoLen);

                #else

                      wcsncpy(retval, input, copyIntoLen);

                #endif

                temp[sizeof(UINT_PTR) / sizeof(OLECHAR) + cch ] = 0;      

          }

          return retval;

    }

    ///<summary>

    ///

    ///</summary>

    BSTR __stdcall SysAllocStringByteLen2(PCSTR input, UINT cch)  throw()

    {

          UINT_PTR copyIntoLen = input != NULL ?  strlen(input) : 0;

          if (copyIntoLen > cch)

                copyIntoLen = cch;

          BSTR retval = NULL;

          UINT_PTR newlen = cch + sizeof(OLECHAR) + sizeof(UINT_PTR);

          if (newlen > MAXBSTRLEN)

                return NULL;

          PWSTR temp = (PWSTR)::CoTaskMemAlloc(MEMALIGN_16(newlen));

          if (temp != NULL)

          {

                UINT_PTR* plen = (UINT_PTR*)temp;

                retval = (BSTR)&plen[1];

                UINT* plen2 = (UINT*)temp;        

                plen2[sizePtrCorrection] = cch;         

                if (copyIntoLen > 0)   

                #if _MSC_VER >= 1310

                      strncpy_s((PSTR)retval, cch + 1, input, copyIntoLen);

                #else

                      strncpy((PSTR)retval, input, copyIntoLen);

                #endif

                PSTR zeroit = (PSTR)temp;

                zeroit[sizeof(UINT_PTR) + cch ] = 0;     //terminate LPWSTR with a wide_string

                zeroit[sizeof(UINT_PTR) + cch + 1] = 0;       

          }

          return retval;

    }

    ///<summary>

    /// Identical to SysAllocString

    ///</summary>

    BSTR __stdcall SysAllocString(const OLECHAR * input)  throw()

    {

          BSTR retval = NULL;

          if (input != NULL)

          {          

                UINT_PTR slen = wcslen(input);

                UINT_PTR newlen = slen * sizeof(OLECHAR)+ sizeof(UINT_PTR) + sizeof(OLECHAR);

                if (newlen > MAXBSTRLEN)

                      return NULL;

                PWSTR temp = NULL;          

                temp = (PWSTR)::CoTaskMemAlloc(MEMALIGN_16(newlen));

                if (temp != NULL)

                {

                      UINT_PTR* plen = (UINT_PTR*)temp;

                      retval = (BSTR)&plen[1];

                      UINT* plen2 = (UINT*)temp;

                      plen2[sizePtrCorrection] = (UINT)slen * sizeof(OLECHAR);

                      if (slen > 0)

                      #if _MSC_VER >= 1310

                            wcsncpy_s(retval, slen + 1, input, slen);

                      #else

                            wcsncpy(retval, input, slen);

                      #endif

                }

          }    

          return retval;

    }

    int __stdcall SysReAllocString(BSTR * bstr, const OLECHAR * input) throw()

    {

          //ATLASSERT(bstr != NULL);

          bool didEmpty = FALSE;

          if (input == NULL)

          {

                FreeString(bstr);

                didEmpty = true;

          }

          else

          {

                UINT_PTR slen = wcslen(input);

                UINT_PTR newlen = slen * sizeof(OLECHAR) + sizeof(OLECHAR) + sizeof(UINT_PTR);

                if (newlen > MAXBSTRLEN)

                      return FALSE;

                //get real address

                BSTR temp = (BSTR)::CoTaskMemRealloc(   

                                                   *bstr == NULL ? NULL : *bstr - sizeof(INT_PTR) / sizeof(OLECHAR),

                                                   MEMALIGN_16(newlen));

                if (temp != NULL)

                {

                      UINT_PTR* plen = (UINT_PTR*)temp;

                      *bstr = (BSTR)&plen[1];

                      UINT* plen2 = (UINT*)temp;

                      plen2[sizePtrCorrection] = (UINT)slen * sizeof(OLECHAR);                    

                      #if _MSC_VER >= 1310

                            wcsncpy_s(*bstr, slen + 1, input, slen);

                      #else

                            wcsncpy(*bstr, input, slen);

                      #endif

                }

          }

          return *bstr == NULL && didEmpty == false? FALSE : TRUE;

    }

    HRESULT __stdcall AllocPSTR(PSTR *lpMulti, SIZE_T lSize, SIZE_T *oldSize) throw()

    {

    //    ATLASSERT(lpMulti != NULL && oldSize != NULL);

          HRESULT hr = S_OK;

          if (*oldSize < lSize)

          {

                PSTR temp = (PSTR)::CoTaskMemRealloc(*lpMulti, lSize);//HeapReAlloc(h, 0, *lpMulti, lSize);

                if (temp == NULL)      

                {    

                      ::CoTaskMemFree(*lpMulti);//(h, 0, *lpMulti);

                      *lpMulti = NULL;

                      *oldSize = 0;

                      hr =E_OUTOFMEMORY;

                }

                else

                {

                      *lpMulti = temp;//successfull realloc

                      *oldSize = lSize;

                }

          }

          return hr;

    }

    UINT __stdcall SysStringByteLen2(BSTR theString) throw()

    {

          //UINT bogus = *(((UINT*)theString) -2 );

          return theString != NULL ? *(((UINT*)theString) -1 ) : 0;

    }

    UINT __stdcall SysStringLen2(BSTR theString) throw()

    {

          //UINT bogus = *(((UINT*)theString) -2 );

          return theString != NULL ? (*(((UINT*)theString) -1 )) / sizeof(OLECHAR) : 0;

    }

    void __stdcall SysFreeString2(BSTR theString) throw()

    {

          //ATLASSERT(theString != NULL);

          if (theString != NULL)

          {

                UINT_PTR* temp = (UINT_PTR*)theString;

                temp--;

                CoTaskMemFree(temp);

          }

    }

     

     

    07-25-2006, 13:20 from EProgrammer Blog to Egbert Nierop technolog
    Filed under: ,
  • Howto: Dynamically Create Gradient Buttons using ASPX and .NET

    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();

     

    }

    }
    07-12-2006, 20:08 from EProgrammer Blog to Egbert Nierop technolog
  • Howto: detect whether or not IIS (any version) is installed?

    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.

     

    07-05-2006, 11:17 from EProgrammer Blog to Egbert Nierop technolog
Powered by Community Server, by Telligent Systems