Support for licensing in both a client and a server is simply a matter of supporting IClassFactory2, as demonstrated in the samples LicenseUser (CHAP05\LICUSER) and DKoala3 (CHAP05\DKOALA3).7
LicenseUser is a program similar to ObjectUser, with two top-level menus. The menu item Class Factory allows you to have the program obtain either an IClassFactory or an IClassFactory2 pointer from DKoala3. If you ask for IClassFactory2, you can also call its RequestLicKey member, which stores the BSTR key in the member variable CApp::m_bstrKey. The menu item Clear LicKey frees this string. The Koala Object menu is enabled only when you have obtained a class factory, so you can then try to create an object with IClassFactory::CreateInstance. If you've used the RequestLicKey menu, you can try creation through IClassFactory::CreateInstanceLic. In either case, LicenseUser tells you the result—whether the object was created or there was a "not licensed" error.
This menu structure and these operations allow you to experiment with different licensing situations, using the server DKOALA3.DLL and its license file DKOALA3.LIC (also in CHAP05\DKOALA3). For licensing to work, the LIC file must be located in the same directory as DKOALA3.DLL. If you run into funny problems, be sure to check this requirement. In any case, here are the scenarios that you can test with these samples:
Let's now see how DKoala3 validates the license and otherwise restrict access to its components. First of all, here's the contents of DKOALA3.LIC, a simple text file with Microsoft's standard license file format:
Koala Object #3 Copyright (c) 1993-1995 Microsoft Corp.
Warning: This product is licensed to you pursuant to the terms of the
Microsoft license agreement included with the original software, and is
protected by copyright law and international treaties. Unauthorized
reproduction or distribution may result in severe civil and criminal
penalties, and will be prosecuted to the maximum extent possible under
the law.
DKoala3 validates this license file during its initialization code in LibMain32 in DKOALA3.CPP, in which a global variable, g_fMachineLicensed, indicates whether a global license is present:
//License key string, stored in ANSI to match contents of LIC file
char g_szLic[]="Koala Object #3 Copyright (c) 1993-1995 Microsoft Corp.";
BOOL g_fMachineLicensed=FALSE;
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
, LPVOID pvReserved)
{
[Other code omitted]
§
g_fMachineLicensed=CheckForLicenseFile(hInstance
, TEXT("DKOALA3.LIC"), (BYTE *)g_szLic, lstrlenA(g_szLic));
§
return TRUE;
}
BOOL CheckForLicenseFile(HINSTANCE hInst, LPTSTR pszFile
, LPBYTE pbLic, UINT cb)
{
BOOL fFound=FALSE;
TCHAR szPath[_MAX_PATH];
LPTSTR pszTemp;
LPBYTE pbCompare;
HANDLE hFile;
UINT cbRead;
ULONG cbWasRead;
//Get module path; then replace DLL name with LIC filename.
GetModuleFileName(hInst, szPath, _MAX_PATH);
pszTemp=_tcsrchr(szPath, '\\')+1;
lstrcpy(pszTemp, pszFile);
hFile=CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ
, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE==hFile)
return FALSE;
cbRead=cb*sizeof(BYTE);
pbCompare=(LPBYTE)malloc(cbRead+4);
if (NULL!=pbCompare)
{
ReadFile(hFile, pbCompare, cbRead, &cbWasRead, NULL);
fFound=(0==memcmp(pbLic, pbCompare, cb));
free(pbCompare);
}
CloseHandle(hFile);
return fFound;
}
The function CheckForLicenseFile attempts to open the license file specified by pszFile in the same location as the server module identified by hInst. If that file is found and opened, the function loads cb bytes from that file and compares it with the contents at pbLic. If the two blocks of data match, the license is validated; otherwise, the machine is not licensed. In DKoala3, I'm comparing only the first line of the license file, which is usually suitable; however, feel free to be as secure as you want.
DKoala3's class factory CKoalaClassFactory (DKOALA3.CPP) now inherits from IClassFactory2. Because both CreateInstance and CreateInstanceLic share a lot of functionality, I broke out the core creation sequence to make a private member function, CKoalaClassFactory::CreateAnObject. This function is unrestrictive and will create an object whenever asked, so the other public members that are simply validating the necessary license then call CreateAnObject:
STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
, REFIID riid, PPVOID ppvObj)
{
*ppvObj=NULL;
if (!g_fMachineLicensed)
return ResultFromScode(CLASS_E_NOTLICENSED);
return CreateAnObject(pUnkOuter, riid, ppvObj);
}
STDMETHODIMP CKoalaClassFactory::CreateInstanceLic(LPUNKNOWN pUnkOuter
, LPUNKNOWN pUnkReserved, REFIID riid, BSTR bstrKey
, PPVOID ppvObj)
{
BOOL fMatch;
BSTR bstrTemp;
HRESULT hr;
*ppvObj=NULL;
//Get our own license key, which should match bstrKey exactly.
hr=RequestLicKey(0, &bstrTemp);
if (FAILED(hr))
return hr;
fMatch=(0==memcmp(bstrTemp, bstrKey, lstrlen(bstrTemp)));
SysFreeString(bstrTemp);
if (!fMatch)
return ResultFromScode(CLASS_E_NOTLICENSED);
return CreateAnObject(pUnkOuter, riid, ppvObj);
}
As you can see, CreateInstance will work only if the global license exists; CreateInstanceLic will allow creation only if it's passed a license key identical to what it returns from RequestLicKey. So what is the key we return? Microsoft's convention is to use the first line of the license file. In DKoala3, this is stored in g_szLic, the same buffer used to validate the license file:8
STDMETHODIMP CKoalaClassFactory::RequestLicKey(DWORD dwReserved
, BSTR *pbstrKey)
{
OLECHAR szTemp[256];
//Can't give away a key on an unlicensed machine.
if (!g_fMachineLicensed)
return ResultFromScode(CLASS_E_NOTLICENSED);
mbstowcs(szTemp, g_szLic, sizeof(g_szLic));
*pbstrKey=SysAllocString(szTemp);
return (NULL!=*pbstrKey) ? NOERROR : ResultFromScode(E_OUTOFMEMORY);
}
Notice how this function fails if a global license is not available. If you didn't include a check like this, it would certainly make it easy for some clever person to cheat you out of an object! Of course, if you choose not to provide run-time licensing at all, RequestLicKey should return E_NOTIMPL.
All that's left now is to implement GetLicInfo, which fills a structure with appropriate information:
STDMETHODIMP CKoalaClassFactory::GetLicInfo(LPLICINFO pLicInfo)
{
if (NULL==pLicInfo)
return ResultFromScode(E_POINTER);
pLicInfo->cbLicInfo=sizeof(LICINFO);
//This says whether RequestLicKey will work.
pLicInfo->fRuntimeKeyAvail=g_fMachineLicensed;
//This says whether standard CreateInstance will work.
pLicInfo->fLicVerified=g_fMachineLicensed;
return NOERROR;
}
Because we implement RequestLicKey, and thereby support run-time licensing, we store TRUE in fRuntimeKeyAvail if the current machine itself is licensed, or store FALSE if otherwise. This is the same value that we store in fLicVerified. If we didn't support run-time licensing, fRuntimeKeyAvail would always be set to FALSE, with fLicVerified remaining variable.
7 There is not a licensed version of EKoala because at the time of writing there was no marshaling support for IClassFactory2 restricting its use to in-process servers only. |
8 SysAllocString always takes a Unicode string in Win32, so we have to convert the ANSI g_szLic to accommodate that condition. |