Friday, February 12, 2010

Problem: Unable to register an in-house generated PIA for a core component in Vista or Windows 7

Scenario:
One may create a PIA for a third party COM component when the vendor does not supply a PIA. However, if the component is a core Windows component such as MSScript.ocx or MSXMLx.dll the generated PIA cannot be registered.

Symptoms:
Regasm produces no error messages but the PIA is not registered for the component and the values are placed under HKLM\Software\Volatile.

And/Or

An installer runs but the PIA is not registered for the component and the values are placed under HKLM\Software\Volatile.

And/Or

You receive the following error when running tlbimp: TlbImp : error TI0000 : System.ApplicationException - Referenced type library ' does not have a primary interop assembly registered

Cause:
All registry keys for core windows components are owned by TrustedInstaller (Windows Resource Protection) and all others, including local service and administrators have Read Only access.

Resolutions:
Factor out the third party component by wrapping the exposed types or moving the dependencies into a module where a local interop assembly can be used or eliminate the use of components where PIA are not supplied.


It is possible to take ownership of the CLSID key from TrustedUser, change the permissions, register the component, and then revert the permissions and ownership. However it defeats the purpose of Windows Resource Protection and will break other components if valid PIA information is overwritten or a PIA is made available in the future.

Thursday, January 28, 2010

A deadlock occurs within a COM component that uses CComCritSecLock when an asynchronous exception occurs and the component was called from .Net

Problem: A deadlock occurs within a COM component that uses CComCritSecLock when an asynchronous exception occurs and the component was called from a .Net client (CLR 3.5 and earlier).

Solution: Fix the source of the asynchronous exception.

Workaround: Compile the COM component with the /EHa compiler switch (Configuration Properties\ C/C++ \ Code Generation \ Enable C++ Exceptions)

Cause:
The CLR (which also uses SEH) is then being “helpful” and handling the exception by passing it up to the client as a managed exception (AccessViolationException) and therefore does not allow the CRT destructor tear down to run. The correct solution is to identify and fix the source of the exception. Any CLR AccessViolationException should be immediately investigated. This same issue can just as easily leak other resources such as handles or memory which can be a beast to track down after the fact. Note that this code is throwing an exception across an interface when the /EHa switch is not set which is also a no-no. Enabling SEH and C++ exceptions and using littering catch (…) all over your code will make finding many issues very, very hard. But you can still break on the first chance exception in a debugger. To enable breaking on first chance exception in VS 2008 select Debug \ Exceptions … \ Win32 exceptions. Access violations triggers a break in windbg by default.
Fortunately .Net 4.0 may help out with this: http://msdn.microsoft.com/en-us/magazine/dd419661.aspx

Sample Code:
A COM object that uses CComCritSecLock

#pragma once
#include "resource.h" // main symbols

#include "SimpleObject_i.h"

// CSimpleton

class ATL_NO_VTABLE CSimpleton :
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl
{
public:
CSimpleton()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLETON)

BEGIN_COM_MAP(CSimpleton)
COM_INTERFACE_ENTRY(ISimpleton)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}

public:

STDMETHODIMP DoSomething()
{

try
{
//This will catch depending upon the compiler setting for EH
// no /EH -> deadlock
// /EHsc -> deadlock
// /EHa -> no deadlock

//If we walk all the way up to the CLR then the destructor will not be called.
//Also, we're throwing across the interface boundary which is a no-no
//See http://msdn.microsoft.com/en-us/library/1deeycx5(VS.71).aspx
//and http://msdn.microsoft.com/en-us/library/d42ws1f6(VS.71).aspx
//for the details of the compiler switches

//use RAII for critical section
if (m_cs.m_sec.OwningThread != 0)
{
ATLTRACE(L"Trouble!\n");
}

ATLTRACE(L"Critical section owning thread %p count %d\n", m_cs.m_sec.OwningThread, m_cs.m_sec.LockCount);

CComCritSecLock lock(m_cs, true);

//This triggers SEH
int *p = NULL;
*p = 0;
}
catch(...)
{
ATLTRACE(L"Caught it.\n");
}

ATLTRACE(L"Exiting\n");
return S_OK;
}


private:

CComAutoCriticalSection m_cs;
};

