COM Interop Part 1
COM Interop Part 1
COM Interop Part 1
COM Interop provides access to existing COM components without requiring that the original component be modified. When you want to incorporate COM code into a managed application, import the relevant COM types by using a COM Interop utility (TlbImp.exe) for that purpose. Once imported, the COM types are ready to use. In addition, COM Interop allows COM developers to access managed objects as easily as they access other COM objects. Again, COM Interop provides a specialized utility (RegAsm.exe) that exports the managed types into a type library and registers the managed component as a traditional COM component. At run time, the common language runtime marshals data between COM objects and managed objects as needed. This tutorial shows how to use C# to interoperate with COM objects. COM Interop Part 2: C# Server Tutorial covers using a C# server with a C++ COM client. For an overview of both tutorials, see COM Interop Tutorials.
Sample Files
See COM Interop Part 1 Sample to download and build the sample files discussed in this tutorial.
Further Reading
C# Attributes 17. Attributes Advanced COM Interop System.Runtime.InteropServices Namespace Type Library to Assembly Conversion Summary Type Library Importer (Tlbimp.exe) MSIL Disassembler (Ildasm.exe) Assembly Registration Tool (Regasm.exe) Interop Marshaling COM Interop Part 2: C# Server Tutorial
Tutorial
C# uses .NET Framework facilities to perform COM Interop. C# has support for: Creating COM objects. Determining if a COM interface is implemented by an object. Calling methods on COM interfaces. Implementing objects and interfaces that can be called by COM clients.
The .NET Framework handles reference-counting issues with COM Interop so there is no need to call or implement AddRef and Release. This tutorial covers the following topics: Creating a COM Class Wrapper Declaring a COM coclass Creating a COM Object Declaring a COM Interface Using Casts Instead of QueryInterface Putting It All Together
A great way to check the output of TlbImp is to run the .NET Framework SDK command-line tool Ildasm.exe (Microsoft Intermediate Language Disassembler) to view the result of the conversion. Although TlbImp is the preferred method for converting COM definitions to C#, it is not always possible to use it (for example, if there is no typelib for the COM definitions, or if TlbImp cannot handle the definitions in the typelib). In these cases, the alternative is to manually define the COM definitions in C# source code using C# attributes. Once you have created the C# source mapping, you simply compile the C# source code to produce the managed wrapper. The main attributes you need to understand to perform COM mapping are: ComImport - Marks a class as an externally implemented COM class. Guid Used to specify a universally unique identifier (UUID) for a class or an interface. InterfaceType specifies whether an interface derives from IUnknown or IDispatch. PreserveSig specifies whether the native return value should be converted from an HRESULT to a .NET Framework exception.
Each of these attributes is shown in the context of an actual example in this tutorial.
The following example declares a coclass in C#: // // declare FilgraphManager as a COM coclass // [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")] class FilgraphManager { } The C# compiler will add a parameterless constructor that you can call to create an instance of the COM coclass.
COM interfaces are represented in C# as interfaces with ComImport and Guid attributes. They cannot include any interfaces in their base interface list, and they must declare the interface member functions in the order that the methods appear in the COM interface. COM interfaces declared in C# must include declarations for all members of their base interfaces with the exception of members of IUnknownand IDispatch the .NET Framework automatically adds these. COM interfaces which derive from IDispatch must be marked with theInterfaceType attribute. When calling a COM interface method from C# code, the common language runtime must marshal the parameters and return values to/from the COM object. For every .NET Framework type, there is a default type that the common language runtime will use to marshal when marshaling across a COM call. For example, the default marshaling for C# string values is to the native type LPTSTR (pointer to TCHAR char buffer). You can override the default marshaling using the MarshalAs attribute in the C# declaration of the COM interface. In COM, a common way to return success or failure is to return an HRESULT and have an out parameter marked as "retval" in MIDL for the real return value of the method. In C# (and the .NET Framework), the standard way to indicate an error has occurred is to throw an exception. By default, the .NET Framework provides an automatic mapping between the two styles of exception handling for COM interface methods called by the .NET Framework. The return value changes to the signature of the parameter marked retval (void if the method has no parameter marked as retval). The parameter marked as retval is left off of the argument list of the method.
Any non-success return value will cause a System.COMException exception to be thrown. This example shows a COM interface declared in MIDL and the same interface declared in C# (note that the methods use the COM error-handling approach). Here is the original MIDL version of the interface: [ odl, uuid(56A868B1-0AD4-11CE-B03A-0020AF0BA770), helpstring("IMediaControl interface"), dual, oleautomation ] interface IMediaControl : IDispatch { [id(0x60020000)] HRESULT Run(); [id(0x60020001)]
HRESULT Pause(); [id(0x60020002)] HRESULT Stop(); [id(0x60020003)] HRESULT GetState( [in] long msTimeout, [out] long* pfs); [id(0x60020004)] HRESULT RenderFile([in] BSTR strFilename); [id(0x60020005)] HRESULT AddSourceFilter( [in] BSTR strFilename, [out] IDispatch** ppUnk); [id(0x60020006), propget] HRESULT FilterCollection([out, retval] IDispatch** ppUnk); [id(0x60020007), propget] HRESULT RegFilterCollection([out, retval] IDispatch** ppUnk); [id(0x60020008)] HRESULT StopWhenReady(); }; Here is the C# equivalent of this interface: using System.Runtime.InteropServices; // Declare IMediaControl as a COM interface which // derives from the IDispatch interface. [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"), InterfaceType(ComInterfaceType.InterfaceIsDual)] interface IMediaControl // cannot list any base interfaces here { // Note that the members of IUnknown and Interface are NOT // listed here // void Run(); void Pause(); void Stop(); void GetState( [In] int msTimeout, [Out] out int pfs); void RenderFile( [In, MarshalAs(UnmanagedType.BStr)] string strFilename); void AddSourceFilter( [In, MarshalAs(UnmanagedType.BStr)] string strFilename, [Out, MarshalAs(UnmanagedType.Interface)] out object ppUnk); [return : MarshalAs(UnmanagedType.Interface)] object FilterCollection(); [return : MarshalAs(UnmanagedType.Interface)] object RegFilterCollection();
void StopWhenReady(); } Note how the C# interface has mapped the error-handling cases. If the COM method returns an error, an exception will be raised on the C# side. To prevent the translation of HRESULTs to COMExceptions, attach the PreserveSig(true) attribute to the method in the C# declaration. For details, see PreserveSigAttribute Class.
Example 1: Using TlbImp This example shows you how to create an AVI viewer using TlbImp. The program reads an AVI filename from the command line, creates an instance of the Quartz COM object, then uses the methods RenderFile and Run to display the AVI file. These are the steps to build the program:
Run TlbImp over the TLB. The Media Player used in this example is contained in Quartz.dll, which should be in your Windows system directory. Use the following command to create the .NET Framework DLL: tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll Note that the resulting DLL needs to be named QuartzTypeLib, so the .NET Framework can load the containing types correctly when running.
You can use the Ildasm tool to examine the resulting DLL. For example, to display the contents of the file QuartzTypeLib.dll, use the following command: Ildasm QuartzTypeLib.dll Build the program using the C# compiler option /R to include the QuartzTypeLib.dll file.
You can then use the program to display a movie (an example movie to try is Clock.avi, which resides in your Windows directory). // interop1.cs // compile with: /R:QuartzTypeLib.dll using System; class MainClass { /************************************************************ Abstract: This method collects the file name of an AVI to show then creates an instance of the Quartz COM object. To show the AVI, the program calls RenderFile and Run on IMediaControl. Quartz uses its own thread and window to display the AVI.The main thread blocks on a ReadLine until the user presses ENTER. Input Parameters: the location of the AVI file it is going to display Returns: void **************************************************************/ public static void Main(string[] args) { // Check to see if the user passed in a filename if (args.Length != 1) { DisplayUsage(); return; } if (args[0] == "/?") { DisplayUsage(); return; } string filename = args[0]; // Check to see if the file exists if (!System.IO.File.Exists(filename)) { Console.WriteLine("File " + filename + " not found.");
DisplayUsage(); return; } // Create instance of Quartz // (Calls CoCreateInstance(E436EBB3-524F-11CE-9F530020AF0BA770, // NULL, CLSCTX_ALL, IID_IUnknown, &graphManager).): try { QuartzTypeLib.FilgraphManager graphManager = new QuartzTypeLib.FilgraphManager(); // QueryInterface for the IMediaControl interface: QuartzTypeLib.IMediaControl mc = (QuartzTypeLib.IMediaControl)graphManager; // Call some methods on a COM interface // Pass in file to RenderFile method on COM object. mc.RenderFile(filename); // Show file. mc.Run(); } catch(Exception ex) { Console.WriteLine("Unexpected COM exception: " + ex.Message); } // Wait for completion. Console.WriteLine("Press Enter to continue."); Console.ReadLine(); } private static void DisplayUsage() { // User did not provide enough parameters. // Display usage: Console.WriteLine("VideoPlayer: Plays AVI files."); Console.WriteLine("Usage: VIDEOPLAYER.EXE filename"); Console.WriteLine("where filename is the full path and"); Console.WriteLine("file name of the AVI to display."); } } Sample Run To display the example movie, Clock.avi, use the following command: interop1 %windir%\clock.avi This will display the movie on your screen after you press ENTER. Example 2: The C# Code Approach
This example uses the same Main method as Example 1, but instead of running TlbImp, it simply maps the Media Player COM object using C#. // interop2.cs using System; using System.Runtime.InteropServices; namespace QuartzTypeLib { // Declare IMediaControl as a COM interface which // derives from IDispatch interface: [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"), InterfaceType(ComInterfaceType.InterfaceIsDual)] interface IMediaControl // Cannot list any base interfaces here { // Note that IUnknown Interface members are NOT listed here: void Run(); void Pause(); void Stop(); void GetState( [In] int msTimeout, [Out] out int pfs); void RenderFile( [In, MarshalAs(UnmanagedType.BStr)] string strFilename); void AddSourceFilter( [In, MarshalAs(UnmanagedType.BStr)] string strFilename, [Out, MarshalAs(UnmanagedType.Interface)] out object ppUnk); [return: MarshalAs(UnmanagedType.Interface)] object FilterCollection(); [return: MarshalAs(UnmanagedType.Interface)] object RegFilterCollection(); void StopWhenReady(); } // Declare FilgraphManager as a COM coclass: [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")] class FilgraphManager // Cannot have a base class or // interface list here. { // Cannot have any members here // NOTE that the C# compiler will add a default constructor // for you (no parameters). } } class MainClass { /********************************************************** Abstract: This method collects the file name of an AVI to
show then creates an instance of the Quartz COM object. To show the AVI, the program calls RenderFile and Run on IMediaControl. Quartz uses its own thread and window to display the AVI.The main thread blocks on a ReadLine until the user presses ENTER. Input Parameters: the location of the AVI file it is going to display Returns: void *************************************************************/ public static void Main(string[] args) { // Check to see if the user passed in a filename: if (args.Length != 1) { DisplayUsage(); return; } if (args[0] == "/?") { DisplayUsage(); return; } String filename = args[0]; // Check to see if the file exists if (!System.IO.File.Exists(filename)) { Console.WriteLine("File " + filename + " not found."); DisplayUsage(); return; } // Create instance of Quartz // (Calls CoCreateInstance(E436EBB3-524F-11CE-9F530020AF0BA770, // NULL, CLSCTX_ALL, IID_IUnknown, // &graphManager).): try { QuartzTypeLib.FilgraphManager graphManager = new QuartzTypeLib.FilgraphManager(); // QueryInterface for the IMediaControl interface: QuartzTypeLib.IMediaControl mc = (QuartzTypeLib.IMediaControl)graphManager; // Call some methods on a COM interface. // Pass in file to RenderFile method on COM object. mc.RenderFile(filename); // Show file. mc.Run(); } catch(Exception ex) {
Console.WriteLine("Unexpected COM exception: " + ex.Message); } // Wait for completion. Console.WriteLine("Press Enter to continue."); Console.ReadLine(); } private static void DisplayUsage() { // User did not provide enough parameters. // Display usage. Console.WriteLine("VideoPlayer: Plays AVI files."); Console.WriteLine("Usage: VIDEOPLAYER.EXE filename"); Console.WriteLine("where filename is the full path and"); Console.WriteLine("file name of the AVI to display."); } } Sample Run To display the example movie, Clock.avi, use the following command: interop2 %windir%\clock.avi This will display the movie on your screen after you press ENTER.
To expose events from your class, you must declare them on the events interface and mark them with a DispId attribute. The class should not implement this interface. The class implements the class interface; it can implement more than one interface, but the first implementation will be the default class interface. Implement the methods and properties exposed to COM here. They must be marked public and must match the declarations in the class interface. Also, declare the events raised by the class here. They must be marked public and must match the declarations in the events interface.
Example
using System.Runtime.InteropServices; namespace project_name { [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")] public interface ComClass1Interface { } [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ComClass1Events { } [Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(ComClass1Events))] public class ComClass1 : ComClass1Interface { } }
Note
Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Visual Studio Settings.
Note
The following instructions demonstrate creating a deployment project using a Visual Basic project. The general principles apply to all Visual Studio language projects that support deploying Windows-based applications.
1. On the File menu, click New Project. 2. In the New Project dialog box, in the Project Types pane, select Visual 3.
Basic, and then click Windows Application in theTemplates pane. In the Name box, type My Notepad. Click OK to close the dialog box. The project is added to Solution Explorer, and the Windows Forms Designer opens.
4. Select the All Windows Forms tab in the Toolbox and drag a Button control onto the form.
5. Double-click the Button control to add an event handler for the button. In
the event handler, add the following code: 6. Shell("Notepad.exe", AppWinStyle.NormalFocus) This will start Notepad.exe and give it focus.
1. On the File menu, point to Add, and then click New Project. 2. In the Add New Project dialog box, in the Project Types pane, open
3.
the Other Project Types node, click Setup and Deployment Projects, click Visual Studio Installer, and then click Setup Project. In the Name box, type My Notepad Installer. Click OK to close the dialog box. The project is added to Solution Explorer, and the File System Editor opens.
Note
The ProductName property specifies the name that will be displayed for the application in folder names and in the Add or Remove Programs dialog box.
To add the Windows-based application to the installer
2. On the Project menu, point to Add, and then click Project Output. 3. In the Add Project Output Group dialog box, select My Notepad from
the Project list.
4. Select the Primary Output group from the list. In the Configuration box,
select (Active). Click OK to close the dialog box.
Note
You must have install permissions on the computer in order to run the installer.
1. Select the My Notepad Installer project in Solution Explorer. 2. In the File System Editor, select the Primary output from My 3.
Notepad node. On the Action menu, click Create Shortcut to Primary Output from My Notepad. This will add a node called Shortcut to Primary output from My Notepad.
4. Rename the shortcut Shortcut to My Notepad. 5. Select Shortcut to My Notepad and drag it to the User's Desktop folder in
the left pane.
3. Rename New Document Type #1 as Vbn.doc. 4. In the Properties window, set the Extensions property of the file type 5. 6.
to vbn. Select the Command property and click the ellipsis (...) button. In the Select Item in Project dialog box, navigate to theApplication Folder, and select Primary output from My Notepad. Click OK to close the dialog box.
This step adds a registry key and value to the registry. You can reference this registry key from your application's code to retrieve user-specific information at run time.
Note
The [Manufacturer] node is enclosed in brackets to indicate that it is a property. It will be replaced by the value entered for theManufacturer property for the deployment project. 3. 4. 5. 6. 7.
On the Action menu, point to New, and then click Key. Rename the key UserChoice and select it. On the Action menu, point to New, and then click String Value. Rename the string value TextColor. In the Properties window, select the Value property and enter Black.
the View menu, point to Editor, and click User Interface. TheUser Interface Editor appears. In the User Interface Editor, select the Start node (under the Install node). On the Action menu, click Add Dialog. In the Add Dialog dialog box, select Checkboxes (A). Click OK to close the dialog box. Right-click the Checkboxes (A) dialog box and select Move Up two times to position it above the Installation Folder dialog box. In the Properties window, set the BannerText property to Samples. Set the BodyText property to the following: The Install Samples check box controls whether the sample files are installed. If left unselected, the samples will not be installed.
9. Set the CheckBox1Label property to Install samples? 10. Set the properties Checkbox2Visible, Checkbox3Visible,
and Checkbox4Visible to False. This will hide the additional check boxes.
This step creates two sample text files that will be installed if the user chooses the Install samples option in the custom dialog box.
1. Using Notepad or another text editor, create a text file that contains the
text This is rules.vbn. Save it as Rules.vbn.
Note
To prevent Notepad from automatically adding a .txt extension, select All Files in the Files of type list. 2. Create another text file that contains the text This is memo.vbn. Save it
as Memo.vbn. This step adds the sample files to the Samples folder, and sets a condition that determines whether to install the files.
the View menu, point to Editor, and then click Launch Conditions.
Note
This step is intended only to demonstrate the concept of launch conditions; the My Notepad application has no actual dependency on Internet Explorer. 2. In the Launch Conditions Editor, select the Requirements on Target 3.
Machine node. On the Action menu, click Add File Launch Condition. A Search for File1 node is added beneath the Search Target Machine node, and a Condition1 node is added beneath the Launch Conditions node.
4. Rename Search for File1 to Search for Internet Explorer. 5. In the Properties window, set the FileName property to Iexplore.exe, 6. 7.
the Folder property to [ProgramFilesFolder], the Depthproperty to 2, and the MinVersion property to 5.00. Select the Condition1 node. Set the Message property to the following: This program requires Microsoft Internet Explorer 5.0 or later. Please install Internet Explorer and rerun the Notepad installer.
4.
Note
You must have install permissions on the computer in order to run the installer.
Deploying to Another Computer
This step will run the installer and install My Notepad on another computer.
1. In Windows Explorer, navigate to your project directory and find the built
installer. The default path will be \Documents and Settings\yourloginname\My Documents\Visual Studio 9.0\Projects\Solution Folder Name\My Notepad Installer\project configuration\My Notepad Installer.msi. (The default project configuration is either Debug or Release.) 2. Copy My Notepad Installer.msi, Setup.exe, and all other files and subdirectories in the directory to another computer.
Note
To install on a computer that is not on a network, copy the files to traditional media such as CD-ROM.
3. On the target computer, double-click Setup.exe to run the installer.
Note
You must have install permissions on the target computer in order to run the installer.
Testing
This step will test starting the application from a desktop shortcut and will also test uninstalling it.
Note
To uninstall the application from your development computer, on the Project menu, click Uninstall.
3. The icon should be removed from the desktop and the installed application files and folders should be deleted from your computer.
Note
The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Working with Settings.
To create a new deployment project
1. On the File menu, point to Add, then click New Project. 2. In the resulting Add New Project dialog box, in the Project Types pane, open the Other Project Types node, open Setup and Deployment Projects, and select Visual Studio Installer. 3. In the Templates pane, choose the type of deployment project you want
to create. For more information, see Setup and Deployment Projects.
1. On the File menu, point to Add, then click Existing Project. 2. In the resulting Add Existing Project dialog box, browse to the location of the deployment project and click Open.
Tip
To see only deployment projects, select Setup and Deployment Projects in the Files of type box.
Note
The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Working with Settings.
To add a project output or file to a deployment project
1. Open the File System Editor. For more information, see How to: Open
the Deployment Editors. 2. Select a folder on the target computer where the item will be installed.
Note
You can also create new folders on the target computer. For more information, see How to: Add and Remove Folders in the File System Editor. 3. On the Action menu, point to Add, and then click Project Output or click File. In the resulting dialog box, select the item that you want to add.
Note
You can also add items to a deployment project by right-clicking the project node in Solution Explorer. Any items added in this manner will be placed in the default folder for standard applications the Application folder is the default; for Web applications theWeb Application folder is the default. You can then move the items to another folder.
1. Select the deployment project in Solution Explorer. 2. On the Project menu, point to Add, and then click Merge Module or click Assembly. In the resulting dialog box, select the item that you want
to add.