March/April 1999
As Dr. GUI writes this, the new color Windows CE Palm-size PCs have just been introduced. (But most models aren't available even as you read this.) The new screens are, as Dr. GUI's medical student friends might say, AWESOME, DUDE!
It's actually not the color that's interesting to the good doctor, although color alone is important for many apps, such as Web browsing and maps. What's really cool, in Dr. GUI's not-so-humble opinion, is that the contrast is much higher than with black-and-white screens, which makes readability much, much better.
I'd like to write a C++/MFC application that'd be able to display HTML files using the WebBrowser control. However, I can't find any documentation on how to make use of it in my app. Can you explain how an application would need be different to host the control?
Tony Rahman
What a great idea! Writing an MFC app that can display HTML! Dr. GUI wishes he'd patented the idea.
Because the Web capabilities are delivered as a set of components, you can use them in your applications—just as Visual Studio, MSDN, Turbo Tax (and many other apps) do; even Windows itself uses them internally. There are excellent references and code samples on reusing this control in custom applications on the MSDN Online Web Workshop (http://msdn.microsoft.com/workshop/).
But if you're using Microsoft Visual C++ 6.0, you've hit the jackpot: VC++ adds a bunch of new functionality that makes using the WebBrowser control easier than before.
With Visual C++ 6.0, this whole process is made easier with the new CHtmlView class. CHtmlView provides a view that hosts the WebBrowser control. When you use the MFC AppWizard to create a new MFC application, select CHtmlView as your view's base class on the last wizard step and that's it. Dr. GUI is not making this up. All that's left is for you to access the WebBrowser control methods and properties from your view class and override any event handlers you require. That's a very good thing.
If you're not using Visual C++ 6.0, upgrade. Actually, you can use the WebBrowser control from any application—the only requirement is that it can host ActiveX controls.
To add any ActiveX control to your MFC project, make sure that support for ActiveX controls was selected in the MFC AppWizard and select Add To Project > Components and Controls... from the Project menu. Once added, the ActiveX control is available as an icon in the resource editor Controls toolbox and accessible through the freshly generated wrapper class that was created from the control's type library. The ActiveX control can then be dropped onto a dialog template or created dynamically in a view.
I have been working with COM and ATL for about a year now and have really enjoyed it, but I have a few questions you might be able to help me with. First, is it possible to use smart pointers for interfaces in the same library? For example, if one has an interface, IFoo, which uses another interface, IGoo,
to pass back data in one of its methods. The smart pointer declaration:
_COM_SMARTPTR_TYPEDEF(IGoo, uuidof(IGoo));
can be added, but this does not give access to the properties. In other words, I'd like to write:
x =IGoo->Gunc
but have to write:
IGoo->get_Gunc( &x )
which is a pain.
Also since I only want IGoo to be created by IFoo, I tried to use the noncreatable description in the IDL file and delete the OBJECT_ENTRY() for IGoo. However, now I can't even use CreateInstance() in the same library. Should I be doing this another way?
Brett D. Bock
The COM smart pointers are pretty nice, aren't they? They're Dr. GUI's favorite way of accessing COM objects—as easy as VB, with all the power of C++.
When you use the #import statement, it not only defines a smart pointer class for your interface using the COM_SMARTPTR_TYPEDEF()
macro but also creates a wrapper class that allows accessing properties as x = IGoo->Gunc
.
The wrapper class contains functions that return the property as a return value. The wrapper classes are in the files created with the .tlh and .tli extension in the output directory. Because you directly added the COM_SMARTPTR_TYPEDEF()
macro, the wrapper classes are not available and you will not be able to do this.
To create the wrapper class, add the #import statement without specifying no_namespaces or named_guids. Specify the <project_name>.tlb file generated by MIDL as the input file. (This is fine because the MIDL compiler runs before the C++ compiler in an ATL project.) If you specify no_namespaces this leads to a clash between the interface definition in the MIDL header and the code generated by #import.
About your difficulties in creation: If the good doctor understands you correctly, you want IGoo to be created only through IFoo and not directly by any external client. The steps you took with the noncreateable IDL attribute for the IGoo coclass and deleting the OBJECT_ENTRY are correct.
However, you cannot use the smart pointer's CreateInstance() call—look at com_ptr_t::CreateInstance()
in comip.h. The smart pointer's CreateInstance method calls CoCreateInstance, which in turn calls CoGetClassObject, which calls IClassFactory::CreateInstance. But because you deleted the OBJECT_ENTRY() macro for IGoo, there is not a class factory associated with IGoo. CoCreateInstance relies on the class factory to create the object—because there is no class factory, CoCreateInstance fails.
To create a COM object in ATL locally, do the following (search on article ID number Q181265 for the Microsoft Knowledge Base article "HOWTO: Create COM ATL Objects" for more information):
//CGoo is class implementing IGoo
CComObject<CGoo>* pGoo;
// Note that at this point the ref count for the object is 0.
HRESULT hRes = CComObject< CGoo >::CreateInstance(&pGoo);
//smart pointer for IGoo
IGooPtr spGoo;
pGoo->QueryInterface(IID_IGoo, (void**) &spGoo);
// use spGoo now, refcount is 1...
We're using the ATL object template class here, and calling the ATL static method CreateInstance to create the object. This is the function that would otherwise be called by the class factory—so this is the right way to create objects without using a class factory.
After we create the object, we call QueryInterface using the ATL smart pointer but pass the COM smart pointer. At this point, we use the object through spGoo so we can take advantage of the helper functions to access properties.
How do I return an ADO recordset from a method in a VC++ COM object?
Jim Hocking
Returning an ADO recordset from C++ requires that you tweak your IDL a bit. Inside the library section, add the following line as the last importlib statement:
importlib("C:\Program Files\Common Files\System\ado\msado15.dll");
(Of course, modify the path to suit your system.)
This will give you a declaration of, among other things, _Recordset.
Once you have this declaration, you can write the IDL for a method that returns a _Recordset:
[id(1), helpstring("Get Recordset")]
HRESULT GetRs([in] BSTR strQuery, [out,retval] _Recordset **ppRs);
Assuming you're using #import on the ADO DLL to get the proper declarations, you could implement this method as follows:
HRESULT CGetRecSet::GetRs(BSTR strQuery , _Recordset **pRs)
{
_RecordsetPtr spRs;
try
{
spRs.CreateInstance(__uuidof(Recordset));
// Set the cursorLoction to client
spRs->CursorLocation = adUseClient;
// Open the recordset on a query passed as parameter
spRs->Open(strQuery, _bstr_t("DSN=Ram;UID=sa;PWD=;"),
adOpenStatic, adLockOptimistic, adCmdText);
// Disconnect the recordset
spRs->putref_ActiveConnection(NULL);
// Set the output parameter by doing a QueryInterface
HRESULT hr = spRs->QueryInterface(
__uuidof(_Recordset),(void **)pRs);
return hr;
}
catch(_com_error &e)
{
return e.Error();
}
}
The Knowledge Base article Q186387, "SAMPLE: Ado2atl.exe Returns ADO Interfaces from COM" (search on article ID number), has a sample project demonstrating how to return an ADO recordset from Visual C++ COM objects. This KB article also demonstrates how to return an ADO recordset as an IDispatch pointer.
Is there any way to force a kernel-mode driver to run on a specific processor?
Or is there a way of finding out what processor you are running on from kernel mode?
Martin Payne
First, perhaps, we should talk to your driver to see why it feels a special affinity for a particular processor. Or perhaps not. But you must admit this is an odd thing to want in a symmetric multiprocessing system—it's hardly symmetric.
In any case, life is a bit different for device drivers—major portions of the Win32 API are simply not available to device drivers. For instance, you cannot call ZwQueryInformationThread in a device driver—so there's no way to find out what processor you're running on.
You can, however, set your thread's affinity from within a device driver by using ZwSetInformationThread:
KAFFINITY threadAffinityMask;
threadAffinityMask = NumberOfActiveProcessors &
(1 << TargetProcessorNumber);
// TargetProcessorNumber is a zero based number
status = ZwSetInformationThread((HANDLE)-2, //current thread handle
ThreadAffinityMask,
&threadAffinityMask,
sizeof(KAFFINITY));
But there's one problem: How do you find NumberOfActiveProcessors? There is no interface to get this information in the device driver; ZwQuerySystemInformation is not available in device drivers. Are we dead?
We may be down, but we're not out. We can get the number of active processors in the user-mode by calling GetSystemInfo(). So the solution for setting a system thread affinity in the device driver would be to get the number-of-processors information from an application through an IOCTL (I/O control) and then use the code just shown. It's a hack, but it should work.
How do you handle passing ADODB.Recordsets from a VB MTS server component to a VC client? I have been absolutely stumped by this problem; the samples and documentation I have seen have helped demystify the inner workings of COM but there aren't very many that deal specifically with this issue.
The sample VC and VB apps I wrote work just fine when I pass data types such as integers and strings but for some reason I get an access violation error on the VC client side when I pass an ADO recordset.
Tony Nero
This is a little tricky indeed. The good doctor isn't going to reproduce your code here, but he noticed three common problems that even great programmers have. To save others the grief, he'll point them out and share them with everyone.
On the VB side, you have a function that returns an object declared on the stack. By the time the VC ATL client gets this object, it has already been destroyed and is therefore invalid. Be careful of this.
The next problem is one of those tricky COM reference counting problems: If you take a look at the .tlh file the #import generates for the COM server function that returns an ADO recordset, you will see that it actually returns a _RecordsetPtr smart pointer (templetized with com_ptr_t())
and not a _recordset *. Here is the code generated by #import:
_RecordsetPtr MyVb ( short * x);
This function returns a temporary RecordsetPtr. Unless you do an AddRef() on the encapsulated object, the destructor of the smart pointer will call Release() on the object and the object may destroy itself. That is why any calls to this object will result in an access violation. There are several tricks you can use to keep the object from being destroyed by the "smart" pointer's destructor. The neatest one is to use com_ptr_t::Detach().
Calling Detach() disassociates the smart pointer from the object without calling Release().
And there is one last problem: You asked for your object to be created as an in-process (or DLL) server by specifying CLSCTX_INPROC_SERVER when you created the object.
If you are using MTS, you are most likely using a local server and not an in-proc server even though your VB application was a DLL. You can see why if you look under HKEY_CLASSES_ROOT\CLSID\{YOURCLASSID} in the registry. Note that MTS changes the InprocServer32 settings to "free"-threaded and removes the path to your DLL COM server. Instead, it creates a new key called "LocalServer32" and puts a reference to mtx.exe, which is the MTS local server that creates your DLL object. MTS is impersonating your VB COM object. Because MTS is implemented as a local server, change your object createInstance() line to CLSCTX_LOCAL_SERVER (or better yet, CLSCTX_ALL).
I have a service that I created using ATL in VC 5.0. The service is basically empty, I haven't added any useful code yet. The difficulty that I am experiencing is related to the process's working set size. When the service loads the working set is around 1000K. Windows NT eventually reduces the size of the process to around 500K. This is still unacceptably large for a process that doesn't do anything. I've tried to reduce its footprint by using SetProcessWorkingSetSize. The linker options /stack:0x4, /HEAP:4, and /WS:aggressive were all equally ineffective. If you can shed some light on all of this I'd be most appreciative.
Joe Holt
Dr. GUI was a bit tired when he first read your question, so he was tempted to make some remark about how Congress has been spending its time in the early part of this year. But he won't.
Let's first make sure that we understand what the working set for a process is. An idealized definition is that the working set consists of all pages belonging to the process in physical RAM. These pages are available to the process and will not trigger a page fault when accessed. The working set consists of both private and shared pages. This includes the heap, stack, code, page tables, mapped data, and other information.
The /stack and /heap are not going to be effective because they only take up a small portion of the working set. The /WS:aggressive is similar to calling SetProcessWorkingSetSize(hProcess, -1, -1). This tells the operating system to reduce the working set to a minimum. The key to using this call effectively is calling it at the right time in your application. If you call it too early, any pages trimmed by the operating system will be reloaded when your application refers to them. If you call it too late, you will not let others take advantage of these unused pages.
In general, a service is waiting to respond to and process an event. If you closely examine the ATL service code, this processing is done in CServiceModule::Run(). Try making the API call within Run() right before the GetMessage() loop. By placing the call at this location, a good friend of the doctor's was able to reduce the size of an ATL service from 1,600 KB to 98 KB. This is a good place to trim the working set, because at this point only pages associated with the GetMessage() loop will be reloaded.
Doctor, I think I've been hallucinating for quite a long time now. What I read from the documentation stated that by #include-ing string.h as <cstring>, you put functions like strtok into the std namespace. I assume then that if we want to call it, we have to put std:: in front of it, like std::strcat, or std::strtok.
But then, VC++ (I use 6.0) complains that strcat is not a member of std!! Can you please help me, because I think I'm very near to total breakdown … the last doctor I met said that "it is just a false perception, induced, presumably, by constant exposure to C++."
Venus Lee
You're not hallucinating, you've just caught yourself a little bug. We goofed.
You can, of course, use the old-style #include <string.h>.
There is one other thing you can do—a temporary define:
#define BUG_HACK
and then use that symbol as the namespace name, as in:
BUG_HACK::strcat(...)
Once the bug's fixed, you could do a global search and replace to replace BUG_HACK
with std
.
I am converting a 16-bit application written in Visual C++ 1.5 to a 32-bit application for NT 4.0, using VC++ 5.0. The original program uses an API called MemoryRead (found in TOOLHELP.DLL) for reading global memory at a specific offset. I have not found an equivalent in TOOLHELP32.
Further, since my OS is NT 4.0, I have been warned against using this library. Is this call available? If not, is there another call I can use to get the same results?
Suman
First off, you need to understand why the old application was reading memory at a specific offset. The chances that the offset is still valid are very slim given the changes in addressing and so forth. Once you understand what the old program was up to, you can decide how to proceed.
Assuming that you really do want to read that particular memory, you'll need to change the API some, because Windows NT 4.0 does not have 32-bit ToolHelp APIs as do Windows 95 and 98. Windows NT 4.0 uses different ways to accomplish similar tasks to ToolHelp—ways that have been around since the dawn of Windows NT. However, if you can wait until Windows 2000, your prayers for 32-bit ToolHelp will be answered. Assuming that you can't (and neither can Dr. GUI), here's how you can do ToolHelp things in Windows NT 4.0:
First of all, please note that the memory architecture in Win32 is different from 16-bit Windows. For starters, there's no global address space shared by all processes (applications); each process gets its own private address space. If you have two or more processes that need to share memory, you must explicitly have them use file mappings (memory-mapped files) to create a section of shared memory.
That said, if you have one process that needs to read from another process's memory (the purpose of MemoryRead), you can use ReadProcessMemory(). However, you must first obtain a handle to the target process with OpenProcess. Then, when you're done, you must call CloseHandle on the process handle to avoid a leak. You may also need VirtualQueryEx to find what you're looking for in the other process's address space.
So, how does a process get the process ID required by OpenProcess for the other process in the first place? It must either spawn the other application, or it must enumerate the processes and find it. You might be thinking that finding the PID is a job for Process32First() and Process32Next(). But ToolHelp32 is not available on Windows NT, remember? Instead, you must use the Registry APIs to read performance counters from HKEY_PERFORMANCE_DATA. You look for the process object, then you can walk through all of the instances. When you find the ID of the process you want, you're in business.
The same method applies to enumerating threads and other objects. There's also the Performance Data Helper (PDH) library and the Process Status Helper API (PSAPI). All of these methods are explained in detail in the Platform SDK documentation in the MSDN Library Online and CD. In addition, Rick Anderson has written a set of articles about the Performance Data Helper API (See "Alerts Are Cheap Insurance" and "Your Right to Know, Part II" in the MSDN Library Online. These articles can also be found in the March/April 1999 issue of the MSDN News.)
Finally, I leave you with a bit of ToolHelp history. Originally, the ToolHelp debugging functions were provided in Windows 3.1 to help make 16-bit Windows debuggers easier to write. While they helped somewhat, they didn't make writing a debugger easy. When Windows 95 came along, it had the snazzy new Win32 debugging APIs (as provided on Windows NT), but didn't have Windows NT's performance counters for process, thread, and so on enumeration. Something had to be done for Windows 95, so ToolHelp32 was invented. The changes from the original 16-bit ToolHelp were needed to make ToolHelp32 work in a preemptive multitasking operating system.
We all remember Microsoft CrAPI (Cryptography API), but the current bee in my bonnet is CIM—should this be pronounced C.I.M., KIM, or SIM?
Kit Ruparel
Bees in your bonnet? Take the bonnet off. It's not Easter yet, anyway.
This tough question took a team of specialists using sophisticated techniques to answer. After sleuthing around the halls of building 24 for several hours, and ensuring we had enough vocalizations of this acronym to produce a statistically valid sample, we came to the conclusion that CIM is indeed pronounced "sim." CIM, for those of you who aren't up on the 2,050,467,329th TLA (or three-letter acronym) in use here at Microsoft, stands for Common Information Model, and is part of an industry-wide effort to provide a unified view of physical and logical objects in the managed environment. You'll see CIM referenced a lot with WBEM, MMC, and WMI, and you can find more information at http://www.microsoft.com/ntserver/management/default.asp.
And, no, Dr. GUI isn't going to claim that there are more TLAs than GUIDs—or, for that matter, particles in the universe.