STDMETHODIMP(ULONG) Release(void)
   {
   LONG res = -mcRef;
   if (res == 0) delete this;
   return res;
   }
 
   Для кэширования обновленного счетчика ссылок необходимо использовать временную переменную, так как нельзя обращаться к элементам данных объекта после того, как объект уже уничтожен.
   Заметим, что показанные реализации Addref и Release используют собственные операторы инкремента и декремента (увеличения и уменьшения на единицу). Для простой реализации это весьма разумно, так как СОМ не допускает более одного потока для обращения к объекту до тех пор, пока конструктор не обеспечит явный многопоточный доступ (почему и как конструктор сделает это, подробно описано в главе 5). В случае объектов, доступных в многопоточной среде, для автоматического подсчета ссылок следует использовать подпрограммы Win32 InterlockedIncrement/InterlockedDecrement:
 
   STDMETHODIMP(ULONG) AddRef(void)
   {
   return InterlockedIncrement(&mcRef);
   }
   STDMETHODIMP(ULONG) Release(void)
   {
   LONG res = InterlockedDecrement(&mcRef);
   if (res == 0) delete this; return res;
   }
 
   Этот код несколько менее эффективен, чем версии, использующие собственные операторы C++. Но, вообще говоря, разумнее использовать менее эффективные варианты InterlockedIncrement / InterlockedDecrement, так как известно, что они надежны во всех ситуациях и освобождают разработчика от необходимости сохранять две версии практически одинакового кода.
   Показанные выше реализации AddRef и Release предполагают, что объект может размещаться только в динамически распределяемой области памяти (в «куче») с использованием С++-оператора new. В определении класса деструктор сделан защищенной операцией для обеспечения того, чтобы ни один экземпляр класса не был определен никаким другим способом. Однако иногда желательно иметь объекты, не размещенные в «куче». Для этих объектов вызов delete в последнем вызове Release был бы гибельным. Так как единственной причиной для того, чтобы объект в первую очередь поддерживал счетчик ссылок, была необходимость вызова delete this, допустимо оптимизировать счетчик ссылок для объектов, не содержащихся в динамически распределяемой области памяти:
 
   STDMETHODIMP(ULONG) GlobalVar::AddRef(void)
   {
   return 2;
   // any non-zero value is legal
   // допустима любая ненулевая величина
   }
   STDMETHODIMP(ULONG) GlobalVar::Release (void)
   {
   return 1;
   // any non-zero value is legal
   // допустима любая ненулевая величина
   }
 
   Эта реализация использует тот факт, что результаты AddRef и Release служат только для сведения и не обязаны быть точными.
   При наличии реализации AddRef и Release единственным еще не реализованным методом из IUnknown остается QueryInterface. Его реализации должны отслеживать иерархию типов объекта и использовать статические приведения типов для возврата правильного типа указателя для всех поддерживаемых интерфейсов. Для определения класса PugCat, рассмотренного ранее, следующий код является корректной реализацией QueryInterface : STDMETHODIMP
 
   PugCat::QueryInterface(REFIID riid, void **ppv)
   {
   assert(ppv != 0);
   // or return EPOINTER in production
   // или возвращаем EPOINTER в реальный продукт
   if (riid == IIDIPug) *ppv = staticcast<IPug*>(this);
   else if (riid == IIDIDog) *ppv = staticcast<IDog*>(this);
   else if (riid == IIDIAnimal)
   // cat or pug?
   // кот или мопс?
   *ppv == staticcast<IDog*>(this);
   else if (riid == IIDIUnknown)
   // cat or pug?
   // кот или мопс?
   *ppv = staticcast<IDog*>(this);
   else if (riid == IIDICat) *ppv = staticcast<ICat*>(this);
   else
   {
   // unsupported interface
   // неподдерживаемый интерфейс
   *ppv = 0;
   return ENOINTERFACE;
   }
   // if we reach this point, *ppv is non-null
   // and must be AddRef'ed (guideline A2)
   // если мы дошли до этого места, то *ppv ненулевой
   // и должен быть обработан AddRef'ом ( принцип A2)
   reinterpretcast<IUnknown*>(*ppv)->AddRef();
   return SOK;
   }
 
   Использование staticcast более предпочтительно, чем традиционные приведения типа в стиле С:
 
   *ppv = (IPug*)this;
 
   так как вариант staticcast вызовет ошибку этапа компиляции, если произведенное приведение типа не согласуется с существующим базовым классом.
   Заметим, что в показанной здесь реализации QueryInterface при запросе на интерфейс, поддерживающийся более чем одним базовым интерфейсом (например, IUnknown, IAnimal) приведение типа должно явно выбрать более определенный базовый класс. Например, для класса PugCat такой вполне безобидно выглядящий код не откомпилируется:
 
   if (riid == IIDIUnknown) *ppv = staticcast<IUnknown*>(this);
 
   Этот код не пройдет компиляцию, поскольку такое приведение типа является неоднозначным и может соответствовать более чем одному базовому классу. Это было показано в случае FastString и IExtensibleObject из предыдущей главы. Вместо этого реализация должна более точно выбрать тип для приведения:
 
   if (riid == IIDIUnknown) ppv = staticcast<IDog*>(this);
   или if (riid == IIDIUnknown) ppv = staticcast<ICat*>(this);
 
   Каждый из этих двух фрагментов кода допустим для реализации PugCat. Первый вариант предпочтительнее, так как многие компиляторы выдают несколько более эффективный код, когда использован крайний левый базовый класс[1].
 

