msctf/tests: Setup test framework for ITfThreadMgrEventSink.
[wine] / dlls / msctf / tests / inputprocessor.c
1 /*
2  * Unit tests for ITfInputProcessor
3  *
4  * Copyright 2009 Aric Stewart, CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdio.h>
22
23 #define COBJMACROS
24 #include "wine/test.h"
25 #include "winuser.h"
26 #include "initguid.h"
27 #include "shlwapi.h"
28 #include "shlguid.h"
29 #include "comcat.h"
30 #include "msctf.h"
31
32 static ITfInputProcessorProfiles* g_ipp;
33 static LANGID gLangid;
34 static ITfCategoryMgr * g_cm = NULL;
35 static ITfThreadMgr* g_tm = NULL;
36
37 static DWORD tmSinkCookie;
38 static DWORD tmSinkRefCount;
39
40 HRESULT RegisterTextService(REFCLSID rclsid);
41 HRESULT UnregisterTextService();
42 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut);
43
44 DEFINE_GUID(CLSID_FakeService, 0xEDE1A7AD,0x66DE,0x47E0,0xB6,0x20,0x3E,0x92,0xF8,0x24,0x6B,0xF3);
45 DEFINE_GUID(CLSID_TF_InputProcessorProfiles, 0x33c53a50,0xf456,0x4884,0xb0,0x49,0x85,0xfd,0x64,0x3e,0xcf,0xed);
46 DEFINE_GUID(CLSID_TF_CategoryMgr,         0xA4B544A1,0x438D,0x4B41,0x93,0x25,0x86,0x95,0x23,0xE2,0xD6,0xC7);
47 DEFINE_GUID(GUID_TFCAT_TIP_KEYBOARD,     0x34745c63,0xb2f0,0x4784,0x8b,0x67,0x5e,0x12,0xc8,0x70,0x1a,0x31);
48 DEFINE_GUID(GUID_TFCAT_TIP_SPEECH,       0xB5A73CD1,0x8355,0x426B,0xA1,0x61,0x25,0x98,0x08,0xF2,0x6B,0x14);
49 DEFINE_GUID(GUID_TFCAT_TIP_HANDWRITING,  0x246ecb87,0xc2f2,0x4abe,0x90,0x5b,0xc8,0xb3,0x8a,0xdd,0x2c,0x43);
50 DEFINE_GUID (GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER,  0x046B8C80,0x1647,0x40F7,0x9B,0x21,0xB9,0x3B,0x81,0xAA,0xBC,0x1B);
51 DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
52 DEFINE_GUID(CLSID_TF_ThreadMgr,           0x529a9e6b,0x6587,0x4f23,0xab,0x9e,0x9c,0x7d,0x68,0x3e,0x3c,0x50);
53
54
55 static HRESULT initialize(void)
56 {
57     HRESULT hr;
58     CoInitialize(NULL);
59     hr = CoCreateInstance (&CLSID_TF_InputProcessorProfiles, NULL,
60           CLSCTX_INPROC_SERVER, &IID_ITfInputProcessorProfiles, (void**)&g_ipp);
61     if (SUCCEEDED(hr))
62         hr = CoCreateInstance (&CLSID_TF_CategoryMgr, NULL,
63           CLSCTX_INPROC_SERVER, &IID_ITfCategoryMgr, (void**)&g_cm);
64     if (SUCCEEDED(hr))
65         hr = CoCreateInstance (&CLSID_TF_ThreadMgr, NULL,
66           CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (void**)&g_tm);
67     return hr;
68 }
69
70 static void cleanup(void)
71 {
72     if (g_ipp)
73         ITfInputProcessorProfiles_Release(g_ipp);
74     if (g_cm)
75         ITfCategoryMgr_Release(g_cm);
76     if (g_tm)
77         ITfThreadMgr_Release(g_tm);
78     CoUninitialize();
79 }
80
81 static void test_Register(void)
82 {
83     HRESULT hr;
84
85     static const WCHAR szDesc[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',0};
86     static const WCHAR szFile[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',' ','F','i','l','e',0};
87
88     hr = RegisterTextService(&CLSID_FakeService);
89     ok(SUCCEEDED(hr),"Unable to register COM for TextService\n");
90     hr = ITfInputProcessorProfiles_Register(g_ipp, &CLSID_FakeService);
91     ok(SUCCEEDED(hr),"Unable to register text service(%x)\n",hr);
92     hr = ITfInputProcessorProfiles_AddLanguageProfile(g_ipp, &CLSID_FakeService, gLangid, &CLSID_FakeService, szDesc, sizeof(szDesc)/sizeof(WCHAR), szFile, sizeof(szFile)/sizeof(WCHAR), 1);
93     ok(SUCCEEDED(hr),"Unable to add Language Profile (%x)\n",hr);
94 }
95
96 static void test_Unregister(void)
97 {
98     HRESULT hr;
99     hr = ITfInputProcessorProfiles_Unregister(g_ipp, &CLSID_FakeService);
100     ok(SUCCEEDED(hr),"Unable to unregister text service(%x)\n",hr);
101     UnregisterTextService();
102 }
103
104 static void test_EnumInputProcessorInfo(void)
105 {
106     IEnumGUID *ppEnum;
107     BOOL found = FALSE;
108
109     if (SUCCEEDED(ITfInputProcessorProfiles_EnumInputProcessorInfo(g_ipp, &ppEnum)))
110     {
111         ULONG fetched;
112         GUID g;
113         while (IEnumGUID_Next(ppEnum, 1, &g, &fetched) == S_OK)
114         {
115             if(IsEqualGUID(&g,&CLSID_FakeService))
116                 found = TRUE;
117         }
118     }
119     ok(found,"Did not find registered text service\n");
120 }
121
122 static void test_EnumLanguageProfiles(void)
123 {
124     BOOL found = FALSE;
125     IEnumTfLanguageProfiles *ppEnum;
126     if (SUCCEEDED(ITfInputProcessorProfiles_EnumLanguageProfiles(g_ipp,gLangid,&ppEnum)))
127     {
128         TF_LANGUAGEPROFILE profile;
129         while (IEnumTfLanguageProfiles_Next(ppEnum,1,&profile,NULL)==S_OK)
130         {
131             if (IsEqualGUID(&profile.clsid,&CLSID_FakeService))
132             {
133                 found = TRUE;
134                 ok(profile.langid == gLangid, "LangId Incorrect\n");
135                 ok(IsEqualGUID(&profile.catid,&GUID_TFCAT_TIP_KEYBOARD), "CatId Incorrect\n");
136                 ok(IsEqualGUID(&profile.guidProfile,&CLSID_FakeService), "guidProfile Incorrect\n");
137             }
138         }
139     }
140     ok(found,"Registered text service not found\n");
141 }
142
143 static void test_RegisterCategory(void)
144 {
145     HRESULT hr;
146     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
147     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
148     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
149     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
150 }
151
152 static void test_UnregisterCategory(void)
153 {
154     HRESULT hr;
155     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
156     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
157     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
158     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
159 }
160
161 static void test_FindClosestCategory(void)
162 {
163     GUID output;
164     HRESULT hr;
165     const GUID *list[3] = {&GUID_TFCAT_TIP_SPEECH, &GUID_TFCAT_TIP_KEYBOARD, &GUID_TFCAT_TIP_HANDWRITING};
166
167     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, NULL, 0);
168     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
169     ok(IsEqualGUID(&output,&GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER),"Wrong GUID\n");
170
171     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 1);
172     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
173     ok(IsEqualGUID(&output,&GUID_NULL),"Wrong GUID\n");
174
175     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 3);
176     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
177     ok(IsEqualGUID(&output,&GUID_TFCAT_TIP_KEYBOARD),"Wrong GUID\n");
178 }
179
180 static void test_Enable(void)
181 {
182     HRESULT hr;
183     BOOL enabled = FALSE;
184
185     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, TRUE);
186     ok(SUCCEEDED(hr),"Failed to enable text service\n");
187     hr = ITfInputProcessorProfiles_IsEnabledLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, &enabled);
188     ok(SUCCEEDED(hr),"Failed to get enabled state\n");
189     ok(enabled == TRUE,"enabled state incorrect\n");
190 }
191
192 static void test_Disable(void)
193 {
194     HRESULT hr;
195
196     trace("Disabling\n");
197     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, FALSE);
198     ok(SUCCEEDED(hr),"Failed to disable text service\n");
199 }
200
201 static void test_ThreadMgrAdviseSinks(void)
202 {
203     ITfSource *source = NULL;
204     HRESULT hr;
205     IUnknown *sink;
206
207     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
208     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
209     if (!source)
210         return;
211
212     ThreadMgrEventSink_Constructor(&sink);
213
214     tmSinkRefCount = 1;
215     tmSinkCookie = 0;
216     hr = ITfSource_AdviseSink(source,&IID_ITfThreadMgrEventSink, sink, &tmSinkCookie);
217     ok(SUCCEEDED(hr),"Failed to Advise Sink\n");
218     ok(tmSinkCookie!=0,"Failed to get sink cookie\n");
219
220     /* Advising the sink adds a ref, Relesing here lets the object be deleted
221        when unadvised */
222     tmSinkRefCount = 2;
223     IUnknown_Release(sink);
224     ITfSource_Release(source);
225 }
226
227 static void test_ThreadMgrUnadviseSinks(void)
228 {
229     ITfSource *source = NULL;
230     HRESULT hr;
231
232     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
233     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
234     if (!source)
235         return;
236
237     tmSinkRefCount = 1;
238     hr = ITfSource_UnadviseSink(source, tmSinkCookie);
239     ok(SUCCEEDED(hr),"Failed to unadvise Sink\n");
240     ITfSource_Release(source);
241 }
242
243 START_TEST(inputprocessor)
244 {
245     if (SUCCEEDED(initialize()))
246     {
247         gLangid = GetUserDefaultLCID();
248         test_Register();
249         test_RegisterCategory();
250         test_EnumInputProcessorInfo();
251         test_Enable();
252         test_ThreadMgrAdviseSinks();
253         test_EnumLanguageProfiles();
254         test_FindClosestCategory();
255         test_Disable();
256         test_ThreadMgrUnadviseSinks();
257         test_UnregisterCategory();
258         test_Unregister();
259     }
260     else
261         skip("Unable to create InputProcessor\n");
262     cleanup();
263 }
264
265 /**********************************************************************
266  * ITfThreadMgrEventSink
267  **********************************************************************/
268 typedef struct tagThreadMgrEventSink
269 {
270     const ITfThreadMgrEventSinkVtbl *ThreadMgrEventSinkVtbl;
271     LONG refCount;
272 } ThreadMgrEventSink;
273
274 static void ThreadMgrEventSink_Destructor(ThreadMgrEventSink *This)
275 {
276     HeapFree(GetProcessHeap(),0,This);
277 }
278
279 static HRESULT WINAPI ThreadMgrEventSink_QueryInterface(ITfThreadMgrEventSink *iface, REFIID iid, LPVOID *ppvOut)
280 {
281     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
282     *ppvOut = NULL;
283
284     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfThreadMgrEventSink))
285     {
286         *ppvOut = This;
287     }
288
289     if (*ppvOut)
290     {
291         IUnknown_AddRef(iface);
292         return S_OK;
293     }
294
295     return E_NOINTERFACE;
296 }
297
298 static ULONG WINAPI ThreadMgrEventSink_AddRef(ITfThreadMgrEventSink *iface)
299 {
300     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
301     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
302     return InterlockedIncrement(&This->refCount);
303 }
304
305 static ULONG WINAPI ThreadMgrEventSink_Release(ITfThreadMgrEventSink *iface)
306 {
307     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
308     ULONG ret;
309
310     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
311     ret = InterlockedDecrement(&This->refCount);
312     if (ret == 0)
313         ThreadMgrEventSink_Destructor(This);
314     return ret;
315 }
316
317 static HRESULT WINAPI ThreadMgrEventSink_OnInitDocumentMgr(ITfThreadMgrEventSink *iface,
318 ITfDocumentMgr *pdim)
319 {
320     trace("\n");
321     return S_OK;
322 }
323
324 static HRESULT WINAPI ThreadMgrEventSink_OnUninitDocumentMgr(ITfThreadMgrEventSink *iface,
325 ITfDocumentMgr *pdim)
326 {
327     trace("\n");
328     return S_OK;
329 }
330
331 static HRESULT WINAPI ThreadMgrEventSink_OnSetFocus(ITfThreadMgrEventSink *iface,
332 ITfDocumentMgr *pdimFocus, ITfDocumentMgr *pdimPrevFocus)
333 {
334     trace("\n");
335     return S_OK;
336 }
337
338 static HRESULT WINAPI ThreadMgrEventSink_OnPushContext(ITfThreadMgrEventSink *iface,
339 ITfContext *pic)
340 {
341     trace("\n");
342     return S_OK;
343 }
344
345 static HRESULT WINAPI ThreadMgrEventSink_OnPopContext(ITfThreadMgrEventSink *iface,
346 ITfContext *pic)
347 {
348     trace("\n");
349     return S_OK;
350 }
351
352 static const ITfThreadMgrEventSinkVtbl ThreadMgrEventSink_ThreadMgrEventSinkVtbl =
353 {
354     ThreadMgrEventSink_QueryInterface,
355     ThreadMgrEventSink_AddRef,
356     ThreadMgrEventSink_Release,
357
358     ThreadMgrEventSink_OnInitDocumentMgr,
359     ThreadMgrEventSink_OnUninitDocumentMgr,
360     ThreadMgrEventSink_OnSetFocus,
361     ThreadMgrEventSink_OnPushContext,
362     ThreadMgrEventSink_OnPopContext
363 };
364
365 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut)
366 {
367     ThreadMgrEventSink *This;
368
369     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(ThreadMgrEventSink));
370     if (This == NULL)
371         return E_OUTOFMEMORY;
372
373     This->ThreadMgrEventSinkVtbl = &ThreadMgrEventSink_ThreadMgrEventSinkVtbl;
374     This->refCount = 1;
375
376     *ppOut = (IUnknown *)This;
377     return S_OK;
378 }
379
380
381 /********************************************************************************************
382  * Stub text service for testing
383  ********************************************************************************************/
384
385 static LONG TS_refCount;
386 static IClassFactory *cf;
387 static DWORD regid;
388
389 typedef HRESULT (*LPFNCONSTRUCTOR)(IUnknown *pUnkOuter, IUnknown **ppvOut);
390
391 typedef struct tagClassFactory
392 {
393     const IClassFactoryVtbl *vtbl;
394     LONG   ref;
395     LPFNCONSTRUCTOR ctor;
396 } ClassFactory;
397
398 typedef struct tagTextService
399 {
400     const ITfTextInputProcessorVtbl *TextInputProcessorVtbl;
401     LONG refCount;
402 } TextService;
403
404 static void ClassFactory_Destructor(ClassFactory *This)
405 {
406     HeapFree(GetProcessHeap(),0,This);
407     TS_refCount--;
408 }
409
410 static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, LPVOID *ppvOut)
411 {
412     *ppvOut = NULL;
413     if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown))
414     {
415         IClassFactory_AddRef(iface);
416         *ppvOut = iface;
417         return S_OK;
418     }
419
420     return E_NOINTERFACE;
421 }
422
423 static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
424 {
425     ClassFactory *This = (ClassFactory *)iface;
426     return InterlockedIncrement(&This->ref);
427 }
428
429 static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
430 {
431     ClassFactory *This = (ClassFactory *)iface;
432     ULONG ret = InterlockedDecrement(&This->ref);
433
434     if (ret == 0)
435         ClassFactory_Destructor(This);
436     return ret;
437 }
438
439 static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *punkOuter, REFIID iid, LPVOID *ppvOut)
440 {
441     ClassFactory *This = (ClassFactory *)iface;
442     HRESULT ret;
443     IUnknown *obj;
444
445     ret = This->ctor(punkOuter, &obj);
446     if (FAILED(ret))
447         return ret;
448     ret = IUnknown_QueryInterface(obj, iid, ppvOut);
449     IUnknown_Release(obj);
450     return ret;
451 }
452
453 static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock)
454 {
455     if(fLock)
456         InterlockedIncrement(&TS_refCount);
457     else
458         InterlockedDecrement(&TS_refCount);
459
460     return S_OK;
461 }
462
463 static const IClassFactoryVtbl ClassFactoryVtbl = {
464     /* IUnknown */
465     ClassFactory_QueryInterface,
466     ClassFactory_AddRef,
467     ClassFactory_Release,
468
469     /* IClassFactory*/
470     ClassFactory_CreateInstance,
471     ClassFactory_LockServer
472 };
473
474 static HRESULT ClassFactory_Constructor(LPFNCONSTRUCTOR ctor, LPVOID *ppvOut)
475 {
476     ClassFactory *This = HeapAlloc(GetProcessHeap(),0,sizeof(ClassFactory));
477     This->vtbl = &ClassFactoryVtbl;
478     This->ref = 1;
479     This->ctor = ctor;
480     *ppvOut = (LPVOID)This;
481     TS_refCount++;
482     return S_OK;
483 }
484
485 static void TextService_Destructor(TextService *This)
486 {
487     HeapFree(GetProcessHeap(),0,This);
488 }
489
490 static HRESULT WINAPI TextService_QueryInterface(ITfTextInputProcessor *iface, REFIID iid, LPVOID *ppvOut)
491 {
492     TextService *This = (TextService *)iface;
493     *ppvOut = NULL;
494
495     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfTextInputProcessor))
496     {
497         *ppvOut = This;
498     }
499
500     if (*ppvOut)
501     {
502         IUnknown_AddRef(iface);
503         return S_OK;
504     }
505
506     return E_NOINTERFACE;
507 }
508
509 static ULONG WINAPI TextService_AddRef(ITfTextInputProcessor *iface)
510 {
511     TextService *This = (TextService *)iface;
512     return InterlockedIncrement(&This->refCount);
513 }
514
515 static ULONG WINAPI TextService_Release(ITfTextInputProcessor *iface)
516 {
517     TextService *This = (TextService *)iface;
518     ULONG ret;
519
520     ret = InterlockedDecrement(&This->refCount);
521     if (ret == 0)
522         TextService_Destructor(This);
523     return ret;
524 }
525
526 static HRESULT WINAPI TextService_Activate(ITfTextInputProcessor *iface,
527         ITfThreadMgr *ptim, TfClientId id)
528 {
529     trace("TextService_Activate\n");
530     return S_OK;
531 }
532
533 static HRESULT WINAPI TextService_Deactivate(ITfTextInputProcessor *iface)
534 {
535     trace("TextService_Deactivate\n");
536     return S_OK;
537 }
538
539 static const ITfTextInputProcessorVtbl TextService_TextInputProcessorVtbl=
540 {
541     TextService_QueryInterface,
542     TextService_AddRef,
543     TextService_Release,
544
545     TextService_Activate,
546     TextService_Deactivate
547 };
548
549 HRESULT TextService_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut)
550 {
551     TextService *This;
552     if (pUnkOuter)
553         return CLASS_E_NOAGGREGATION;
554
555     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(TextService));
556     if (This == NULL)
557         return E_OUTOFMEMORY;
558
559     This->TextInputProcessorVtbl= &TextService_TextInputProcessorVtbl;
560     This->refCount = 1;
561
562     *ppOut = (IUnknown *)This;
563     return S_OK;
564 }
565
566 HRESULT RegisterTextService(REFCLSID rclsid)
567 {
568     ClassFactory_Constructor( TextService_Constructor ,(LPVOID*)&cf);
569     return CoRegisterClassObject(rclsid, (IUnknown*) cf, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &regid);
570 }
571
572 HRESULT UnregisterTextService()
573 {
574     return CoRevokeClassObject(regid);
575 }