mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1371 lines
35 KiB
1371 lines
35 KiB
//---------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation 1991-1994
|
|
//
|
|
// File: cplobj.c
|
|
//
|
|
// This file contains the persistent-object-binding mechanism which is
|
|
// slightly different from OLE's binding.
|
|
//
|
|
// History:
|
|
// 06-04-93 GeorgeP Copied from printer.c
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include "shellprv.h"
|
|
#pragma hdrstop
|
|
|
|
typedef struct tagCPLAPPLETID
|
|
{
|
|
ATOM aCPL; // CPL name atom (so we can match requests)
|
|
ATOM aApplet; // applet name atom (so we can match requests, may be zero)
|
|
HWND hwndStub; // window for this dude (so we can switch to it)
|
|
UINT flags; // see PCPLIF_ flags below
|
|
} CPLAPPLETID;
|
|
|
|
//
|
|
// PCPLIF_DEFAULT_APPLET
|
|
// There are two ways of getting the default applet, asking for it my name
|
|
// and passing an empty applet name. This flag should be set regardless,
|
|
// so that the code which switches to an already-active applet can always
|
|
// find a previous instance if it exists.
|
|
//
|
|
|
|
#define PCPLIF_DEFAULT_APPLET (0x1)
|
|
|
|
#define CCHSZSHORT 32
|
|
|
|
#define APPLET_NAME_SIZE (ARRAYSIZE(((LPNEWCPLINFO)0)->szName)) // NB: size in chars, not bytes
|
|
|
|
typedef struct tagCPLEXECINFO
|
|
{
|
|
int icon;
|
|
TCHAR cpl[ CCHPATHMAX ];
|
|
TCHAR applet[ APPLET_NAME_SIZE ];
|
|
TCHAR *params;
|
|
} CPLEXECINFO;
|
|
|
|
ATOM aCPLName = (ATOM)0;
|
|
ATOM aCPLFlags = (ATOM)0;
|
|
|
|
void CPL_ParseCommandLine( CPLEXECINFO *info, LPTSTR cmdline, BOOL extract_icon );
|
|
BOOL CPL_LoadAndFindApplet( LPCPLMODULE *, UINT *, CPLEXECINFO * );
|
|
|
|
BOOL
|
|
CPL_FindCPLInfo( LPTSTR cmdline, LPCPLMODULE *ppmod, UINT *ppapl, LPTSTR *pparm )
|
|
{
|
|
CPLEXECINFO info;
|
|
|
|
CPL_ParseCommandLine( &info, cmdline, TRUE );
|
|
|
|
if( CPL_LoadAndFindApplet( ppmod, ppapl, &info ) )
|
|
{
|
|
*pparm = info.params;
|
|
return TRUE;
|
|
}
|
|
|
|
*pparm = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
typedef struct _fcc {
|
|
LPTSTR lpszClassStub;
|
|
CPLAPPLETID *target;
|
|
HWND hwndMatch;
|
|
} FCC, *LPFCC;
|
|
|
|
|
|
BOOL _FindCPLCallback( HWND hwnd, LPARAM lParam)
|
|
{
|
|
LPFCC lpfcc = (LPFCC)lParam;
|
|
TCHAR szClass[CCHSZSHORT];
|
|
|
|
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
|
|
|
|
if (lstrcmp(szClass, lpfcc->lpszClassStub) == 0) // Must be same class...
|
|
{
|
|
// Found a stub window
|
|
if (lpfcc->target->aCPL != 0)
|
|
{
|
|
HANDLE hHandle;
|
|
|
|
ATOM aCPL;
|
|
hHandle = GetProp(hwnd, (LPCTSTR)(DWORD)aCPLName);
|
|
|
|
ASSERT((DWORD) hHandle < MAXUSHORT);
|
|
aCPL = (ATOM)(DWORD) hHandle;
|
|
|
|
if (aCPL != 0 && aCPL == lpfcc->target->aCPL)
|
|
{
|
|
ATOM aApplet;
|
|
hHandle = GetProp(hwnd, (LPCTSTR)(DWORD)aCPL);
|
|
aApplet = (ATOM)(DWORD) hHandle;
|
|
ASSERT((DWORD) hHandle < MAXUSHORT);
|
|
|
|
// users may request any applet by name
|
|
if (aApplet != 0 && aApplet == lpfcc->target->aApplet)
|
|
{
|
|
lpfcc->hwndMatch = hwnd;
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Users may request the default w/o specifying a name
|
|
//
|
|
if (lpfcc->target->flags & PCPLIF_DEFAULT_APPLET)
|
|
{
|
|
UINT flags = (UINT)GetProp(hwnd, MAKEINTATOM(aCPLFlags));
|
|
|
|
if (flags & PCPLIF_DEFAULT_APPLET)
|
|
{
|
|
lpfcc->hwndMatch = hwnd;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HWND
|
|
FindCPL( HWND hwndStub, CPLAPPLETID *target )
|
|
{
|
|
HWND hwnd;
|
|
FCC fcc;
|
|
TCHAR szClassStub[CCHSZSHORT];
|
|
|
|
if (aCPLName == (ATOM)0)
|
|
{
|
|
aCPLName = GlobalAddAtom(TEXT("CPLName"));
|
|
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
|
|
|
|
if (aCPLName == (ATOM)0 || aCPLFlags == (ATOM)0)
|
|
return NULL; // This should never happen... didn't find hwnd
|
|
}
|
|
|
|
GetClassName(hwndStub, szClassStub, ARRAYSIZE(szClassStub));
|
|
fcc.lpszClassStub = szClassStub;
|
|
fcc.target = target;
|
|
fcc.hwndMatch = (HWND)0;
|
|
|
|
EnumWindows(_FindCPLCallback, (LPARAM)&fcc);
|
|
|
|
return fcc.hwndMatch;
|
|
}
|
|
|
|
|
|
//
|
|
// IShellUI stuff
|
|
//
|
|
|
|
typedef struct _ControlObjsShellUI
|
|
{
|
|
WCommonUnknown cunk;
|
|
LPCTSTR pszSubObject;
|
|
LPCPLMODULE pcplm;
|
|
int nControl;
|
|
|
|
} CControlObjsShellUI, *PControlObjsShellUI;
|
|
|
|
|
|
//
|
|
// Member: IShellUI::GetIconLocation
|
|
//
|
|
STDMETHODIMP CControlObjs_EI_GetIconLocation(IExtractIcon * psui,
|
|
UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags)
|
|
{
|
|
PControlObjsShellUI this = IToClassN(CControlObjsShellUI, cunk.ck.unk, psui);
|
|
LPTSTR pszComma;
|
|
|
|
if (uFlags & GIL_OPENICON)
|
|
{
|
|
return(ResultFromScode(S_FALSE));
|
|
}
|
|
|
|
lstrcpyn(szIconFile, this->pszSubObject, cchMax);
|
|
pszComma = StrChr(szIconFile, TEXT(','));
|
|
if (pszComma)
|
|
{
|
|
*pszComma++=TEXT('\0');
|
|
*piIndex = StrToInt(pszComma);
|
|
*pwFlags = GIL_PERINSTANCE;
|
|
|
|
//
|
|
// normally the index will be negative (a resource id)
|
|
// check for some special cases like dynamic icons and bogus ids
|
|
//
|
|
if (*piIndex == 0)
|
|
{
|
|
LPTSTR lpExtraParms = NULL;
|
|
|
|
// this is a dynamic applet icon
|
|
*pwFlags |= GIL_DONTCACHE | GIL_NOTFILENAME;
|
|
|
|
// use the applet index in case there's more than one
|
|
if (this->pcplm || CPL_FindCPLInfo((LPTSTR)this->pszSubObject,
|
|
&this->pcplm, &(UINT)this->nControl, &lpExtraParms))
|
|
{
|
|
*piIndex = this->nControl;
|
|
}
|
|
else
|
|
{
|
|
// we failed to load the applet all of the sudden
|
|
// use the first icon in the cpl file (*piIndex == 0)
|
|
//
|
|
// Assert(FALSE);
|
|
DebugMsg(DM_ERROR, TEXT("Control Panel CCEIGIL: ") TEXT("Enumeration failed \"%s\""), (LPTSTR)this->pszSubObject);
|
|
}
|
|
}
|
|
else if (*piIndex > 0)
|
|
{
|
|
// this is an invalid icon for a control panel
|
|
// use the first icon in the file
|
|
// this may be wrong but it's better than a generic doc icon
|
|
// this fixes ODBC32 which is NOT dynamic but returns bogus ids
|
|
*piIndex = 0;
|
|
}
|
|
|
|
return(NOERROR);
|
|
}
|
|
|
|
return(ResultFromScode(S_FALSE));
|
|
}
|
|
|
|
STDMETHODIMP CControlObjs_EI_ExtractIcon(LPEXTRACTICON pxicon,
|
|
LPCTSTR pszFile,
|
|
UINT nIconIndex,
|
|
HICON *phiconLarge,
|
|
HICON *phiconSmall,
|
|
UINT nIconSize)
|
|
{
|
|
LPCPLITEM pcpli;
|
|
LPTSTR lpExtraParms = NULL;
|
|
PControlObjsShellUI this = IToClassN(CControlObjsShellUI, cunk.ck.unk, pxicon);
|
|
HRESULT result = S_FALSE;
|
|
LPCTSTR p;
|
|
|
|
//-------------------------------------------------------------------
|
|
// if there is no icon index then we must extract by loading the dude
|
|
// if we have an icon index then it can be extracted with ExtractIcon
|
|
// (which is much faster)
|
|
// only perform a custom extract if we have a dynamic icon
|
|
// otherwise just return S_FALSE and let our caller call ExtractIcon.
|
|
//-------------------------------------------------------------------
|
|
|
|
p = StrChr(this->pszSubObject, TEXT(','));
|
|
|
|
if ((!p || !StrToInt(p+1)) &&
|
|
(this->pcplm || CPL_FindCPLInfo((LPTSTR)this->pszSubObject, &this->pcplm, &(UINT)this->nControl, &lpExtraParms)))
|
|
{
|
|
pcpli = DSA_GetItemPtr(this->pcplm->hacpli, this->nControl);
|
|
|
|
if (pcpli->hIcon)
|
|
{
|
|
*phiconLarge = CopyIcon(pcpli->hIcon);
|
|
*phiconSmall = NULL;
|
|
|
|
if( *phiconLarge )
|
|
result = NOERROR;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE CControlObjs_EI_Release(IUnknown *punk)
|
|
{
|
|
PControlObjsShellUI this = IToClass(CControlObjsShellUI, cunk.unk, punk);
|
|
|
|
this->cunk.cRef--;
|
|
if (this->cunk.cRef > 0)
|
|
{
|
|
return(this->cunk.cRef);
|
|
}
|
|
|
|
if (this->pcplm)
|
|
{
|
|
CPL_FreeCPLModule(this->pcplm);
|
|
this->pcplm = NULL;
|
|
}
|
|
|
|
LocalFree((HLOCAL)this);
|
|
return(0);
|
|
}
|
|
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IUnknownVtbl s_ControlObjsAggEIVtbl =
|
|
{
|
|
WCommonUnknown_QueryInterface,
|
|
WCommonUnknown_AddRef,
|
|
CControlObjs_EI_Release
|
|
};
|
|
|
|
IExtractIconVtbl s_ControlObjsEIVtbl =
|
|
{
|
|
WCommonKnown_QueryInterface,
|
|
WCommonKnown_AddRef,
|
|
WCommonKnown_Release,
|
|
CControlObjs_EI_GetIconLocation,
|
|
CControlObjs_EI_ExtractIcon
|
|
} ;
|
|
#pragma data_seg()
|
|
|
|
HRESULT ControlObjs_CreateEI(IUnknown *punkOuter,
|
|
LPCOMMINFO lpcinfo, REFIID riid, IUnknown * *punkAgg)
|
|
{
|
|
PControlObjsShellUI this;
|
|
HRESULT hRes;
|
|
|
|
hRes = WU_CreateInterface(SIZEOF(CControlObjsShellUI), &IID_IExtractIcon,
|
|
&s_ControlObjsAggEIVtbl, &s_ControlObjsEIVtbl,
|
|
punkOuter, riid, punkAgg);
|
|
|
|
if (!SUCCEEDED(hRes))
|
|
{
|
|
return(hRes);
|
|
}
|
|
|
|
this = IToClassN(CControlObjsShellUI, cunk.unk, *punkAgg);
|
|
this->pszSubObject = lpcinfo->pszSubObject;
|
|
this->pcplm = NULL;
|
|
this->nControl = -1;
|
|
|
|
return(hRes);
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// Member: IShellUI::IExtractIconA::GetIconLocation
|
|
//
|
|
STDMETHODIMP CControlObjs_EIA_GetIconLocation(IExtractIconA * psui,
|
|
UINT uFlags, LPSTR pszIconFile, UINT cchMax, int * piIndex, UINT * pwFlags)
|
|
{
|
|
WCHAR szIconFile[MAX_PATH];
|
|
HRESULT hres;
|
|
|
|
//
|
|
// Just pretend we are the same IExtractIcon interface
|
|
// (use its implemenation)
|
|
//
|
|
hres = CControlObjs_EI_GetIconLocation((IExtractIcon *)psui, uFlags,
|
|
szIconFile, ARRAYSIZE(szIconFile),
|
|
piIndex, pwFlags);
|
|
|
|
if (SUCCEEDED(hres) && hres != S_FALSE)
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szIconFile, -1,
|
|
pszIconFile, cchMax,
|
|
NULL, NULL);
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
STDMETHODIMP CControlObjs_EIA_ExtractIcon(LPEXTRACTICONA pxiconA,
|
|
LPCSTR pszFile,
|
|
UINT nIconIndex,
|
|
HICON *phiconLarge,
|
|
HICON *phiconSmall,
|
|
UINT nIconSize)
|
|
{
|
|
//
|
|
// Just pretend we are the same IExtractIcon interface
|
|
// (use its implementation).
|
|
//
|
|
// Also apparently the ExtractIcon method doesn't use the file name.
|
|
//
|
|
return CControlObjs_EI_ExtractIcon((IExtractIcon *)pxiconA, NULL, nIconIndex,
|
|
phiconLarge, phiconSmall, nIconSize);
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE CControlObjs_EIA_Release(IUnknown *punk)
|
|
{
|
|
//
|
|
// Just pretend we are the same IExtractIcon interface
|
|
// (use its implementation).
|
|
//
|
|
return CControlObjs_EI_Release(punk);
|
|
}
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IUnknownVtbl s_ControlObjsAggEIAVtbl =
|
|
{
|
|
WCommonUnknown_QueryInterface,
|
|
WCommonUnknown_AddRef,
|
|
CControlObjs_EIA_Release
|
|
};
|
|
|
|
IExtractIconAVtbl s_ControlObjsEIAVtbl =
|
|
{
|
|
WCommonKnown_QueryInterface,
|
|
WCommonKnown_AddRef,
|
|
WCommonKnown_Release,
|
|
CControlObjs_EIA_GetIconLocation,
|
|
CControlObjs_EIA_ExtractIcon
|
|
} ;
|
|
#pragma data_seg()
|
|
|
|
HRESULT ControlObjs_CreateEIA(IUnknown *punkOuter,
|
|
LPCOMMINFO lpcinfo, REFIID riid, IUnknown * *punkAgg)
|
|
{
|
|
PControlObjsShellUI this;
|
|
HRESULT hRes;
|
|
|
|
hRes = WU_CreateInterface(SIZEOF(CControlObjsShellUI), &IID_IExtractIconA,
|
|
&s_ControlObjsAggEIAVtbl, &s_ControlObjsEIAVtbl,
|
|
punkOuter, riid, punkAgg);
|
|
|
|
if (!SUCCEEDED(hRes))
|
|
{
|
|
return(hRes);
|
|
}
|
|
|
|
this = IToClassN(CControlObjsShellUI, cunk.unk, *punkAgg);
|
|
this->pszSubObject = lpcinfo->pszSubObject;
|
|
this->pcplm = NULL;
|
|
this->nControl = -1;
|
|
|
|
return(hRes);
|
|
}
|
|
#endif
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// parsing helper for comma lists
|
|
//
|
|
|
|
TCHAR *
|
|
CPL_ParseToSeparator( TCHAR *dst, TCHAR *src, size_t dstmax, BOOL spacedelimits )
|
|
{
|
|
if( src )
|
|
{
|
|
TCHAR *delimiter, *closingquote = NULL;
|
|
|
|
//
|
|
// eat whitespace
|
|
//
|
|
|
|
while( *src == TEXT(' ') )
|
|
src++;
|
|
|
|
delimiter = src;
|
|
|
|
//
|
|
// ignore stuff inside quoted strings
|
|
//
|
|
|
|
if( *src == TEXT('"') )
|
|
{
|
|
//
|
|
// start after first quote, advance src past quote
|
|
//
|
|
|
|
closingquote = ++src;
|
|
|
|
while( *closingquote && *closingquote != TEXT('"') )
|
|
closingquote++;
|
|
|
|
//
|
|
// see if loop above ended on a quote
|
|
//
|
|
|
|
if( *closingquote )
|
|
{
|
|
//
|
|
// temporary NULL termination
|
|
//
|
|
|
|
*closingquote = 0;
|
|
|
|
//
|
|
// start looking for delimiter again after quotes
|
|
//
|
|
|
|
delimiter = closingquote + 1;
|
|
}
|
|
else
|
|
closingquote = NULL;
|
|
}
|
|
|
|
if( spacedelimits )
|
|
{
|
|
delimiter += StrCSpn( delimiter, TEXT(", ") );
|
|
|
|
if( !*delimiter )
|
|
delimiter = NULL;
|
|
}
|
|
else
|
|
delimiter = StrChr( delimiter, TEXT(',') );
|
|
|
|
//
|
|
// temporary NULL termination
|
|
//
|
|
|
|
if( delimiter )
|
|
*delimiter = 0;
|
|
|
|
if( dst )
|
|
{
|
|
lstrcpyn( dst, src, (int)dstmax );
|
|
dst[ dstmax - 1 ] = 0;
|
|
}
|
|
|
|
//
|
|
// put back stuff we terminated above
|
|
//
|
|
|
|
if( delimiter )
|
|
*delimiter = TEXT(',');
|
|
|
|
if( closingquote )
|
|
*closingquote = TEXT('"');
|
|
|
|
//
|
|
// return start of next string
|
|
//
|
|
|
|
src = ( delimiter? ( delimiter + 1 ) : NULL );
|
|
}
|
|
else if( dst )
|
|
{
|
|
*dst = 0;
|
|
}
|
|
|
|
//
|
|
// new source location
|
|
//
|
|
|
|
return src;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// parse the Control_RunDLL command line
|
|
// format: "CPL name, applet name, extra params"
|
|
// format: "CPL name, icon index, applet name, extra params"
|
|
//
|
|
// NOTE: [stevecat] 3/10/95
|
|
//
|
|
// The 'extra params' do not have to be delimited by a ","
|
|
// in NT for the case "CPL name applet name extra params"
|
|
//
|
|
// A workaround for applet names that include a space
|
|
// in their name would be to enclose that value in
|
|
// double quotes (see the CPL_ParseToSeparator routine.)
|
|
//
|
|
|
|
void
|
|
CPL_ParseCommandLine( CPLEXECINFO *info, LPTSTR cmdline, BOOL extract_icon )
|
|
{
|
|
//
|
|
// parse out the CPL name, spaces are valid separators
|
|
//
|
|
|
|
cmdline = CPL_ParseToSeparator( info->cpl, cmdline, CCHPATHMAX, TRUE );
|
|
|
|
if( extract_icon )
|
|
{
|
|
TCHAR icon[ 8 ];
|
|
|
|
//
|
|
// parse out the icon id/index, spaces are not valid separators
|
|
//
|
|
|
|
cmdline = CPL_ParseToSeparator( icon, cmdline, ARRAYSIZE( icon ), FALSE );
|
|
|
|
info->icon = StrToInt( icon );
|
|
}
|
|
else
|
|
info->icon = 0;
|
|
|
|
//
|
|
// parse out the applet name, spaces are not valid separators
|
|
//
|
|
|
|
info->params = CPL_ParseToSeparator( info->applet, cmdline,
|
|
APPLET_NAME_SIZE, FALSE );
|
|
|
|
CPL_StripAmpersand( info->applet );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
BOOL CPL_LoadAndFindApplet( LPCPLMODULE *ppcplm,
|
|
UINT *puControl,
|
|
CPLEXECINFO *info )
|
|
{
|
|
TCHAR szControl[ARRAYSIZE(((LPNEWCPLINFO)0)->szName)];
|
|
LPCPLMODULE pcplm;
|
|
LPCPLITEM pcpli;
|
|
int nControl = 0; // fall thru to default
|
|
int NumControls;
|
|
|
|
pcplm = CPL_LoadCPLModule(info->cpl);
|
|
|
|
if (!pcplm)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("Control_RunDLL: ") TEXT("CPL_LoadCPLModule failed \"%s\""), info->cpl);
|
|
goto Error0;
|
|
}
|
|
|
|
//
|
|
// Look for the specified applet
|
|
// no applet specified selects applet 0
|
|
//
|
|
|
|
if (*info->applet)
|
|
{
|
|
NumControls = DSA_GetItemCount(pcplm->hacpli);
|
|
|
|
if (info->applet[0] == TEXT('@'))
|
|
{
|
|
nControl = StrToLong(info->applet+1);
|
|
|
|
if (nControl >= 0 && nControl < NumControls)
|
|
{
|
|
goto GotControl;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check for the "Setup" argument and send the special CPL_SETUP
|
|
// message to the applet to tell it we are running under Setup.
|
|
//
|
|
|
|
if (!lstrcmpi (TEXT("Setup"), info->params))
|
|
CPL_CallEntry(pcplm, NULL, CPL_SETUP, 0L, 0L);
|
|
|
|
|
|
for (nControl=0; nControl < NumControls; nControl++)
|
|
{
|
|
pcpli = DSA_GetItemPtr(pcplm->hacpli, nControl);
|
|
lstrcpy(szControl, pcpli->pszName);
|
|
CPL_StripAmpersand(szControl);
|
|
if (lstrcmpi(info->applet, szControl) == 0)
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we get to the end of the list, bail out
|
|
//
|
|
|
|
if (nControl >= NumControls)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("Control_RunDLL: ") TEXT("Cannot find specified applet"));
|
|
goto Error1;
|
|
}
|
|
}
|
|
|
|
GotControl:
|
|
//
|
|
// yes, we really do want to pass negative indices through...
|
|
//
|
|
|
|
*puControl = (UINT)nControl;
|
|
*ppcplm = pcplm;
|
|
|
|
return TRUE;
|
|
|
|
Error1:
|
|
CPL_FreeCPLModule( pcplm );
|
|
Error0:
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void OpenControlPanelFolder(HWND hwnd, int nCmdShow, UINT nFolder)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
|
|
//
|
|
// Open a folder (nFolder)
|
|
//
|
|
|
|
pidl = SHCloneSpecialIDList(hwnd, nFolder, FALSE);
|
|
if (pidl)
|
|
{
|
|
CMINVOKECOMMANDINFOEX ici = {
|
|
SIZEOF(CMINVOKECOMMANDINFOEX),
|
|
0L,
|
|
hwnd,
|
|
NULL,
|
|
NULL, NULL,
|
|
nCmdShow,
|
|
};
|
|
|
|
InvokeFolderCommandUsingPidl(&ici,NULL,pidl,NULL, SEE_MASK_FLAG_DDEWAIT);
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
CPL_Identify( CPLAPPLETID *identity, CPLEXECINFO *info, HWND stub )
|
|
{
|
|
identity->aApplet = (ATOM)0;
|
|
identity->hwndStub = stub;
|
|
identity->flags = 0;
|
|
|
|
if( (identity->aCPL = GlobalAddAtom( info->cpl )) == (ATOM)0 )
|
|
return FALSE;
|
|
|
|
if( *info->applet )
|
|
{
|
|
if( (identity->aApplet = GlobalAddAtom( info->applet )) == (ATOM)0 )
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// no applet name means use the default
|
|
//
|
|
|
|
identity->flags = PCPLIF_DEFAULT_APPLET;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void
|
|
CPL_UnIdentify( CPLAPPLETID *identity )
|
|
{
|
|
if( identity->aCPL )
|
|
{
|
|
GlobalDeleteAtom( identity->aCPL );
|
|
identity->aCPL = (ATOM)0;
|
|
}
|
|
|
|
if( identity->aApplet )
|
|
{
|
|
GlobalDeleteAtom( identity->aApplet );
|
|
identity->aApplet = (ATOM)0;
|
|
}
|
|
|
|
identity->hwndStub = NULL;
|
|
identity->flags = 0;
|
|
}
|
|
|
|
|
|
// Goes through all of the work of identifying and starting a control
|
|
// applet. Accepts a flag specifying whether or not to load a new DLL if it
|
|
// is not already present. This code will ALLWAYS switch to an existing
|
|
// instance of the applet if one is known.
|
|
// WARNING: this function butchers the command line you pass in!
|
|
|
|
BOOL
|
|
CPL_RunMeBaby(HWND hwndStub, HINSTANCE hAppInstance, LPTSTR lpszCmdLine, int nCmdShow, BOOL bAllowLoad )
|
|
{
|
|
|
|
int i, nApplet;
|
|
LPCPLMODULE pcplm;
|
|
LPCPLITEM pcpli;
|
|
CPLEXECINFO info;
|
|
CPLAPPLETID identity;
|
|
TCHAR szApplet[ APPLET_NAME_SIZE ];
|
|
BOOL bResult = FALSE;
|
|
HWND hwndOtherStub;
|
|
|
|
//
|
|
// parse the command line we got
|
|
//
|
|
|
|
CPL_ParseCommandLine( &info, lpszCmdLine, FALSE );
|
|
|
|
//
|
|
// no applet to run means open the controls folder
|
|
//
|
|
|
|
if( !*info.cpl )
|
|
{
|
|
OpenControlPanelFolder( hwndStub, nCmdShow, CSIDL_CONTROLS );
|
|
bResult = TRUE;
|
|
goto Error0;
|
|
}
|
|
|
|
// expand CPL name to a full path if it isn't already
|
|
if( PathIsFileSpec( info.cpl ) )
|
|
{
|
|
if( !PathFindOnPath( info.cpl, NULL ) )
|
|
goto Error0;
|
|
}
|
|
else if( !PathFileExists( info.cpl ) )
|
|
goto Error0;
|
|
|
|
if( !CPL_Identify( &identity, &info, hwndStub ) )
|
|
goto Error0;
|
|
|
|
//
|
|
// If we have already loaded this CPL, then jump to the existing window
|
|
//
|
|
|
|
hwndOtherStub = FindCPL(hwndStub, &identity);
|
|
|
|
if (hwndOtherStub)
|
|
{
|
|
//
|
|
// try to find a CPL window on top of it
|
|
//
|
|
|
|
HWND hwndTarget = GetLastActivePopup( hwndOtherStub );
|
|
|
|
if( hwndTarget && IsWindow( hwndTarget ) )
|
|
{
|
|
|
|
DebugMsg(DM_WARNING, TEXT("Control_RunDLL: ") TEXT("Switching to already loaded CPL applet"));
|
|
SetForegroundWindow( hwndTarget );
|
|
bResult = TRUE;
|
|
goto Error1;
|
|
}
|
|
|
|
//
|
|
// couldn't find it, must be exiting or some sort of error...
|
|
// so ignore it.
|
|
//
|
|
|
|
DebugMsg(DM_WARNING, TEXT("Control_RunDLL: ") TEXT("Bogus CPL identity in array; purging after (presumed) RunDLL crash"));
|
|
}
|
|
|
|
//
|
|
// stop here if we're not allowed to load the cpl
|
|
//
|
|
|
|
if( !bAllowLoad )
|
|
goto Error1;
|
|
|
|
//
|
|
// i guess we didn't stop up there
|
|
//
|
|
|
|
if( !CPL_LoadAndFindApplet( &pcplm, &nApplet, &info ) )
|
|
goto Error1;
|
|
|
|
//
|
|
// get the name that the applet thinks it should have
|
|
//
|
|
|
|
pcpli = DSA_GetItemPtr(pcplm->hacpli, nApplet);
|
|
|
|
lstrcpy(szApplet, pcpli->pszName);
|
|
|
|
CPL_StripAmpersand(szApplet);
|
|
|
|
//
|
|
// handle "default applet" cases before running anything
|
|
//
|
|
|
|
if( identity.aApplet )
|
|
{
|
|
//
|
|
// we were started with an explicitly named applet
|
|
//
|
|
|
|
if( !nApplet )
|
|
{
|
|
//
|
|
// we were started with the name of the default applet
|
|
//
|
|
|
|
identity.flags |= PCPLIF_DEFAULT_APPLET;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// we were started without a name, assume the default applet
|
|
//
|
|
|
|
identity.flags |= PCPLIF_DEFAULT_APPLET;
|
|
|
|
//
|
|
// get the applet's name (now that we've loaded it's CPL)
|
|
//
|
|
|
|
if( (identity.aApplet = GlobalAddAtom( szApplet )) == (ATOM)0 )
|
|
{
|
|
//
|
|
// bail 'cause we could nuke a CPL if we don't have this
|
|
//
|
|
|
|
goto Error2;
|
|
}
|
|
}
|
|
|
|
//
|
|
// mark the window so we'll be able to verify that it's really ours
|
|
//
|
|
if (aCPLName == (ATOM)0)
|
|
{
|
|
aCPLName = GlobalAddAtom(TEXT("CPLName"));
|
|
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
|
|
|
|
if (aCPLName == (ATOM)0 || aCPLFlags == (ATOM)0)
|
|
goto Error2; // This should never happen... blow off applet
|
|
}
|
|
|
|
if( !SetProp( hwndStub, // Mark its name
|
|
MAKEINTATOM(aCPLName), (HANDLE)(DWORD)identity.aCPL) )
|
|
{
|
|
goto Error2;
|
|
}
|
|
|
|
if( !SetProp( hwndStub, // Mark its applet
|
|
MAKEINTATOM(identity.aCPL), (HANDLE)(DWORD)identity.aApplet ) )
|
|
{
|
|
goto Error2;
|
|
}
|
|
if (identity.flags)
|
|
{
|
|
if (aCPLFlags == (ATOM)0)
|
|
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
|
|
// Mark its flags
|
|
SetProp(hwndStub, MAKEINTATOM(aCPLFlags), (HANDLE)identity.flags);
|
|
}
|
|
|
|
#ifdef COOLICON
|
|
//
|
|
// Send the stub window a message so it will have the correct title and
|
|
// icon in the task list, etc...
|
|
//
|
|
|
|
if (hwndStub)
|
|
{
|
|
RUNDLL_NOTIFY sNotify;
|
|
|
|
sNotify.hIcon = pcpli->hIcon;
|
|
sNotify.lpszTitle = szApplet;
|
|
|
|
//
|
|
// HACK: It will look like the stub window is sending itself
|
|
// a WM_NOTIFY message. Oh well.
|
|
//
|
|
|
|
SendNotify(hwndStub, hwndStub, RDN_TASKINFO, (NMHDR FAR*)&sNotify);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Bring up the applet
|
|
//
|
|
|
|
if( info.params )
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_STARTWPARAMS to applet with: %s"), info.params);
|
|
|
|
bResult = CPL_CallEntry(pcplm, hwndStub, CPL_STARTWPARMS,
|
|
(LONG)nApplet, (LONG)info.params);
|
|
|
|
#ifdef DEBUG
|
|
if( !bResult )
|
|
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Applet failed CPL_STARTWPARAMS"));
|
|
#endif
|
|
}
|
|
|
|
#ifdef WINNT // REVIEW: May need this on Nashville too...
|
|
//
|
|
// Check whether we need to run as a different windows version
|
|
//
|
|
{
|
|
PPEB Peb = NtCurrentPeb();
|
|
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pcplm->minst.hinst;
|
|
PIMAGE_NT_HEADERS pHeader = (PIMAGE_NT_HEADERS)((DWORD)pcplm->minst.hinst + pDosHeader->e_lfanew);
|
|
|
|
if (pHeader->FileHeader.SizeOfOptionalHeader != 0 &&
|
|
pHeader->OptionalHeader.Win32VersionValue != 0)
|
|
{
|
|
//
|
|
// Stolen from ntos\mm\procsup.c
|
|
//
|
|
Peb->OSMajorVersion = pHeader->OptionalHeader.Win32VersionValue & 0xFF;
|
|
Peb->OSMinorVersion = (pHeader->OptionalHeader.Win32VersionValue >> 8) & 0xFF;
|
|
Peb->OSBuildNumber = (pHeader->OptionalHeader.Win32VersionValue >> 16) & 0x3FFF;
|
|
Peb->OSPlatformId = (pHeader->OptionalHeader.Win32VersionValue >> 30) ^ 0x2;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// If the cpl didn't respond to CPL_STARTWPARMSW (unicode version),
|
|
// maybe it is an ANSI only CPL
|
|
//
|
|
if( info.params && (!bResult) )
|
|
{
|
|
LPSTR lpstrParams;
|
|
int cchParams;
|
|
|
|
cchParams = WideCharToMultiByte(CP_ACP, 0, info.params, -1, NULL, 0, NULL, NULL);
|
|
|
|
lpstrParams = LocalAlloc( LMEM_FIXED, SIZEOF(char) * cchParams );
|
|
|
|
if (lpstrParams != NULL) {
|
|
|
|
WideCharToMultiByte(CP_ACP, 0, info.params, -1, lpstrParams, cchParams, NULL, NULL);
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_STARTWPARAMSA to applet with: %hs"), lpstrParams);
|
|
|
|
bResult = CPL_CallEntry(pcplm, hwndStub, CPL_STARTWPARMSA, (LONG)nApplet, (LONG)lpstrParams);
|
|
|
|
LocalFree( lpstrParams );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if( !bResult )
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_DBLCLK to applet"));
|
|
|
|
CPL_CallEntry(pcplm, hwndStub, CPL_DBLCLK, (LONG)nApplet, pcpli->lData);
|
|
|
|
//
|
|
// some 3x applets return the wrong value so we can't fail here
|
|
//
|
|
|
|
bResult = TRUE;
|
|
}
|
|
|
|
//
|
|
// wow, we made it!
|
|
//
|
|
|
|
bResult = TRUE;
|
|
|
|
RemoveProp( hwndStub, (LPCTSTR)(UINT)identity.aCPL );
|
|
Error2:
|
|
CPL_FreeCPLModule( pcplm );
|
|
Error1:
|
|
CPL_UnIdentify( &identity );
|
|
Error0:
|
|
return bResult;
|
|
}
|
|
|
|
|
|
static TCHAR const c_szDLLAndControl[] = TEXT("shell32,Control_RunDLL %s");
|
|
|
|
//
|
|
// Starts a remote control applet on a new RunDLL process.
|
|
//
|
|
|
|
BOOL
|
|
CPL_RunRemote( const TCHAR *cmdline, HWND errwnd )
|
|
{
|
|
TCHAR runparams[ 2 * MAXPATHLEN ];
|
|
|
|
wsprintf( runparams, c_szDLLAndControl, cmdline );
|
|
|
|
#ifdef SN_TRACE
|
|
DebugMsg(DM_TRACE, TEXT("cplobj.c - TRACE: Start RUNDLL with (%s)"), runparams);
|
|
#endif
|
|
|
|
return SHRunDLLProcess( errwnd, runparams, SW_SHOWNORMAL,
|
|
IDS_CONTROLPANEL );
|
|
}
|
|
|
|
|
|
//
|
|
// Attempts to open the specified control applet.
|
|
// Tries to switch to an existing instance before starting a new one.
|
|
// This function is exported through the semi-private table. <shsemip.h>
|
|
// UDOCUMENTED: You may pass a shell32 resource ID in place of a cmdline
|
|
//
|
|
|
|
BOOL
|
|
SHRunControlPanel( const TCHAR *orig_cmdline, HWND errwnd )
|
|
{
|
|
BOOL result = FALSE;
|
|
const TCHAR *cmdline;
|
|
TCHAR *dup;
|
|
|
|
//
|
|
// check to see if the caller passed a resource id instead of a string
|
|
//
|
|
|
|
if( HIWORD( orig_cmdline ) )
|
|
{
|
|
//
|
|
// looks like a real string to me!
|
|
//
|
|
|
|
cmdline = orig_cmdline;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// looks like a resource id to me!
|
|
//
|
|
|
|
if( ( cmdline = (const TCHAR *)LocalAlloc( LPTR, MAX_PATH ) ) == NULL )
|
|
return FALSE;
|
|
|
|
//
|
|
// NOTE: I'm casting away const below to initialize 'cmdline' only
|
|
// hereafter, it should be treated as const
|
|
//
|
|
|
|
if( !LoadString( HINST_THISDLL, (UINT)orig_cmdline, (TCHAR *)cmdline,
|
|
MAX_PATH ) )
|
|
{
|
|
LocalFree( (TCHAR *)cmdline );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// CPL_RunMeBaby whacks on the command line while parsing...use a dup
|
|
//
|
|
|
|
if( ( dup = (TCHAR *)LocalAlloc( LPTR, (lstrlen( cmdline ) + 1) * SIZEOF(TCHAR) ) ) != NULL )
|
|
{
|
|
lstrcpy( dup, cmdline );
|
|
|
|
//
|
|
// try to switch to an active CPL which matches our cmdline
|
|
//
|
|
|
|
result = CPL_RunMeBaby( NULL, NULL, dup, SW_SHOWNORMAL, FALSE );
|
|
|
|
LocalFree( dup );
|
|
}
|
|
|
|
if( !result )
|
|
{
|
|
//
|
|
// CPL_RunMeBaby couldn't find an active CPL to switch to (bummer)
|
|
// .: we should launch a new one in a separate process
|
|
//
|
|
|
|
result = CPL_RunRemote( cmdline, errwnd );
|
|
}
|
|
|
|
//
|
|
// did we load the command line from a resource?
|
|
//
|
|
|
|
if( cmdline != orig_cmdline )
|
|
LocalFree( (TCHAR *)cmdline );
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempts to open the specified control applet.
|
|
// This function is intended to be called by RunDLL for isolating applets.
|
|
// Tries to switch to an existing instance before starting a new one.
|
|
//
|
|
|
|
void WINAPI
|
|
Control_RunDLL(HWND hwndStub, HINSTANCE hAppInstance, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
#ifdef UNICODE
|
|
UINT iLen = lstrlenA(lpszCmdLine)+1;
|
|
LPWSTR lpwszCmdLine;
|
|
|
|
lpwszCmdLine = (LPWSTR)LocalAlloc(LPTR,iLen*SIZEOF(WCHAR));
|
|
if (lpwszCmdLine)
|
|
{
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
lpszCmdLine, -1,
|
|
lpwszCmdLine, iLen);
|
|
CPL_RunMeBaby( hwndStub,
|
|
hAppInstance,
|
|
lpwszCmdLine,
|
|
nCmdShow,
|
|
TRUE );
|
|
LocalFree(lpwszCmdLine);
|
|
}
|
|
#else
|
|
CPL_RunMeBaby( hwndStub,
|
|
hAppInstance,
|
|
lpszCmdLine,
|
|
nCmdShow,
|
|
TRUE // load our own if we can't switch to an existing one
|
|
);
|
|
#endif
|
|
}
|
|
|
|
void WINAPI
|
|
Control_RunDLLW(HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow)
|
|
{
|
|
#ifdef UNICODE
|
|
CPL_RunMeBaby( hwndStub,
|
|
hAppInstance,
|
|
lpwszCmdLine,
|
|
nCmdShow,
|
|
TRUE // load our own if we can't switch to an existing one
|
|
);
|
|
#else
|
|
UINT iLen = WideCharToMultiByte(CP_ACP, 0,
|
|
lpwszCmdLine, -1,
|
|
NULL, 0,
|
|
NULL, NULL)+1;
|
|
LPSTR lpszCmdLine;
|
|
|
|
lpszCmdLine = (LPSTR)LocalAlloc(LPTR,iLen);
|
|
if (lpszCmdLine)
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
lpwszCmdLine, -1,
|
|
lpszCmdLine, iLen,
|
|
NULL, NULL);
|
|
CPL_RunMeBaby( hwndStub,
|
|
hAppInstance,
|
|
lpszCmdLine,
|
|
nCmdShow,
|
|
TRUE );
|
|
LocalFree(lpszCmdLine);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
//
|
|
// Type checking
|
|
//
|
|
static RUNDLLPROCA lpfnRunDLL=Control_RunDLL;
|
|
static RUNDLLPROCW lpfnRunDLLW=Control_RunDLLW;
|
|
#endif
|
|
|
|
|
|
//
|
|
// data passed around dialog and worker thread for Control_FillCache_RunDLL
|
|
//
|
|
|
|
typedef struct
|
|
{
|
|
LPSHELLFOLDER psfControl;
|
|
LPENUMIDLIST penumControl;
|
|
HWND dialog;
|
|
|
|
} FillCacheData;
|
|
|
|
|
|
//
|
|
// important work of Control_FillCache_RunDLL
|
|
// jogs the control panel enumerator so it will fill the presentation cache
|
|
// also forces the applet icons to be extracted into the shell icon cache
|
|
//
|
|
|
|
DWORD
|
|
_Control_FillCacheThread( FillCacheData *data )
|
|
{
|
|
LPITEMIDLIST pidlApplet;
|
|
ULONG dummy;
|
|
|
|
while( data->penumControl->lpVtbl->Next( data->penumControl, 1,
|
|
&pidlApplet, &dummy ) == NOERROR )
|
|
{
|
|
SHMapPIDLToSystemImageListIndex( data->psfControl, pidlApplet, NULL );
|
|
ILFree( pidlApplet );
|
|
}
|
|
|
|
if( data->dialog )
|
|
EndDialog( data->dialog, 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// dlgproc for Control_FillCache_RunDLL UI
|
|
// just something to keep the user entertained while we load a billion DLLs
|
|
//
|
|
|
|
BOOL CALLBACK
|
|
_Control_FillCacheDlg( HWND dialog, UINT message, WPARAM wparam, LPARAM lparam )
|
|
{
|
|
switch( message )
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
DWORD dummy;
|
|
HANDLE thread;
|
|
|
|
((FillCacheData *)lparam)->dialog = dialog;
|
|
|
|
thread = CreateThread( NULL, 0,
|
|
(LPTHREAD_START_ROUTINE)_Control_FillCacheThread,
|
|
(LPVOID)lparam, 0, &dummy );
|
|
|
|
if( thread )
|
|
CloseHandle( thread );
|
|
else
|
|
EndDialog( dialog, -1 );
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// enumerates control applets in a manner that fills the presentation cache
|
|
// this is so the first time a user opens the control panel it comes up fast
|
|
// intended to be called at final setup on first boot
|
|
//
|
|
// FUNCTION WORKS FOR BOTH ANSI/UNICODE, it never uses lpszCmdLine
|
|
//
|
|
|
|
void WINAPI
|
|
Control_FillCache_RunDLL( HWND hwndStub, HINSTANCE hAppInstance, LPSTR lpszCmdLine, int nCmdShow )
|
|
{
|
|
LPSHELLFOLDER psfDesktop;
|
|
HKEY hk;
|
|
|
|
// nuke the old data so that any bogus cached info from a beta goes away
|
|
//
|
|
if( RegOpenKey( HKEY_LOCAL_MACHINE, c_szCPLCache, &hk ) == ERROR_SUCCESS )
|
|
{
|
|
RegDeleteValue( hk, c_szCPLData );
|
|
RegCloseKey( hk );
|
|
}
|
|
|
|
psfDesktop = Desktop_GetShellFolder( TRUE );
|
|
Shell_GetImageLists( NULL, NULL ); // make sure icon cache is around
|
|
|
|
if( psfDesktop )
|
|
{
|
|
LPITEMIDLIST pidlControl =
|
|
SHCloneSpecialIDList( hwndStub, CSIDL_CONTROLS, FALSE );
|
|
|
|
if( pidlControl )
|
|
{
|
|
FillCacheData data;
|
|
|
|
if( SUCCEEDED( psfDesktop->lpVtbl->BindToObject( psfDesktop,
|
|
pidlControl, NULL, &IID_IShellFolder, &data.psfControl ) ) )
|
|
{
|
|
if( SUCCEEDED( data.psfControl->lpVtbl->EnumObjects(
|
|
data.psfControl, NULL, SHCONTF_NONFOLDERS, &data.penumControl ) ) )
|
|
{
|
|
if( DialogBoxParam( HINST_THISDLL,
|
|
MAKEINTRESOURCE( DLG_CPL_FILLCACHE ), hwndStub,
|
|
_Control_FillCacheDlg, (LPARAM)&data ) == -1 )
|
|
{
|
|
_Control_FillCacheThread( &data );
|
|
}
|
|
|
|
data.penumControl->lpVtbl->Release( data.penumControl );
|
|
}
|
|
|
|
data.psfControl->lpVtbl->Release( data.psfControl );
|
|
}
|
|
|
|
ILFree( pidlControl );
|
|
}
|
|
}
|
|
}
|
|
|
|
void WINAPI
|
|
Control_FillCache_RunDLLW( HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow )
|
|
{
|
|
Control_FillCache_RunDLL(hwndStub,hAppInstance,NULL,nCmdShow);
|
|
}
|