Использование указателей интерфейса СОМ

   Программисты C++ должны использовать методы IUnknown явно, потому что перевод модели СОМ на язык C++ не предусматривает использования среды поддержки выполнения (runtime layer) между кодом клиента и кодом объекта. Поэтому IUnknown можно рассматривать просто как набор обещаний, которые все программисты СОМ дают друг другу. Это дает преимущество программистам C++, так как C++ может создавать код, который потенциально более эффективен, чем языки, которые требуют такого динамического слоя при работе с СОМ.
   При работе на Visual Basic и Java, в отличие от C++, программисты никогда не видят QueryInterface, AddRef или Release. Для этих двух языков детали IUnknown надежно скрыты за поддерживающей эти языки виртуальной машиной. На Java QueryInterface просто отображается в приведение типа:
 
   public void TryToSnoreAndIgnore(Object obj)
   {
   IPug pug;
   try
   {
   pug = (IPug)obj;
   // VM calls QueryInterface
   // VM вызывает QueryInterface
   pug.Snore();
   }
   catch (Throwable ex)
   {
   // ignore method or QI failures
   // игнорируем сбой метода или QI
   }
   ICat cat;
   try
   {
   cat = (ICat)obj;
   // VM calls QueryInterface
   // VM вызывает QueryInterface
   cat.IgnoreMaster();
   }
   catch (Throwable ex)
   {
   // ignore method or QI failures
   // игнорируется сбой метода или QI
   }
   }
 
   Visual Basic не требует от клиентов приведения типов. Вместо этого, когда указатель интерфейса присваивается переменной неподходящего типа, виртуальная машина (VM) Visual Basic молча вызывает QueryInterface от имени клиента:
 
   Sub TryToSnoreAndIgnore(obj as Object)
   On Error Resume Next
   ' ignore errors
   ' игнорируем ошибки
   Dim pug as IPug
   Set pug = obj
   ' VM calls QueryInterface
   ' VM вызывает QueryInterface
   If Not (pug is Nothing)
   Then pug.Snore
   End
   if Dim cat as ICat
   Set cat = obj
   ' VM calls QueryInterface
   ' VM вызывает QueryInterface
   If Not (cat is Nothing)
   Then cat.IgnoreMaster
   End if End Sub
 
   Обе виртуальные машины, как Java, так и Visual Basic, выбросят при сбое QueryInterface исключения. В обеих средах виртуальная машина автоматически преобразует языковую концепцию живучести переменной в явные вызовы AddRef и Release , избавляя клиента и от этой подробности.
   Одна методика, потенциально способная упростить использование в СОМ интерфейсных указателей из C++, состоит в том, чтобы скрыть их в классе интеллектуальных указателей. Это устраняет необходимость необработанных (raw ) вызовов методов IUnknown. В идеале интеллектуальный указатель СОМ будет:
 
   Корректно обрабатывать каждый вызов Add/Release во время присваивания.
   Автоматически уничтожать интерфейс в деструкторе, что снижает возможность утечки ресурса и повышает безопасность (надежность) исключений.
   Использует систему типов C++ для упрощения вызовов QueryInterface.
   Прозрачным образом (незаметно для пользователя или программы) замещает необработанные интерфейсные указатели в существующем коде без компрометации правильности программы.
 
   Последний пункт представляет собой чрезвычайно серьезную проблему. Интернет забит интеллектуальными СОМ-указателями, которые проделывают прозрачную замену обычных указателей, но при этом вводят столько же скрытых ошибок, сколько претендуют устранить. Visual C++ 5.0, например, фактически действует с тремя такими указателями (один на MSC, другой на ATL, а третий для поддержки Direct-to-COM), которые очень просто использовать как правильно, так и неправильно. В сентябрьском 1995 года и в февральском 1996 года выпусках "C++ Report " опубликованы две статьи, где на примерах показаны различные подводные камни при использовании интеллектуальных указателей[1]. Исходный код, который приводится в данной книге, содержит интеллектуальный СОМ-указатель, созданный в процессе написания этих двух статей. В нем делается попытка учесть общие ошибки, случающиеся как в простых, так и в интеллектуальных указателях СОМ. Класс интеллектуальных указателей, SmartInterface , имеет два шаблонных (template) параметра: тип интерфейса в C++ и указатель на соответствующий IID . Все обращения к методам IUnknown скрыты путем перегрузки операторов:
 
   #include «smartif.h»
   void TryToSnoreAndIgnore(/* [in] */ IUnknown *pUnk)
   {
   // copy constructor calls QueryInterface
   // конструктор копирования вызывает QueryInterface
   SmartInterface<IPug, &IIDIPug> pPug = pUnk;
   if (pPug)
   // typecast operator returns null-ness
   // оператор приведения типа возвращает нуль pPug->Snore();
   // operator-> returns safe raw ptr
   // оператор -> возвращает прямой указатель
   // copy constructor calls QueryInterface
   // конструктор копирования вызывает QueryInterface
   SmartInterface<ICat, &IIDICat> pCat = pUnk;
   if (pCat)
   // typecast operator returns null-ness
   // оператор приведения типа возвращает нуль pCat->IgnoreMaster();
   // operator-> returns safe raw ptr
   // оператор -> возвращает прямой указатель
   // destructors release held pointers on leaving scope
   // деструкторы освобождают удерживаемые указатели при
   // выходе из области действия
   }
 
   Интеллектуальные указатели выглядят очень привлекательными на первый взгляд, но могут оказаться очень опасными, так как погружают программиста в дремотное состояние; будто бы ничего страшного, относящегося к СОМ, произойти не может. Интеллектуальные указатели действительно решают реальные проблемы, особенно связанные с исключениями; однако при неосторожном употреблении они могут внести столько же дефектов, сколько они предотвращают. Например, многие интеллектуальные указатели позволяют вызывать любой метод интерфейса через оператор интеллектуального указателя –>. К сожалению, это позволяет клиенту вызывать Release с помощью этого оператора-стрелки без сообщения базовому интеллектуальному указателю о том, что его автоматический вызов Release в его деструкторе теперь является излишним и недопустимым.
 