OBJECT_ENTRY_AUTO(__uuidof(Simpleton), CSimpleton)


A C# client using an instance of the COM object:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace LockUp
{
class Program
{
[MTAThread()]
static void Main(string[] args)
{
//changed to x86 target for W7:
//http://blogs.msdn.com/karthick/archive/2006/02/28/540780.aspx
SimpleObjectLib.SimpletonClass testObj = new SimpleObjectLib.SimpletonClass();

//Call on first thread
Thread thread1 = new Thread(LockIt);
thread1.Start(testObj);
thread1.Join();

//No call from a separate thread
Thread thread2 = new Thread(LockIt);
thread2.Start(testObj);
thread2.Join();
}

//Use the object normally
static void LockIt(object state)
{
try
{

System.Diagnostics.Trace.WriteLine(string.Format("Preparing to lock with thread {0:x}", AppDomain.GetCurrentThreadId()));
SimpleObjectLib.ISimpleton testObj = (SimpleObjectLib.ISimpleton)state;
testObj.DoSomething();
}
catch (AccessViolationException)
{
//CLR is “helpful” and wraps the SHE instead of just crashing
}
System.Diagnostics.Trace.WriteLine("Lock Released?");
}
}
}


The output is:
With the /EHa switch (note the output will be the same with or without the try / catch in DoSomething)

Preparing to lock with thread 5e0
Critical section owning thread 00000000 count -1
First-chance exception at 0x5e759ade (SimpleObject.dll) in LockUp.exe: 0xC0000005: Access violation writing location 0x00000000.
Caught it.
Exiting
Lock Released?
The thread 0x5e0 has exited with code 0 (0x0).
The thread 'Win32 Thread' (0x5e0) has exited with code 0 (0x0).
Preparing to lock with thread 15ac
Critical section owning thread 00000000 count -1
First-chance exception at 0x5e759ade (SimpleObject.dll) in LockUp.exe: 0xC0000005: Access violation writing location 0x00000000.
Caught it.
Exiting
Lock Released?

With no /EH switch or /EHsc
Preparing to lock with thread b48
Critical section owning thread 00000000 count -1
First-chance exception at 0x5e8294a2 (SimpleObject.dll) in LockUp.exe: 0xC0000005: Access violation writing location 0x00000000.
A first chance exception of type 'System.AccessViolationException' occurred in LockUp.exe
Lock Released?
The thread 0xb48 has exited with code 0 (0x0).
The thread 'Win32 Thread' (0xb48) has exited with code 0 (0x0).
Preparing to lock with thread 570
Trouble!
Critical section owning thread 00000B48 count -2

Wednesday, January 7, 2009

How to use C# to read performance data from a log file

Problem:  The counter classes provided by .Net framework are useful for reading and writing live counter values but do not appear to be able to bind to performance logs.

Soltuion: Use the PDH API via P/Invoke. Performance Monitoring in MSDN provides all of the basic information on the PDH API and some samples in C++. 

Notes:
  • Some counters require multiple samples to be calculated.  PdhCollectQueryData will need to be called multiple times prior to PdhGetFormattedCounterValue in that case.  This demo is using Processor \% Processor Time which requires two samples.
  • Multiple log files can be queried together using PdhBindInputDataSource along with the API functions ending in "H".
  • Use System.Runtime.Interop.Marshal to unpack strings from PDH_FMT_COUNTERVALUE if required.
The following is a simple console application that demonstrates using the API in C# and is a basic port of the example provided in MSDN.  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Security.Permissions;
using System.Security;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
 
namespace PdhLogDemo
{
 
    #region PDH Definitions
    /// 
    /// A safe wrapper around a PDH Log handle.
    /// 
    /// 
    /// Use this along with PdhBindInputDataSource and the "H" APIs to bind multiple logs together
    /// 
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public class PdhLogHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public PdhLogHandle() : base(true)
        {
 
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle()
        {
            return PdhApi.PdhCloseLog(handle, PdhApi.PDH_FLAGS_CLOSE_QUERY) == 0;
        }
    }
 
    /// 
    /// A safe wrapper arounda query handle
    /// 
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public class PdhQueryHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public PdhQueryHandle()
            : base(true)
        {
 
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle()
        {
            return PdhApi.PdhCloseQuery(handle) == 0;
        }
    }
 
