|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article demonstrates a subtle problem that can occur when using ActiveX controls in a managed application. I first ran into this problem while using a third party ActiveX control in one of my own C# programs. Occasionally, when I attempted to access one of my unmanaged resources via a COM object, I found that it was in a locked state. Based on former experience with the control, the components, and several unmanaged VB6 programs that used them, I knew that the existing unmanaged code was unlikely to be the cause of the problem. In fact, I wrote several test programs in VB6 to try and reproduce the problem, but the problem did not show up until I moved to a C# Windows Forms application. After careful investigation, I found that extra references where being held on the COM objects by the .NET runtime callable wrappers (RCW) for the objects. This occurred even though I never directly accessed the COM object in the managed .NET code. The demo project included with this article demonstrates when the problem occurs, and some workarounds. BackgroundThis article assumes that you have basic familiarity with ActiveX controls. In addition, it is assumed that you understand how to create and work with a simple C# Windows Forms application that uses an ActiveX control. Although the ActiveX control included with the demo project is written in C++ using ATL, the underlying code is kept as simple as possible. The Demo ProjectThe project included with this article gives a simple example that demonstrates the problem. The project consists of three pieces:
The managed C# program, ClientApp, uses the ActiveX control When the viewer is opened, it creates an instance of a To keep the logic simple, the viewer does not attempt to read the existing file contents when opened. This logic could be added, but it does not affect the concepts I'm demonstrating here. Whenever the user types into the edit control, the __interface _IDataViewerEvents
{
[id(1), helpstring("method DataChanged")]
HRESULT DataChanged([in] IDispatch* newVal);
};
When the viewer is closed, it releases its reference on the underlying To build the project, you'll need Visual Studio 2005. Open the BuildAll project and rebuild the entire solution. This will automatically build and register the COM component, the ActiveX control, and the C# application. You can then run the ClientApp application. This project was built and tested on Windows XP Service Pack 2. Demonstrating the ProblemTo see the problem in action, start up the client application and select "Open" to open a file. Type something into the edit control and click on the "Close" button. Then, quickly attempt to open the same file again. Every so often, you'll get an error as shown in the figure below. You won't see the error every time, so try a few times if you don't see it immediately. Remember to type into the edit control each time, or the ActiveX control data change events will not be fired.
When this error occurs, it's an indication that the underlying file could not be reopened. The question is, why can't it be opened? What's locking the file? Tracking Down the ProblemBy using the Sysinternals.com "Process Explorer" tool, I quickly realized that the file was not always closed when the "Close" button was pressed. Sometimes the file would remain open for several seconds after "Close". Turning to Process Explorer once again, it was apparent that the file was not closed until the application went through an additional .NET garbage collection. Clearly, the .NET runtime was creating a reference to the underlying Armed with this information, I tried to figure out what could be causing the reference to the underlying data object. The object was not used or referenced anywhere in the managed code. Eventually, I focused in on the data changed event. This was the only place where the C# code could interact with the underlying After further testing, I concluded that it didn't matter that there was no explicit event handler. The Windows Forms application must be hooking up to the event source, even though my application didn't use the event. I verified this by explicitly adding an event handler with code to free the COM object. The event handler routine is shown below: /// This routine is called whenever the data
/// is changed in the Active X viewer. This occurs whenever
/// the user types into the edit control.
///
/// Arguments:
/// sender - The ActiveX control object
/// e - The event object (which contains
/// the CDataInfo object reference)
private void CDataViewer_DataChanged(object sender,
AxInfoSource._IDataViewerEvents_DataChangedEvent e)
{
// In this event handler,
// e.newVal is the object pointer for
// the CDataInfo object from the ActiveX control.
// If desired, use the object to get
// its data contents (what the user typed)
// string str = ((CDataInfo)e.newVal).DataContents;
// Decrement the reference count
// on the runtime callable wrapper.
// If we don't do this, the object
// will linger and the underlying file object
// will remain open until a garbage collection occurs.
int i = System.Runtime.InteropServices.
Marshal.ReleaseComObject(e.newVal);
}
With the In the demo project, you can experiment with the different scenarios by using the checkboxes for the data-changed event handler. You can also force a .NET garbage collection by using the "Force GC" button. The Technical DetailsOnce I understood the problem, I decided to investigate further to understand the cause. Since the problem was clearly occurring in the interop layer between the ActiveX control and the .NET form, I modified the projects to explicitly create the interop assemblies using Aximp.exe instead of using the Toolbox in Visual Studio. Aximp.exe is a utility that generates interop assemblies for ActiveX controls. It is included with Visual Studio. One of the benefits of using Aximp.exe is that you have the option of generating the C# source code for the Windows Forms wrapper. The demo project makes use of Aximp.exe. With the source code and a debugger, it's easy to see what's happening. Use the debugger to put a breakpoint in the functions The code to determine whether or not to forward the events to a .NET client is in the Since the COM events are always handled, even when they are not forwarded to .NET clients, a runtime callable wrapper (RCW) is used to manage the The figure below illustrates the concept:
The The Some WorkaroundsLet's consider some options to work around the problem.
Obviously, options 3 and 4 are not always practical. If you are using a third party control without access to the source, code modification is impossible. ConclusionThis article demonstrated how ActiveX events in Windows Forms applications can cause COM objects to linger while waiting for a .NET garbage collection. Obviously, this situation does not apply to all ActiveX controls. However, if your ActiveX control exposes events to .NET clients, and those events pass along COM objects that lock critical resources, you might find yourself tracking down the same problem presented in this article. As mentioned above, you might encounter this problem even if you have not explicitly hooked up an event handler for the control. Hopefully, the concepts presented in this article will help you to understand the problem and avoid it in your own code. History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||