Оптимизация QueryInterface

   Фактически реализация QueryInterface, показанная ранее в этой главе, очень проста и легко может поддерживаться любым программистом, имеющим хоть некоторое представление о СОМ и C++. Тем не менее, многие среды и каркасы приложений поддерживают реализацию, управляемую данными. Это помогает достичь большей расширяемости и эффективности благодаря уменьшению размера кода. Такие реализации предполагают, что каждый совместимый с СОМ класс предусматривает таблицу, которая отображает каждый поддерживаемый IID на какой-нибудь аспект объекта, используя фиксированные смещения или какие-то другие способы. В сущности, реализация QueryInterface , приведенная ранее в этой главе, строит таблицу, основанную на скомпилированном машинном коде для каждого из последовательных операторов if, а фиксированные смещения вычисляются с использованием оператора staticcast (staticcast просто добавляет смещение базового класса, чтобы найти совместимый с типом указатель vptr).
   Чтобы реализовать управляемый таблицей QueryInterface, необходимо сначала определить, что эта таблица будет содержать. Как минимум, каждый элемент таблицы должен содержать указатель на IID и некое дополнительное состояние, которое позволит реализации найти указатель vptr объекта для запрошенного интерфейса. Хранение указателя функции в каждом элементе таблицы придаст этому способу максимальную гибкость, так как это даст возможность добавлять новые методики поиска интерфейсов к обычному вычислению смещения, которое используется для приведения к базовому классу. Исходный код в приложении к данной книге содержит заголовочный файл inttable.h , который определяет элементы таблицы интерфейсов следующим образом:
 
   // inttable.h (book-specific header file)
   // inttable.h (заголовочный файл, специфический для этой книги)
   // typedef for extensibility function
   // typedef для функции расширяемости
   typedef HRESULT (*INTERFACEFINDER) (void *pThis, DWORD dwData, REFIID riid, void **ppv);
   // pseudo-function to indicate entry is just offset
   // псевдофункция для индикации того, что запись просто
   // является смещением
   #define ENTRYISOFFSET INTERFACEFINDER(-1)
   // basic table layout // представление базовой таблицы
   typedef struct INTERFACEENTRY
   {
   const IID * pIID;
   // the IID to match
   // соответствующий IID
   INTERFACEFINDER pfnFinder;
   // функция finder DWORD dwData;
   // offset/aux data
   // данные по offset/aux
   } INTERFACEENTRY;
 
   Заголовочный файл также содержит следующие макросы для создания интерфейсных таблиц внутри определения класса:
 
   // Inttable.h (book-specific header file)
   // Inttable.h (заголовочный файл, специфический для данной книги)
   #define BASEOFFSET(ClassName, BaseName) \ (DWORD(staticcast<BaseName*>(reinterpretcast\ <ClassName*>(0x10000000))) – 0х10000000)
   #define BEGININTERFACETABLE(ClassName) \ typedef ClassName ITCls;\ const INTERFACEENTRY *GetInterfaceTable(void) {\ static const INTERFACEENTRY table [] = {\
   #define IMPLEMENTSINTERFACE(Itf) \ {&IID##Itf,ENTRYISOFFSET,BASEOFFSET(ITCls,Itf)},
   #define IMPLEMENTSINTERFACEAS(req, Itf) \ {&IID##req,ENTRYISOFFSET, BASEOFFSET(ITCls, Itf)},
   #define ENDINTERFACETABLE() \ { 0, 0, 0 } }; return table; }
 
   Все, что требуется, – это стандартная функция, которая может анализировать интерфейсную таблицу в ответ на запрос QueryInterface. Такая функция содержится в файле Inttable.h:
 
   // inttable.cpp (book-specific source file)
   // inttable.h (заголовочный файл, специфический для данной книги)
   HRESULT InterfaceTableQueryInterface(void *pThis, const INTERFACEENTRY *pTable, REFIID riid, void **ppv)
   {
   if (InlineIsEqualGUID(riid, IIDIUnknown))
   {
   // first entry must be an offset
   // первый элемент должен быть смещением
   *ppv = (char*)pThis + pTable->dwData;
   ((Unknown*) (*ppv))->AddRef () ;
   // A2
   return SOK;
   } else
   {
   HRESULT hr = ENOINTERFACE;
   while (pTable->pfnFinder)
   {
   // null fn ptr == EOT
   if (!pTable->pIID || InlineIsEqualGUID(riid,*pTable->pIID))
   {
   if (pTable->pfnFinder == ENTRYISOFFSET)
   {
   *ppv = (char*)pThis + pTable->dwData;
   ((IUnknown*)(*ppv))->AddRef();
   // A2
   hr = SOK;
   break;
   } else
   {
   hr = pTable->pfnFinder(pThis, pTable->dwData, riid, ppv);
   if (hr == SOK) break;
   }
   }
   pTable++;
   }
   if (hr!= SOK)
   *ppv = 0;
   return hr;
   }
   }
 
   Получив указатель на запрошенный объект, InterfaceTableQueryInterface сканирует таблицу в поисках элемента, соответствующего запрошенному IID, и либо добавляет соответствующее смещение, либо вызывает соответствующую функцию. Приведенный выше код использует усовершенствованную версию IsEqualGUID, которая генерирует несколько больший код, но результаты по скорости примерно на 20-30 процентов превышают данные по существующей реализации, которая не управляется таблицей. Поскольку код для InterfaceTableQueryInterface появится в исполняемой программе только один раз, это весьма неплохой компромисс.
   Очень легко автоматизировать поддержку СОМ для любого класса C++, основанную на таком табличном управлении, простым использованием С-препроцессора. Следующий фрагмент из заголовочного файла impunk.h определяет QueryInterface, AddRef и Release для объекта, использующего интерфейсные таблицы и расположенного в динамически распределяемой области памяти:
 
   // impunk.h (book-specific header file)
   // impunk.h (заголовочный файл, специфический для данной книги)
   // AUTOLONG is just a long that constructs to zero
   // AUTOLONG – это просто long, с конструктором,
   // устанавливающим значение в О
   struct AUTOLONG
   {
   LONG value;
   AUTOLONG (void) : value (0) {}
   };
 
   #define IMPLEMENTUNKNOWN(ClassName)
   \ AUTOLONG mcRef;
   \ STDMETHODIMP QueryInterface(REFIID riid, void **ppv){
   \ return InterfaceTableQueryInterface(this,
   \ GetInterfaceTable(), riid, ppv);
   \ }
   \ STDMETHODIMP(ULONG) AddRef(void) {
   \ return InterlockedIncrement(&mcRef.value);
   \ }
   \ STDMETHODIMP(ULONG) Release(void) {
   \ ULONG res = InterlockedDecrement(&mcRef.value) ;
   \ if (res == 0)
   \ delete this;
   \ return res;
   \ }
 
   Настоящий заголовочный файл содержит дополнительные макросы для поддержки объектов, не находящихся в динамически распределяемой области памяти.
   Для реализации примера PugCat, уже встречавшегося в этой главе, необходимо всего лишь удалить текущие реализации QueryInterface, AddRef и Release и добавить соответствующие макросы:
 
   class PugCat : public IPug, public ICat
   {
   protected:
   virtual ~PugCat(void);
   public: PugCat(void);
   // IUnknown methods
   // методы IUnknown
   IMPLEMENTUNKNOWN (PugCat)
   BEGININTERFACETABLE(PugCat)
   IMPLEMENTSINTERFACE(IPug)
   IMPLEMENTSINTERFACE(IDog)
   IMPLEMENTSINTERFACEAS(IAnimal,IDog)
   IMPLEMENTSINTERFACE(ICat)
   ENDINTERFACETABLE()
   // IAnimal methods
   // методы IAnimal
   STDMETHODIMP Eat(void);
   // IDog methods
   // методы IDog
   STDMETHODIMP Bark(void);
   // IPug methods
   // методы IPug
   STDMETHODIMP Snore(void);
   // ICat methods
   // методы ICat
   STDMETHODIMP IgnoreMaster(void);
   };
 
   Когда используются эти макросы препроцессора, для поддержки IUnknown не требуется никакого дополнительного кода. Все, что осталось сделать, это реализовать текущие методы интерфейса, которые делают этот класс уникальным.
 