    /// 
    /// A safe handle around a counter
    /// 
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public class PdhCounterHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public PdhCounterHandle()
            : base(true)
        {
 
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle()
        {
            return PdhApi.PdhRemoveCounter(handle) == 0;
        }
    }
 
    /// 
    /// The value of a counter as returned by  API.
    /// 
    /// In C/C++ this is a union.
    [StructLayout(LayoutKind.Explicit)]
    public struct PDH_FMT_COUNTERVALUE
    {
        [FieldOffset(0)]
        public UInt32 CStatus;
        [FieldOffset(1)]
        public int longValue;
        [FieldOffset(1)]
        public double doubleValue;
        [FieldOffset(1)]
        public long longLongValue;
        [FieldOffset(1)]
        public IntPtr AnsiStringValue;
        [FieldOffset(1)]
        public IntPtr WideStringValue;
    }
 
    /// 
    /// The requested format for the  API.
    /// 
    [Flags()]
    public enum PdhFormat : uint 
    {
        PDH_FMT_RAW = 0x00000010,
        PDH_FMT_ANSI = 0x00000020,
        PDH_FMT_UNICODE = 0x00000040,
        PDH_FMT_LONG = 0x00000100,
        PDH_FMT_DOUBLE = 0x00000200,
        PDH_FMT_LARGE = 0x00000400,
        PDH_FMT_NOSCALE = 0x00001000,
        PDH_FMT_1000 = 0x00002000,
        PDH_FMT_NODATA = 0x00004000
    }
 
    /// 
    /// Static class containing some usefull PDH API's
    /// 
    [SuppressUnmanagedCodeSecurity()]
    internal class PdhApi
    {
        #region A few common flags and status codes
        public const UInt32 PDH_FLAGS_CLOSE_QUERY = 1;
        public const UInt32 PDH_NO_MORE_DATA = 0xC0000BCC;
        public const UInt32 PDH_INVALID_DATA = 0xC0000BC6;
        public const UInt32 PDH_ENTRY_NOT_IN_LOG_FILE = 0xC0000BCD;
        #endregion
 
        /// 
        /// Opens a query handle
        /// 
        /// 
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern UInt32 PdhOpenQuery(
         string szDataSource,
         IntPtr dwUserData,
         out  PdhQueryHandle phQuery);
 
        /// 
        /// Opens a query against a bound input source.
        /// 
        /// 
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhOpenQueryH(
         PdhLogHandle hDataSource,
         IntPtr dwUserData,
         out  PdhQueryHandle phQuery);
 
        /// 
        /// Binds multiple logs files together.
        /// 
        /// Use this along with the API's ending in 'H' to string multiple files together.
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhBindInputDataSource(
            out PdhLogHandle phDataSource,
            string szLogFileNameList);
 
        /// 
        /// Closes a handle to a log
        /// 
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhCloseLog(
            IntPtr hLog,
            long dwFlags);
 
        /// 
        /// Closes a handle to the log
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhCloseQuery(
            IntPtr hQuery);
 
        /// 
        /// Removes a counter from the given query.
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhRemoveCounter(
            IntPtr hQuery);
 
        /// 
        /// Adds a counter the query and passes out a handle to the counter.
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern UInt32 PdhAddCounter(
            PdhQueryHandle hQuery,
            string szFullCounterPath,
            IntPtr dwUserData,
            out PdhCounterHandle phCounter);
 
        /// 
        /// Retrieves a sample from the source.
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhCollectQueryData(
            PdhQueryHandle phQuery);
 
        /// 
        /// Retrieves a specific counter value in the specified format.
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        [DllImport("pdh.dll", SetLastError = true)]
        public static extern UInt32 PdhGetFormattedCounterValue(
            PdhCounterHandle phCounter,
            PdhFormat dwFormat,
            IntPtr lpdwType,
            out PDH_FMT_COUNTERVALUE pValue);
    }
    #endregion
 
 
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("PdhLogDemo is a simple C# port of the sample application describe in the PDH API MSDN help.");
                Console.WriteLine("The sample takes a single argument which should be the path to perf log containing at least two samples");
                Console.WriteLine(@"of the counter \Processor(0)\% Processor Time");
                return;
            }
 
            //the name of the counter
            string counterName = @"\Processor(0)\% Processor Time";
            //the location of the log
            string log = args[0]; //@"C:\perflogs\Admin\New Data Collector Set\000002\New Data Collector.blg";
            PdhQueryHandle query;
            PdhCounterHandle counter;
 
            UInt32 result = PdhApi.PdhOpenQuery(log, IntPtr.Zero, out query);
            if (result != 0)
            {
                Console.WriteLine("Unable to open query.  Return was: " + result.ToString());
                return;
            }
 
            //Add the counter that we want to see
            //The PdhEnumXXX methods can be used to discover them
            result = PdhApi.PdhAddCounter(query, counterName, IntPtr.Zero, out counter);
            if (result != 0)
            {
                //0xC0000BCD (PDH_ENTRY_NOT_IN_LOG_FILE) means the counter was not found in the set
                Console.WriteLine("Unable to add counter.  Return was: " + result.ToString());
                return;
            }
 
            //Since we're using % processor time which requires two samples call this once initially to pull the first sample
            result = PdhApi.PdhCollectQueryData(query);
            if (result == PdhApi.PDH_NO_MORE_DATA)
            {
                Console.WriteLine("The log file contains no data.");
                return;
            }
 
            do
            {
                result = PdhApi.PdhCollectQueryData(query);
                if (result == 0)
                {
                    PDH_FMT_COUNTERVALUE value;
                    result = PdhApi.PdhGetFormattedCounterValue(counter, PdhFormat.PDH_FMT_DOUBLE, IntPtr.Zero, out value);
                    //Check CStatus to make sure it is ok
                    //0xC0000BC6 (PDH_INVALID_DATA) can be returned when more samples are needed to calculate the value.  Call PdhCollectQueryData
                    if (value.CStatus == 0)
                    {
                        Console.WriteLine("Value = " + value.doubleValue.ToString());
                    }
                    else
                    {
                        Console.WriteLine("Bad CStatus on Value: " + value.CStatus.ToString());
                    }
                }
            }
            while (result == 0);
 
        }
    }
}
 

Thursday, December 4, 2008

Regasm and GAC fun (MSB3217 and RA0000)

Problem: A project builds an assembly successfully but An indicates an error similar to the following:
C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(2937,9): error MSB3217: Cannot register assembly "C:\Source\Blog\RegasmExamples2\bin\Debug\RegasmExamples2.dll". Method 'SomeProperty' in type 'RegasmExamples2.BrokenClass' from assembly 'RegasmExamples2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
When using the "Register for COM interop" option in a .Net project

Or

RegAsm : error RA0000: Method 'get_SomeOtherProperty' in type 'RegasmExamples2.BrokenClass2' from assembly 'RegasmExamples2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=72d8f57e6c124ae0' does not have an implementation.C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3314,13): error MSB3073: The command "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm C:\Source\Blog\RegasmExamples\RegasmExamples2\bin\Debug\RegasmExamples2.dll" exited with code 100.
When using Regasm as a post build step in a .Net Project


Common Causes:

  • An interop assembly is in the GAC that has a different interface definition than the one that was referenced and built against but they have the same strong name. Remove the incorrect version of the interop from the GAC and identify the correct version of the interop and install it in the GAC if required.
  • An older version of the project's target assembly is installed in the GAC that does not implement the new interface definition but has the same strong name. Remove the build target from the GAC either manually or with a pre-build step.

Notes:

  • The location of the assembly that is used when building is defined by the reference and may be different than the assembly probing sequence at runtime.
  • Regasm is a .Net application and will follow the standard assembly probing sequence. If an older build of the assembly is in the GAC (with the same assembly version, culture, and architecture) then it will use that binary.
  • This problem occurs most commonly when someone is monkeying around with interfaces (typically when "fleshing" them out) or as a regression when building mutliple versions of a product on a development machine and someone decided to make an interface "better" instead of extending it with a new one.

Tools to Diagnose:

  • File Monitor or ProcessMonitor by Sysinternals to monitor the build and registration
  • Reflector to view the interface definition of the assembly in the GAC