xref: /reactos/dll/win32/msctf/msctf.c (revision 8c2e9189)
1 /*
2  * MSCTF Server DLL
3  *
4  * Copyright 2008 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 "config.h"
22 
23 #include <stdarg.h>
24 #include <stdio.h>
25 
26 #define COBJMACROS
27 
28 #include "wine/debug.h"
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winreg.h"
32 #include "shlwapi.h"
33 #include "shlguid.h"
34 #include "comcat.h"
35 #include "olectl.h"
36 #include "rpcproxy.h"
37 #include "msctf.h"
38 #include "inputscope.h"
39 
40 #include "msctf_internal.h"
41 
42 WINE_DEFAULT_DEBUG_CHANNEL(msctf);
43 
44 static HINSTANCE MSCTF_hinstance;
45 
46 typedef struct
47 {
48     DWORD id;
49     DWORD magic;
50     LPVOID data;
51 } CookieInternal;
52 
53 typedef struct {
54     TF_LANGUAGEPROFILE      LanguageProfile;
55     ITfTextInputProcessor   *pITfTextInputProcessor;
56     ITfThreadMgrEx          *pITfThreadMgrEx;
57     ITfKeyEventSink         *pITfKeyEventSink;
58     TfClientId              tid;
59 } ActivatedTextService;
60 
61 typedef struct
62 {
63     struct list entry;
64     ActivatedTextService *ats;
65 } AtsEntry;
66 
67 static CookieInternal *cookies;
68 static UINT id_last;
69 static UINT array_size;
70 
71 static struct list AtsList = LIST_INIT(AtsList);
72 static UINT activated = 0;
73 
74 DWORD tlsIndex = 0;
75 TfClientId processId = 0;
76 ITfCompartmentMgr *globalCompartmentMgr = NULL;
77 
78 const WCHAR szwSystemTIPKey[] = {'S','O','F','T','W','A','R','E','\\','M','i','c','r','o','s','o','f','t','\\','C','T','F','\\','T','I','P',0};
79 const WCHAR szwSystemCTFKey[] = {'S','O','F','T','W','A','R','E','\\','M','i','c','r','o','s','o','f','t','\\','C','T','F',0};
80 
81 typedef HRESULT (*LPFNCONSTRUCTOR)(IUnknown *pUnkOuter, IUnknown **ppvOut);
82 
83 static const struct {
84     REFCLSID clsid;
85     LPFNCONSTRUCTOR ctor;
86 } ClassesTable[] = {
87     {&CLSID_TF_ThreadMgr, ThreadMgr_Constructor},
88     {&CLSID_TF_InputProcessorProfiles, InputProcessorProfiles_Constructor},
89     {&CLSID_TF_CategoryMgr, CategoryMgr_Constructor},
90     {&CLSID_TF_LangBarMgr, LangBarMgr_Constructor},
91     {&CLSID_TF_DisplayAttributeMgr, DisplayAttributeMgr_Constructor},
92     {NULL, NULL}
93 };
94 
95 typedef struct tagClassFactory
96 {
97     IClassFactory IClassFactory_iface;
98     LONG   ref;
99     LPFNCONSTRUCTOR ctor;
100 } ClassFactory;
101 
102 static inline ClassFactory *impl_from_IClassFactory(IClassFactory *iface)
103 {
104     return CONTAINING_RECORD(iface, ClassFactory, IClassFactory_iface);
105 }
106 
107 static void ClassFactory_Destructor(ClassFactory *This)
108 {
109     TRACE("Destroying class factory %p\n", This);
110     HeapFree(GetProcessHeap(),0,This);
111 }
112 
113 static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, LPVOID *ppvOut)
114 {
115     *ppvOut = NULL;
116     if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown)) {
117         IClassFactory_AddRef(iface);
118         *ppvOut = iface;
119         return S_OK;
120     }
121 
122     WARN("Unknown interface %s\n", debugstr_guid(riid));
123     return E_NOINTERFACE;
124 }
125 
126 static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
127 {
128     ClassFactory *This = impl_from_IClassFactory(iface);
129     return InterlockedIncrement(&This->ref);
130 }
131 
132 static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
133 {
134     ClassFactory *This = impl_from_IClassFactory(iface);
135     ULONG ret = InterlockedDecrement(&This->ref);
136 
137     if (ret == 0)
138         ClassFactory_Destructor(This);
139     return ret;
140 }
141 
142 static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *punkOuter, REFIID iid, LPVOID *ppvOut)
143 {
144     ClassFactory *This = impl_from_IClassFactory(iface);
145     HRESULT ret;
146     IUnknown *obj;
147 
148     TRACE("(%p, %p, %s, %p)\n", iface, punkOuter, debugstr_guid(iid), ppvOut);
149     ret = This->ctor(punkOuter, &obj);
150     if (FAILED(ret))
151         return ret;
152     ret = IUnknown_QueryInterface(obj, iid, ppvOut);
153     IUnknown_Release(obj);
154     return ret;
155 }
156 
157 static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock)
158 {
159     ClassFactory *This = impl_from_IClassFactory(iface);
160 
161     TRACE("(%p)->(%x)\n", This, fLock);
162 
163     return S_OK;
164 }
165 
166 static const IClassFactoryVtbl ClassFactoryVtbl = {
167     /* IUnknown */
168     ClassFactory_QueryInterface,
169     ClassFactory_AddRef,
170     ClassFactory_Release,
171 
172     /* IClassFactory*/
173     ClassFactory_CreateInstance,
174     ClassFactory_LockServer
175 };
176 
177 static HRESULT ClassFactory_Constructor(LPFNCONSTRUCTOR ctor, LPVOID *ppvOut)
178 {
179     ClassFactory *This = HeapAlloc(GetProcessHeap(),0,sizeof(ClassFactory));
180     This->IClassFactory_iface.lpVtbl = &ClassFactoryVtbl;
181     This->ref = 1;
182     This->ctor = ctor;
183     *ppvOut = &This->IClassFactory_iface;
184     TRACE("Created class factory %p\n", This);
185     return S_OK;
186 }
187 
188 /*************************************************************************
189  * DWORD Cookie Management
190  */
191 DWORD generate_Cookie(DWORD magic, LPVOID data)
192 {
193     UINT i;
194 
195     /* try to reuse IDs if possible */
196     for (i = 0; i < id_last; i++)
197         if (cookies[i].id == 0) break;
198 
199     if (i == array_size)
200     {
201         if (!array_size)
202         {
203             cookies = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(CookieInternal) * 10);
204             if (!cookies)
205             {
206                 ERR("Out of memory, Unable to alloc cookies array\n");
207                 return 0;
208             }
209             array_size = 10;
210         }
211         else
212         {
213             CookieInternal *new_cookies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cookies,
214                                                       sizeof(CookieInternal) * (array_size * 2));
215             if (!new_cookies)
216             {
217                 ERR("Out of memory, Unable to realloc cookies array\n");
218                 return 0;
219             }
220             cookies = new_cookies;
221             array_size *= 2;
222         }
223     }
224 
225     cookies[i].id = i + 1; /* a return of 0 is used for failure */
226     cookies[i].magic = magic;
227     cookies[i].data = data;
228 
229     if (i == id_last)
230         id_last++;
231 
232     return cookies[i].id;
233 }
234 
235 DWORD get_Cookie_magic(DWORD id)
236 {
237     UINT index = id - 1;
238 
239     if (index >= id_last)
240         return 0;
241 
242     if (cookies[index].id == 0)
243         return 0;
244 
245     return cookies[index].magic;
246 }
247 
248 LPVOID get_Cookie_data(DWORD id)
249 {
250     UINT index = id - 1;
251 
252     if (index >= id_last)
253         return NULL;
254 
255     if (cookies[index].id == 0)
256         return NULL;
257 
258     return cookies[index].data;
259 }
260 
261 LPVOID remove_Cookie(DWORD id)
262 {
263     UINT index = id - 1;
264 
265     if (index >= id_last)
266         return NULL;
267 
268     if (cookies[index].id == 0)
269         return NULL;
270 
271     cookies[index].id = 0;
272     return cookies[index].data;
273 }
274 
275 DWORD enumerate_Cookie(DWORD magic, DWORD *index)
276 {
277     unsigned int i;
278     for (i = *index; i < id_last; i++)
279         if (cookies[i].id != 0 && cookies[i].magic == magic)
280         {
281             *index = (i+1);
282             return cookies[i].id;
283         }
284     return 0x0;
285 }
286 
287 HRESULT advise_sink(struct list *sink_list, REFIID riid, DWORD cookie_magic, IUnknown *unk, DWORD *cookie)
288 {
289     Sink *sink;
290 
291     sink = HeapAlloc(GetProcessHeap(), 0, sizeof(*sink));
292     if (!sink)
293         return E_OUTOFMEMORY;
294 
295     if (FAILED(IUnknown_QueryInterface(unk, riid, (void**)&sink->interfaces.pIUnknown)))
296     {
297         HeapFree(GetProcessHeap(), 0, sink);
298         return CONNECT_E_CANNOTCONNECT;
299     }
300 
301     list_add_head(sink_list, &sink->entry);
302     *cookie = generate_Cookie(cookie_magic, sink);
303     TRACE("cookie %x\n", *cookie);
304     return S_OK;
305 }
306 
307 static void free_sink(Sink *sink)
308 {
309     list_remove(&sink->entry);
310     IUnknown_Release(sink->interfaces.pIUnknown);
311     HeapFree(GetProcessHeap(), 0, sink);
312 }
313 
314 HRESULT unadvise_sink(DWORD cookie)
315 {
316     Sink *sink;
317 
318     sink = remove_Cookie(cookie);
319     if (!sink)
320         return CONNECT_E_NOCONNECTION;
321 
322     free_sink(sink);
323     return S_OK;
324 }
325 
326 void free_sinks(struct list *sink_list)
327 {
328     while(!list_empty(sink_list))
329     {
330         Sink* sink = LIST_ENTRY(sink_list->next, Sink, entry);
331         free_sink(sink);
332     }
333 }
334 
335 /*****************************************************************************
336  * Active Text Service Management
337  *****************************************************************************/
338 static HRESULT activate_given_ts(ActivatedTextService *actsvr, ITfThreadMgrEx *tm)
339 {
340     HRESULT hr;
341 
342     /* Already Active? */
343     if (actsvr->pITfTextInputProcessor)
344         return S_OK;
345 
346     hr = CoCreateInstance (&actsvr->LanguageProfile.clsid, NULL, CLSCTX_INPROC_SERVER,
347         &IID_ITfTextInputProcessor, (void**)&actsvr->pITfTextInputProcessor);
348     if (FAILED(hr)) return hr;
349 
350     hr = ITfTextInputProcessor_Activate(actsvr->pITfTextInputProcessor, (ITfThreadMgr *)tm, actsvr->tid);
351     if (FAILED(hr))
352     {
353         ITfTextInputProcessor_Release(actsvr->pITfTextInputProcessor);
354         actsvr->pITfTextInputProcessor = NULL;
355         return hr;
356     }
357 
358     actsvr->pITfThreadMgrEx = tm;
359     ITfThreadMgrEx_AddRef(tm);
360     return hr;
361 }
362 
363 static HRESULT deactivate_given_ts(ActivatedTextService *actsvr)
364 {
365     HRESULT hr = S_OK;
366 
367     if (actsvr->pITfTextInputProcessor)
368     {
369         hr = ITfTextInputProcessor_Deactivate(actsvr->pITfTextInputProcessor);
370         ITfTextInputProcessor_Release(actsvr->pITfTextInputProcessor);
371         ITfThreadMgrEx_Release(actsvr->pITfThreadMgrEx);
372         actsvr->pITfTextInputProcessor = NULL;
373         actsvr->pITfThreadMgrEx = NULL;
374     }
375 
376     return hr;
377 }
378 
379 static void deactivate_remove_conflicting_ts(REFCLSID catid)
380 {
381     AtsEntry *ats, *cursor2;
382 
383     LIST_FOR_EACH_ENTRY_SAFE(ats, cursor2, &AtsList, AtsEntry, entry)
384     {
385         if (IsEqualCLSID(catid,&ats->ats->LanguageProfile.catid))
386         {
387             deactivate_given_ts(ats->ats);
388             list_remove(&ats->entry);
389             HeapFree(GetProcessHeap(),0,ats->ats);
390             HeapFree(GetProcessHeap(),0,ats);
391             /* we are guaranteeing there is only 1 */
392             break;
393         }
394     }
395 }
396 
397 HRESULT add_active_textservice(TF_LANGUAGEPROFILE *lp)
398 {
399     ActivatedTextService *actsvr;
400     ITfCategoryMgr *catmgr;
401     AtsEntry *entry;
402     ITfThreadMgrEx *tm = TlsGetValue(tlsIndex);
403     ITfClientId *clientid;
404 
405     if (!tm) return E_UNEXPECTED;
406 
407     actsvr = HeapAlloc(GetProcessHeap(),0,sizeof(ActivatedTextService));
408     if (!actsvr) return E_OUTOFMEMORY;
409 
410     ITfThreadMgrEx_QueryInterface(tm, &IID_ITfClientId, (void **)&clientid);
411     ITfClientId_GetClientId(clientid, &lp->clsid, &actsvr->tid);
412     ITfClientId_Release(clientid);
413 
414     if (!actsvr->tid)
415     {
416         HeapFree(GetProcessHeap(),0,actsvr);
417         return E_OUTOFMEMORY;
418     }
419 
420     actsvr->pITfTextInputProcessor = NULL;
421     actsvr->LanguageProfile = *lp;
422     actsvr->pITfKeyEventSink = NULL;
423 
424     /* get TIP category */
425     if (SUCCEEDED(CategoryMgr_Constructor(NULL,(IUnknown**)&catmgr)))
426     {
427         static const GUID *list[3] = {&GUID_TFCAT_TIP_SPEECH, &GUID_TFCAT_TIP_KEYBOARD, &GUID_TFCAT_TIP_HANDWRITING};
428 
429         ITfCategoryMgr_FindClosestCategory(catmgr,
430                 &actsvr->LanguageProfile.clsid, &actsvr->LanguageProfile.catid,
431                 list, 3);
432 
433         ITfCategoryMgr_Release(catmgr);
434     }
435     else
436     {
437         ERR("CategoryMgr construction failed\n");
438         actsvr->LanguageProfile.catid = GUID_NULL;
439     }
440 
441     if (!IsEqualGUID(&actsvr->LanguageProfile.catid,&GUID_NULL))
442         deactivate_remove_conflicting_ts(&actsvr->LanguageProfile.catid);
443 
444     if (activated > 0)
445         activate_given_ts(actsvr, tm);
446 
447     entry = HeapAlloc(GetProcessHeap(),0,sizeof(AtsEntry));
448 
449     if (!entry)
450     {
451         HeapFree(GetProcessHeap(),0,actsvr);
452         return E_OUTOFMEMORY;
453     }
454 
455     entry->ats = actsvr;
456     list_add_head(&AtsList, &entry->entry);
457 
458     return S_OK;
459 }
460 
461 BOOL get_active_textservice(REFCLSID rclsid, TF_LANGUAGEPROFILE *profile)
462 {
463     AtsEntry *ats;
464 
465     LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
466     {
467         if (IsEqualCLSID(rclsid,&ats->ats->LanguageProfile.clsid))
468         {
469             if (profile)
470                 *profile = ats->ats->LanguageProfile;
471             return TRUE;
472         }
473     }
474     return FALSE;
475 }
476 
477 HRESULT activate_textservices(ITfThreadMgrEx *tm)
478 {
479     HRESULT hr = S_OK;
480     AtsEntry *ats;
481 
482     activated ++;
483     if (activated > 1)
484         return S_OK;
485 
486     LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
487     {
488         hr = activate_given_ts(ats->ats, tm);
489         if (FAILED(hr))
490             FIXME("Failed to activate text service\n");
491     }
492     return hr;
493 }
494 
495 HRESULT deactivate_textservices(void)
496 {
497     AtsEntry *ats;
498 
499     if (activated > 0)
500         activated --;
501 
502     if (activated == 0)
503         LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
504             deactivate_given_ts(ats->ats);
505 
506     return S_OK;
507 }
508 
509 CLSID get_textservice_clsid(TfClientId tid)
510 {
511     AtsEntry *ats;
512 
513     LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
514         if (ats->ats->tid == tid)
515             return ats->ats->LanguageProfile.clsid;
516     return GUID_NULL;
517 }
518 
519 HRESULT get_textservice_sink(TfClientId tid, REFCLSID iid, IUnknown **sink)
520 {
521     AtsEntry *ats;
522 
523     if (!IsEqualCLSID(iid,&IID_ITfKeyEventSink))
524         return E_NOINTERFACE;
525 
526     LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
527         if (ats->ats->tid == tid)
528         {
529             *sink = (IUnknown*)ats->ats->pITfKeyEventSink;
530             return S_OK;
531         }
532 
533     return E_FAIL;
534 }
535 
536 HRESULT set_textservice_sink(TfClientId tid, REFCLSID iid, IUnknown* sink)
537 {
538     AtsEntry *ats;
539 
540     if (!IsEqualCLSID(iid,&IID_ITfKeyEventSink))
541         return E_NOINTERFACE;
542 
543     LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
544         if (ats->ats->tid == tid)
545         {
546             ats->ats->pITfKeyEventSink = (ITfKeyEventSink*)sink;
547             return S_OK;
548         }
549 
550     return E_FAIL;
551 }
552 
553 /*************************************************************************
554  * MSCTF DllMain
555  */
556 BOOL WINAPI DllMain(HINSTANCE hinst, DWORD fdwReason, LPVOID fImpLoad)
557 {
558     TRACE("%p 0x%x %p\n", hinst, fdwReason, fImpLoad);
559     switch (fdwReason)
560     {
561         case DLL_WINE_PREATTACH:
562             return FALSE;   /* prefer native version */
563         case DLL_PROCESS_ATTACH:
564             MSCTF_hinstance = hinst;
565             tlsIndex = TlsAlloc();
566             break;
567         case DLL_PROCESS_DETACH:
568             if (fImpLoad) break;
569             TlsFree(tlsIndex);
570             break;
571     }
572     return TRUE;
573 }
574 
575 /*************************************************************************
576  *              DllCanUnloadNow (MSCTF.@)
577  */
578 HRESULT WINAPI DllCanUnloadNow(void)
579 {
580     return S_FALSE;
581 }
582 
583 /***********************************************************************
584  *              DllGetClassObject (MSCTF.@)
585  */
586 HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, LPVOID *ppvOut)
587 {
588     int i;
589 
590     *ppvOut = NULL;
591     if (!IsEqualIID(iid, &IID_IUnknown) && !IsEqualIID(iid, &IID_IClassFactory))
592         return E_NOINTERFACE;
593 
594     for (i = 0; ClassesTable[i].clsid != NULL; i++)
595         if (IsEqualCLSID(ClassesTable[i].clsid, clsid)) {
596             return ClassFactory_Constructor(ClassesTable[i].ctor, ppvOut);
597         }
598     FIXME("CLSID %s not supported\n", debugstr_guid(clsid));
599     return CLASS_E_CLASSNOTAVAILABLE;
600 }
601 
602 /***********************************************************************
603  *		DllRegisterServer (MSCTF.@)
604  */
605 HRESULT WINAPI DllRegisterServer(void)
606 {
607     return __wine_register_resources( MSCTF_hinstance );
608 }
609 
610 /***********************************************************************
611  *		DllUnregisterServer (MSCTF.@)
612  */
613 HRESULT WINAPI DllUnregisterServer(void)
614 {
615     return __wine_unregister_resources( MSCTF_hinstance );
616 }
617 
618 /***********************************************************************
619  *              TF_CreateThreadMgr (MSCTF.@)
620  */
621 HRESULT WINAPI TF_CreateThreadMgr(ITfThreadMgr **pptim)
622 {
623     TRACE("\n");
624     return ThreadMgr_Constructor(NULL,(IUnknown**)pptim);
625 }
626 
627 /***********************************************************************
628  *              TF_GetThreadMgr (MSCTF.@)
629  */
630 HRESULT WINAPI TF_GetThreadMgr(ITfThreadMgr **pptim)
631 {
632     TRACE("\n");
633     *pptim = TlsGetValue(tlsIndex);
634 
635     if (*pptim)
636         ITfThreadMgr_AddRef(*pptim);
637 
638     return S_OK;
639 }
640 
641 /***********************************************************************
642  *              SetInputScope(MSCTF.@)
643  */
644 HRESULT WINAPI SetInputScope(HWND hwnd, InputScope inputscope)
645 {
646     FIXME("STUB: %p %i\n",hwnd,inputscope);
647     return S_OK;
648 }
649 
650 /***********************************************************************
651  *              SetInputScopes(MSCTF.@)
652  */
653 HRESULT WINAPI SetInputScopes(HWND hwnd, const InputScope *pInputScopes,
654                               UINT cInputScopes, WCHAR **ppszPhraseList,
655                               UINT cPhrases, WCHAR *pszRegExp, WCHAR *pszSRGS)
656 {
657     UINT i;
658     FIXME("STUB: %p ... %s %s\n",hwnd, debugstr_w(pszRegExp), debugstr_w(pszSRGS));
659     for (i = 0; i < cInputScopes; i++)
660         TRACE("\tScope[%u] = %i\n",i,pInputScopes[i]);
661     for (i = 0; i < cPhrases; i++)
662         TRACE("\tPhrase[%u] = %s\n",i,debugstr_w(ppszPhraseList[i]));
663 
664     return S_OK;
665 }
666 
667 /***********************************************************************
668  *              TF_CreateInputProcessorProfiles(MSCTF.@)
669  */
670 HRESULT WINAPI TF_CreateInputProcessorProfiles(
671                         ITfInputProcessorProfiles **ppipr)
672 {
673     return InputProcessorProfiles_Constructor(NULL,(IUnknown**)ppipr);
674 }
675 
676 /***********************************************************************
677  *              TF_InvalidAssemblyListCacheIfExist(MSCTF.@)
678  */
679 HRESULT WINAPI TF_InvalidAssemblyListCacheIfExist(void)
680 {
681     FIXME("Stub\n");
682     return S_OK;
683 }
684 
685 /***********************************************************************
686  *              TF_CreateLangBarMgr (MSCTF.@)
687  */
688 HRESULT WINAPI TF_CreateLangBarMgr(ITfLangBarMgr **pppbm)
689 {
690     TRACE("\n");
691     return LangBarMgr_Constructor(NULL,(IUnknown**)pppbm);
692 }
693 
694 HRESULT WINAPI TF_CreateLangBarItemMgr(ITfLangBarItemMgr **pplbim)
695 {
696     FIXME("stub %p\n", pplbim);
697     *pplbim = NULL;
698 
699     return E_NOTIMPL;
700 }
701 
702 /***********************************************************************
703  *              TF_InitMlngInfo (MSCTF.@)
704  */
705 HRESULT WINAPI TF_InitMlngInfo(void)
706 {
707     FIXME("stub\n");
708     return S_OK;
709 }
710