Типы данных

   Все интерфейсы СОМ должны быть определены в IDL. IDL позволяет описывать довольно сложные типы данных в стиле, не зависящем от языка и платформы. Рисунок 2.6 показывает базовые типы, которые поддерживаются IDL, и их отображения в языки С, Java и Visual Basic. Целые и вещественные типы не требуют объяснений. Первые «интересные» типы данных, встречающиеся при программировании в СОМ, – это символы и строки.
 
 
   Все символы в СОМ представлены с использованием типа данных OLECHAR. Для Windows NT, Windows 95, Win32s и Solaris OLECHAR – это просто typedef для типа данных С wchar_t. Специфика других платформ описана в соответствующих документациях. Платформы Win32 используют тип данных wchar_t для представления 16-битных символов Unicode[1]. Поскольку типы указателей в IDL созданы так, что указывают на одиночные переменные, а не на массивы, то IDL вводит атрибут [string], чтобы подчеркнуть, что указатель указывает на массив-строку с завершающим нулем:
 
   HRESULT Method([in, string] const OLECHAR *pwsz);
 
   Для определения строк и символов, совместимых с OLECHAR, в СОМ введен макрос OLESTR, который приписывает букву L перед строковой или символьной константой, информируя таким образом компилятор о том, что эта константа имеет тип wchar_t. Например, правильным будет такой способ инициализировать указатель OLECHAR с помощью строкового литерала:
 
   const OLECHAR *pwsz = OLESTR(«Hello»);
 
   Под Win32 или Solaris это эквивалентно
 
   const wchar_t *pwsz = L"Hello";
 
   Первый вариант предпочтительней, так как он будет надежно компилироваться на всех платформах.
   Поскольку часто возникает необходимость копировать строки на основе типа wchar_t в обычные буфера на основе char, то динамическая библиотека С предлагает две процедуры для преобразования типов:
 
   size_t mbstowcs(wchar_t *pwsz, const char *psz, int cch);
   size_t wcstombs(char *psz, const wchar_t *pwsz, int cch);
 
   Эти две процедуры работают аналогично динамической С-процедуре strncpy, за исключением того, что в эти процедуры как часть операции копирования включено расширение или сужение строки. Следующий код показывает, как параметр метода, размещенный в OLECHAR, можно скопировать в элемент данных, размещенный в char:
 
   class BigDog : public ILabrador
   {
   char m_szName[1024] ;
   public:
   STDMETHODIMP SetName(/* [in,string]*/ const OLECHAR *pwsz)
   {
   HRESULT hr = S_OK;
   size_t cb = wcstombs(m_szName, pwsz, 1024);
   // check for buffer overflow or bad conversion
   // проверяем переполнение буфера или неверное преобразование
   if (cb == sizeof(m_szName) || cb == (size_t)-1)
   {
   m_szName[0] = 0; hr = E_INVALIDARG;
   }
   return hr;
   }
   };
 
   Этот код является довольно простым, хотя программист должен осознавать, что используются два различных типа символов. Несколько более сложный (и чаще встречающийся) случай – преобразование между типами данных OLECHAR и TCHAR из Win32. Так как OLECHAR условно компилируется как char или wchar_t, то при реализации метода необходимо должным образом рассмотреть оба сценария:
 
   class BigDog : public ILabrador
   {
   TCHAR m_szName[1024];
   // note TCHAR-based string
   // отметим строку типа TCHAR
   public:
   STDMETHODIMP SetName( /*[in,string]*/ const OLECHAR *pwsz)
   {
   HRESULT hr = S_OK;
   #ifdef UNICODE
   // Unicode build (TCHAR == wchar_t)
   // конструкция Unicode (TCHAR == wchar_t)
   wcsncpy(m_szName, pwsz, 1024);
   // check for buffer overflow
   // проверка на переполнение буфера
   if (m_szName[1023] != 0)
   {
   m_szName[0] = 0;
   hr = E_INVALIDARG;
   }
   #else
   // Non-Unicode build (TCHAR == char)
   // не является конструкцией Unicode (TCHAR == char)
   size_t cb = wcstombs(m_szName, pwsz, 1024);
   // check for buffer overflow or bad conversion
   // проверка переполнения буфера или ошибки преобразования
   if (cb == sizeof(m_szName) || cb == (size_t)-1)
   {
   m_szName[0] =0;
   hr = E_INVALIDARG; 
   } 
   #endif return hr;
   }
   };
 
   Очевидно, операции с преобразованиями OLECHAR в TCHAR значительно сложнее. Но, к сожалению, это самый распространенный сценарий при программировании в СОМ на базе Win32.
   Одним из подходов к упрощению преобразования текста является применение системы типов C++ и использование перегрузки функций для выбора нужной строковой процедуры, построенной на типах параметров. Заголовочный файл ustring.h из приложения к этой книге содержит семейство библиотечных строковых процедур, аналогичных стандартным библиотечным процедурам С, которые находятся в файле string.h. Например, функция strncpy имеет четыре соответствующих процедуры, зависящие от каждого из параметров, которые могут быть одного из двух символьных типов (wchar_t или char):
 
   // from ustring.h (book-specific header)
   // из ustring.h (заголовок, специфический для данной книги)
   inline bool ustrncpy(char *p1, const wchar_t *p2, size_t c)
   {
   size_t cb = wcstombs(p1, p2, c);
   return cb != c && cb != (size_t)-1;
   };
   inline bool ustrncpy(wchar_t *p1, const wchar_t *p2, size_t c)
   {
   wcsncpy(p1, p2, c);
   return p1[c – 1] == 0;
   };
   inline bool ustrncpy(char *p1, const char *p2, size_t c)
   {
   strncpy(p1, p2, c);
   return p1[c – 1] == 0;
   };
   inline bool ustrncpy(wchar_t *p1, const char *p2, size_t c)
   {
   size_t cch = mbstowcs(p1, p2, c);
   return cch != c && cch != (size_t)-1;
   }
 
   Отметим, что для любого сочетания типов идентификаторов может быть найдена соответствующая перегруженная функция ustrncpy, причем результат показывает, была или нет вся строка целиком скопирована или преобразована. Поскольку эти процедуры определены как встраиваемые (inline) функции, их использование не внесет никаких затрат при выполнении. С этими процедурами предыдущий фрагмент кода станет значительно проще и не потребует условной компиляции:
 
   class BigDog : public ILabrador
   {
   TCHAR m_szName[1024];
   // note TCHAR-based string
   // отметим строку типа TCHAR
   public:
   STDMETHODIMP SetName(/* [in,string] */ const OLECHAR *pwsz)
   {
   HRESULT hr = S_OK;
   // use book-specific overloaded ustrncpy to copy or convert
   // используем для копирования и преобразования
   // перегруженную функцию ustrncpy, специфическую для данной книги
   if (!ustrncpy(m_szName, pwsz, 1024))
   {
   m_szName[0] = 0;
   hr = E_INVALIDARG;
   } return hr;
   }
   };
 
   Соответствующие перегруженные функции для процедур strlen, strcpy и strcat также включены в заголовочный файл ustring.h.
   Использование перегрузки библиотечных функций для копирования строк из одного буфера в другой, как это показано выше, обеспечивает лучшее качество исполнения, уменьшает размер кода и непроизводительные издержки программиста. Однако часто возникает ситуация, когда одновременно используются СОМ и API-функции Win32, что не дает возможности применить эту технику. Рассмотрим следующий фрагмент кода, читающий строку из элемента редактирования и преобразующий ее в IID:
 
   HRESULT IIDFromHWND(HWND hwnd, IID& riid)
   {
   TCHAR szEditText[1024];
   // call a TCHAR-based Win32 routine
   // вызываем TCHAR-процедуру Win32
   GetWindowText(hwnd, szEditText, 1024);
   // call an OLECHAR-based СОМ routine
   // вызываем OLECHAR-процедуру СОМ
   return IIDFromString(szEditText, &riid);
   }
 
   Допуская, что этот код скомпилирован с указанным символом С-препроцессора UNICODE; он работает безупречно, так как TCHAR и OLECHAR являются просто псевдонимами wchar_t и никакого преобразования не требуется. Если же функция скомпилирована с версией Win32 API, не поддерживающей Unicode, то TCHAR является псевдонимом для char, и первый параметр для IIDFromString имеет неправильный тип. Чтобы решить эту проблему, нужно провести условную компиляцию: