Where does MFC COleControl::DoPropExchange store persistent properties? - c++

I have taken over maintenance of a legacy MFC OCX control in C++. The project is now in VS2013. I'm trying to understand the functioning of the DoPropExchange method. This method appears to be calling PX_?????(member) for nearly all the data members in the control where ???? is the type (Bool, Short, Long ...) My understanding is these are called for the purpose of providing persistent storage of properties. But from my understanding of the operation of the OCX control there are no persistent properties. Would there be any other reason to be calling PX_???? for all data members in DoPropExchange other than to support persistent properties? I'm also trying to understand where these persistent properties are loaded/stored. Where is the serialized file for loading/storing persistent property values specified?
Here is the source for DoPropExchange
// CSigPlusCtrl::DoPropExchange - Persistence support
void CSigPlusCtrl::DoPropExchange(CPropExchange* pPX)
{
DWORD Version;
long BaudRate;
short ComPort;
BOOL Rv;
LOG(("DoPropExchange Entry"));
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
Version = pPX->GetVersion();
if (pPX->IsLoading())
{
LoadDefaultProperties();
LoadIniParameters();
}
if ((Version & 0xFFFF0000) == (DWORD)MAKELONG(0, _wVerMajor))
{
Rv = PX_Short(pPX, _T("ImageFileFormat"), ImageFileFormat, 0);
Rv = PX_Short(pPX, _T("ImageXSize"), ImageXSize, 0);
Rv = PX_Short(pPX, _T("ImageYSize"), ImageYSize, 0);
Rv = PX_Short(pPX, _T("ImagePenWidth"), ImagePenWidth, 1);
. . .
Rv = PX_Short(pPX, _T("ZoomY"), ZoomY, 0);
Rv = PX_Short(pPX, _T("ZoomPower"), ZoomPower, 1);
if (pPX->IsLoading())
{
if (SigBlob != NULL)
{
GlobalFree(SigBlob);
SigBlob = NULL;
}
}
else
{
if (SigBlob == NULL)
{
SigBlobType* SigBlobPtr;
SigBlob = GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
SigBlobPtr = (SigBlobType*)GlobalLock(SigBlob);
SigBlobPtr->Size = 0;
GlobalUnlock(SigBlob);
}
}
if ((Version & 0xFFFF) == Version223)
{
Rv = PX_Blob(pPX, _T("SigBlob"), SigBlob, NULL);
}
if ((Version & 0xFFFF) >= Version224)
{
CString SigStr;
if (!pPX->IsLoading())
{
SigStr = BlobToString();
}
Rv = PX_String(pPX, _T("SigStringStored"), SigStr, _T(""));
if (pPX->IsLoading())
{
BlobFromString(SigStr);
}
}
}
else
{
SigMessageBox("Warning Incompatable Versions of SigPlus Control");
}
LoadTabletParameters();
LOG(("DoPropExchange Exit"));
}
EDIT Added 6-21-2018
Running in the debugger I observe that when DoPropExchange is called, VS2013 shows the stack with a message that stack frames below may be incorrect. And the one frame just above, that calls DoPropExchange, is from mfc120d.dll which does not have symbol file available mfc120d.i386.pdb.
This Microsoft Forum Post seems to indicate that the symbol file is not available for VS2015 and I'm wondering if that is also the case for VS2013. So far I have not been able to find place to download MFC120 symbols for debug.
Starting a bounty today to find someone who can explain how and where properties are normally serialized for OLE controls and what methods are used to specify the serialized data storage location/media. This is of concern because this control runs in a Citrix ZenDesk network environment in a Terminal Aware program and if properties are being stored somewhere then each client needs to specify a location unique to that client.

The DoPropExchange is used to implement control attribute persistence mainly between design and run-time. The actual destination sink is passed by the client of the OCX.
In VC the the settings are stored in RC file while in VB in frm and frx files. If you open FRM in notepad you would probably see the section with the properties of this control.
As a side note, there is a similar implementation in case the control is used in HTML with inline settings in he html itself.
Unless your VB clients saves the settings externally via direct calls to Property bag function it is not likely you have a issue here since the above properties are not stored at runtime.

Related

How to create a list of Keyboards. Extract KLID from HKL?

Windows 10 / C++ / Win32
I need to make a list of 'installed' Keyboards.
The components of my 'list' needs to contain:
The HKL for the keyboard.
The KLID for the keyboard.
The values of the registry values obtained from:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts
Example: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00000409
Value: Layout File
Value: Layout Text
The only way I am aware of enumerating Keyboards is via GetKeyboardLayoutList(), which returns a list of HKL's.
Methodology which works for 'standard' keyboards (HKL's 04090409, 04070407,..).
TCHAR Buffer[50];
TCHAR Buffer2[50];
HKL LoadedKeyboardLayout;
// Hkl: 04090409
_stprintf_s(Buffer, (sizeof(Buffer)/sizeof(TCHAR)), _T("%08X"), (((UINT)Hkl >> 16) & 0xFFFF));
// Buffer: "0000409"
LoadedKeyboardLayout = LoadKeyboardLayout(Buffer, (UINT)0);
// It Loads
ActivateKeyboardLayout(LoadedKeyboardLayout, KLF_SETFORPROCESS);
// It Activates
GetKeyboardLayoutName(Buffer2);
// Buffer2: "00000409"
// I can now fish the registry HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00000409
This DOES not work when the 'device identifier' is NOT 0000.
From LoadKeyboardLayout() documentation:
The name of the input locale identifier to load. This name is a string composed of the hexadecimal value of the Language Identifier (low word) and a device identifier (high word). For example, U.S. English has a language identifier of 0x0409, so the primary U.S. English layout is named "00000409". Variants of U.S. English layout (such as the Dvorak layout) are named "00010409", "00020409", and so on.
If one creates a 'Custom' keyboard, I find it is impossible to get the 'device identifier' from the custom keyboards HKL.
For instance:
Create a Custom Keyboard US with MKLCS.
Install it.
The 'KLID' will be 'A0000409'.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\a0000409
The HKL will be 0xF0C00409 (as returned within the HKL list generated by GetKeyboardLayoutList()).
In order to load the keyboard with LoadKeyboardLayout(), one needs 'A0000409'.
It does not seem possible to create A0000409 from F0C00409.
I also created my own keyboard layout without MKLCS.
I arbitrarily named it 00060409.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00060409
It's HKL is FFFE0409 (as returned within the HKL list generated by GetKeyboardLayoutList()).
It does not seem possible to create 00060409 from FFFE0409.
With all of that said, how does one obtain a KLID from an HKL?
Or, is there another way I can go about creating my list of installed keyboards?
//================================================================
11/25/2020 Addition.
Thank you Rita.
It seems as if GetKeyboardLayoutList() creates a list of 'Loaded' keyboard layouts.
System loaded keyboard layouts, 0x0409409,....
Keyboard Layouts installed via MKLCS installation.
It seems that any Keyboards defined within the following registry keys will get loaded at boot.
HKEY_CURRENT_USER\Keyboard Layout
Note the Preload and Substitute value's.
(Note there are many other 'Keyboard Layout' keys spread around within the registry, so I am
not sure if HKEY_CURRENT_USER\Keyboard Layout is the actual registry key that defines the
PreLoad - Substitutes.
HKEY_USERS\.DEFAULT\Keyboard Layout
HKEY_USERS\S-1-5-18 (My User Account)
HKEY_LOCAL_MACHINE\SYSTEM\Keyboard Layout
)
So there is no doubt in my mind that Rita's example works due to the fact that her HKEY_CURRENT_USER\Keyboard Layout contains the following entries:
Preload:
1 d0010804
Substitutes:
d0010804 a0000804
The entries may have been put there by the MKLCS installer?
Or perhaps the action of adding the keyboard via Settings->Time & Language->Click on Preferred Language->Options->Add a Keyboard
From ActivateKeyboardLayout() documentation:
The input locale identifier must have been loaded by a previous call to the LoadKeyboardLayout function.
Since a0000804 (HKL: F0C00804) is actually already loaded ActivateKeyboardLayout() works.
Since my KLID: 00060409 is not referenced within any of the X\Keyboard Layout Preload and Substitutes
I must physically call LoadKeyBoardLayout(L"00060409") in order for it to appear within the GetKeyboardList() HKL's.
Thanks again Rita.
how does one obtain a KLID from an HKL?
There seems no direct method out of box to achieve it.
A workaround is retrieving a list of HKLs, then active a layout via a HKL, then get its KLID. The following is an example:
int cnt = GetKeyboardLayoutList(sizeof(hklArr)/sizeof(hklArr[0]), hklArr);
if(cnt > 0)
{
printf("keyboard list: \n");
for (UINT i = 0; i < cnt; i++)
{
printf("%x\n", (LONG_PTR)hklArr[i]);
if (ActivateKeyboardLayout(hklArr[i], KLF_SETFORPROCESS))
{
WCHAR pName[KL_NAMELENGTH];
if (GetKeyboardLayoutName(pName))
{
wprintf(L"layout name (KLID): %s\n", pName);
}
}
}
}
Result like this:
Update:
Update 2: Share my creating and installing steps.
I use Keyboard Layout Creator 1.4.
Load an existing keyboard for testing purpose. (You can modify based on it or create your own completely.)
Valid and test keyboard to make sure it works as you expected. Then build DLL and setup package.
Run setup.exe generated by step 2. After installation complete you will see the related preload keyboard layout item in registry.
There is no safe way to do this since it is not documented and can be changed after Windows updates.
Proper algorithm is posted here.
Here is my code that is working on my Windows 10 Version 21H2 (November 2021 Update) so far:
// Returns KLID string of size KL_NAMELENGTH
// Same as GetKeyboardLayoutName but for any HKL
// https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values
BOOL GetKLIDFromHKL(HKL hkl, _Out_writes_(KL_NAMELENGTH) LPWSTR pwszKLID)
{
bool succeded = false;
if ((HIWORD(hkl) & 0xf000) == 0xf000) // deviceId contains layoutId
{
WORD layoutId = HIWORD(hkl) & 0x0fff;
HKEY key;
CHECK_EQ(::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key), ERROR_SUCCESS);
DWORD index = 0;
while (::RegEnumKeyW(key, index, pwszKLID, KL_NAMELENGTH) == ERROR_SUCCESS)
{
WCHAR layoutIdBuffer[MAX_PATH] = {};
DWORD layoutIdBufferSize = sizeof(layoutIdBuffer);
if (::RegGetValueW(key, pwszKLID, L"Layout Id", RRF_RT_REG_SZ, nullptr, layoutIdBuffer, &layoutIdBufferSize) == ERROR_SUCCESS)
{
if (layoutId == std::stoul(layoutIdBuffer, nullptr, 16))
{
succeded = true;
DBGPRINT("Found KLID 0x%ls by layoutId=0x%04x", pwszKLID, layoutId);
break;
}
}
++index;
}
CHECK_EQ(::RegCloseKey(key), ERROR_SUCCESS);
}
else
{
WORD langId = LOWORD(hkl);
// deviceId overrides langId if set
if (HIWORD(hkl) != 0)
langId = HIWORD(hkl);
std::swprintf(pwszKLID, KL_NAMELENGTH, L"%08X", langId);
succeded = true;
DBGPRINT("Found KLID 0x%ls by langId=0x%04x", pwszKLID, langId);
}
return succeded;
}
Also you can use this code to enumerate layout profiles with KLID in LAYOUTORTIPPROFILE.szId:
typedef struct tagLAYOUTORTIPPROFILE {
DWORD dwProfileType;
LANGID langid;
CLSID clsid;
GUID guidProfile;
GUID catid;
DWORD dwSubstituteLayout;
DWORD dwFlags;
WCHAR szId[MAX_PATH];
} LAYOUTORTIPPROFILE;
// Flags used in LAYOUTORTIPPROFILE::dwProfileType
#define LOTP_INPUTPROCESSOR 1
#define LOTP_KEYBOARDLAYOUT 2
// Flags used in LAYOUTORTIPPROFILE::dwFlags.
#define LOT_DEFAULT 0x0001
#define LOT_DISABLED 0x0002
std::vector<LAYOUTORTIPPROFILE> EnumLayoutProfiles()
{
// http://archives.miloush.net/michkap/archive/2008/09/29/8968315.html
// https://learn.microsoft.com/en-us/windows/win32/tsf/enumenabledlayoutortip
typedef UINT(WINAPI* EnumEnabledLayoutOrTipFunc)(LPCWSTR pszUserReg, LPCWSTR pszSystemReg, LPCWSTR pszSoftwareReg, LAYOUTORTIPPROFILE* pLayoutOrTipProfile, UINT uBufLength);
static EnumEnabledLayoutOrTipFunc EnumEnabledLayoutOrTip = reinterpret_cast<EnumEnabledLayoutOrTipFunc>(::GetProcAddress(::LoadLibraryA("input.dll"), "EnumEnabledLayoutOrTip"));
if (!EnumEnabledLayoutOrTip)
return {};
const UINT count = EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, nullptr, 0);
std::vector<LAYOUTORTIPPROFILE> layouts;
layouts.resize(count);
const UINT written = EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, layouts.data(), count);
CHECK_EQ(count, written);
return layouts;
}
The LAYOUTORTIPPROFILE.szId string format of the layout is:
<LangID>:<KLID>
The string format of the text service profile (IME) is:
<LangID>:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
More info here.

How can I give an old ActiveX control new GUIDs?

I am trying to modify an ActiveX control developed in Visual Studio 2008 to use it for a purpose for which it was not originally designed. I will be reusing at least 90% of its code. Therefore, I would like to begin by creating an identical control that uses different GUIDs. I tried to follow instructions I found here (a very old link, written in 2004), but when I tried to build my project, I got an assertion failure in ctlreg.cpp line 113. Then, I restored all my changed files back to their original states, and for each GUID is the .odl file, I searched for the GUID in my .cpp and .h files and changed it wherever I found it. I also made sure to change my major version number. I still get the assertion failure. What else should I be doing?
Here's the code from ctlreg.cpp from the start of the method containing the assertion to the assertion itself:
BOOL AFXAPI AfxOleRegisterTypeLib(HINSTANCE hInstance, REFGUID tlid,
LPCTSTR pszFileName, LPCTSTR pszHelpDir)
{
BOOL bSuccess = FALSE;
CStringW strPathNameW;
wchar_t *szPathNameW = strPathNameW.GetBuffer(_MAX_PATH);
::GetModuleFileNameW(hInstance, szPathNameW, _MAX_PATH);
strPathNameW.ReleaseBuffer();
LPTYPELIB ptlib = NULL;
// If a filename was specified, replace final component of path with it.
if (pszFileName != NULL)
{
int iBackslash = strPathNameW.ReverseFind('\\');
if (iBackslash != -1)
strPathNameW = strPathNameW.Left(iBackslash+1);
strPathNameW += pszFileName;
}
if (SUCCEEDED(LoadTypeLib(strPathNameW.GetString(), &ptlib)))
{
ASSERT_POINTER(ptlib, ITypeLib);
LPTLIBATTR pAttr;
GUID tlidActual = GUID_NULL;
if (SUCCEEDED(ptlib->GetLibAttr(&pAttr)))
{
ASSERT_POINTER(pAttr, TLIBATTR);
tlidActual = pAttr->guid;
ptlib->ReleaseTLibAttr(pAttr);
}
// Check that the guid of the loaded type library matches
// the tlid parameter.
ASSERT(IsEqualGUID(tlid, tlidActual));

Saving printer properties in XE4 c++ VCL application

I am trying to save some of the printer properties (ie page size) to an ini file in the following way:
Printer()->PrinterIndex = Printer()->PrinterIndex;
wchar_t printerDevice[256], printerDriver[256], printerPort[256];
THandle printerDeviceModeHandle = 0;
DEVMODE *printerDeviceMode;
Printer()->GetPrinter(printerDevice, printerDriver, printerPort, printerDeviceModeHandle);
if (printerDeviceModeHandle != 0)
{
DEVMODE *printerDeviceMode = (DEVMODE *) GlobalLock((void *)printerDeviceModeHandle);
if ((printerDeviceMode != NULL) && (printerDeviceMode->dmFields & DM_PAPERSIZE))
ini->WriteInteger("PrintingPage", "PaperSize", printerDeviceMode->dmPaperSize);
GlobalUnlock((void *)printerDeviceModeHandle);
}
The code gets within the inner if statement but When I inspect the printerDeviceMode variable only the dmDeviceName appears to be correct. Many of the other settings appear to be garbage (including dmPaperSize):
Note that dmSize is 0.
I have tried changing the page size for the printer using a TPrinterSetupDialog components but the value of dmPaperSize stays the same.
Have I missed a step somewhere?
This is a XE4 c++ builder VCL project.

How to get Game Controller name (Windows 10 / C++)

I've seen lots of info on how to read game controller input using XInput but I really want to know the name of the controller that is connected.
How can I find out the name of connected controllers on a PC or more specifically the name of the controller I am reading XInput from?
You can do this by calling the joyGetDevCaps function which returns a JOYCAPS structure containing all information (including name) of the connected controller.
You can use DirectInput to get the name of the device. You need to do that using a callback:
pDirectInput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoystickCallbackStatus, &joynum, DIEDFL_ATTACHEDONLY);
Then you have to be a bit creative: on Startup detect all devices using the callback and store their name/GUID... and then when a device is hot-plugged (which you detect with XInputGetState) look for the device which you don't know about yet, with a modified version of that earlier callback, something similar to this:
BOOL CALLBACK EnumJoystickCallbackStatus(LPCDIDEVICEINSTANCE pdevinst, LPVOID pref)
{
DWORD devtype = GET_DIDEVICE_TYPE(pdevinst->dwDevType);
DWORD subtype = GET_DIDEVICE_SUBTYPE(pdevinst->dwDevType);
if (devtype == DI8DEVTYPE_KEYBOARD || (devtype == DI8DEVTYPE_SUPPLEMENTAL && subtype == DI8DEVTYPESUPPLEMENTAL_UNKNOWN)) {
return DIENUM_CONTINUE;
}
ULONG* pjoynum = reinterpret_cast<ULONG*>(pref);
if (IsXInputDevice(&pdevinst->guidProduct)) {
// loop through your known devices and see if this GUI already exists
// we are looking for one which we don't know about yet.
if (!found) {
// store GUI / Name / ... in some global controllers-array
return DIENUM_STOP; // done
}
}
DEBUG_INFO(Debug::XDF_General, "continue");
return DIENUM_CONTINUE;
}
Note that if you have multiple xbox-controllers, you'll get a callback for each one separately.
Implementation of IsXInputDevice can be found in the MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx

Address COM in OS X via FireMonkey/C++

The COM-based Blackmagic DeckLink API is available for both Windows and OS X. I wish to address it in OS X but using FireMonkey (FMX) in C++. The problem is that their sample code* is written for Cocoa and I have no idea how to rewrite it for FireMonkey. Does anyone have any experience with this, is it even possible.
Or, is there a generic way in which libraries with a COM interface can be addressed in FireMonkey/OS X?
Here's part of the code for Cocoa, per request.
void InitDeckLinkAPI (void)
{
CFURLRef bundleURL;
bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR(kDeckLinkAPI_BundlePath), kCFURLPOSIXPathStyle, true);
if (bundleURL != NULL)
{
gDeckLinkAPIBundleRef = CFBundleCreate(kCFAllocatorDefault, bundleURL);
if (gDeckLinkAPIBundleRef != NULL)
{
gCreateIteratorFunc = (CreateIteratorFunc)CFBundleGetFunctionPointerForName(gDeckLinkAPIBundleRef, CFSTR("CreateDeckLinkIteratorInstance_0002"));
gCreateAPIInformationFunc = (CreateAPIInformationFunc)CFBundleGetFunctionPointerForName(gDeckLinkAPIBundleRef, CFSTR("CreateDeckLinkAPIInformationInstance_0001"));
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)CFBundleGetFunctionPointerForName(gDeckLinkAPIBundleRef, CFSTR("CreateOpenGLScreenPreviewHelper_0001"));
gCreateCocoaPreviewFunc = (CreateCocoaScreenPreviewFunc)CFBundleGetFunctionPointerForName(gDeckLinkAPIBundleRef, CFSTR("CreateCocoaScreenPreview_0001"));
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)CFBundleGetFunctionPointerForName(gDeckLinkAPIBundleRef, CFSTR("CreateVideoConversionInstance_0001"));
}
CFRelease(bundleURL);
}
}
bool IsDeckLinkAPIPresent (void)
{
// If the DeckLink API bundle was successfully loaded, return this knowledge to the caller
if (gDeckLinkAPIBundleRef != NULL)
return true;
return false;
}
IDeckLinkIterator* CreateDeckLinkIteratorInstance (void)
{
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
if (gCreateIteratorFunc == NULL)
return NULL;
return gCreateIteratorFunc();
}
*Too long to include here but you can download it here.
On platforms without native COM support (such as OS X) a C entry point should be provided to access an Interface and in DeckLink API there is such factory methods :
IDeckLinkIterator *deckLinkIterator = CreateDeckLinkIteratorInstance();
Thus you can simply use DeckLink API in C++Builder. But there is a problem, C++Builder has defined some COM types such as IUnknown in sysmac.h (is included by System.hpp) which conflicts with same types have been defined in CFPluginCOM.h, If your project includes System.hpp such as all firemonkey projects the compiler displays an error:
[bccosx Error] sysmac.h(287): E2238 Multiple declaration for 'IUnknown'
There is a sample named DeckControl in samples directory of DeckLink API which is a console program and you can compile it by C++Builder:
Create a console project and specify main.cpp as project source.
Select "None" as Target Framework
Add "OSX" Platform
The project is compiled successfuly.
And what about Fmx project (uses System.hpp) ?
Create a wrapper unit (For example bcb_deck) put needed APIs in it. Note that do not include "DeckLinkAPI.h" in unit header, this causes same problems described above, but put it in cpp (bcb_deck.cpp), for example:
bcb_deck.cpp:
void* createDeckLinkIteratorInstance() // use camel case to prevent conflict
{
return (void*) CreateDeckLinkIteratorInstance();
}
bool deckLinkIteratorNext(void *hDeckLinkIterator, void *hDeckLink)
{
IDeckLinkIterator *deckLinkIterator = (IDeckLinkIterator*) hDeckLinkIterator;
IDeckLink *deckLink = (IDeckLink*) hDeckLink;
return deckLinkIterator->Next(&deckLink) == S_OK;
}
Usage:
#include "bcb_deck.h"
void *hDeckLinkIterator, *hDeckLink;
hDeckLinkIterator = createDeckLinkIteratorInstance();
if (hDeckLinkIterator)
{
// Enumerate all cards in this system
while (deckLinkIteratorNext(hDeckLinkIterator, hDeckLink))
{
// ...
}
}