Featured Post

Trie implementation in C

Component Object Model(COM) - Implementation in C++ -- Usage in C++, C#

As per Microsoft "Component Object Model or COM is a platform-independent, distributed, object-oriented system for creating binary software components that can interact".
COM defines a standard (Object Model) and the implementation part is left to the developer. These objects can communicate within a process or across the processes,  on the same or different machine with different programming languages.

Language requirement for COM :

1. Ability to create structures of pointers

2. Ability to call functions using pointers

Object-oriented languages such as C++ and Smalltalk provide programming mechanisms that simplify the implementation of COM objects, but languages such as C, Java, and VBScript can be used create and use COM objects.

Object's data is accessed using interfaces. The functions of this interface are called methods. The pointers to these interfaces enables to call the methods.

More information on COM is available at the MSDN .

In a nutshell , COM provides interfaces which can be implemented in different languages and can be used over distributed platforms.

To provide portability across platforms and programming languages, COM uses interface definition in platform-independent language, IDL(Interface Definition Language).

Steps involved to create COM interface:

  1. Define the interface/s in .idl file.
  2. Compile the .idl file using IDL compiler(platform and language specific) to generate language and platform specific code.
  3. Implement these interface/s in the specific language.
  4. Use it.


In our example, we are using MIDL compiler to generate the C++ specific code and DLL.
We will use ATL to make the implementation easier. ATL is the Active Template Library, a set of template-based C++ classes with which you can easily create small, fast Component Object Model (COM) objects. It has special support for key COM features including: stock implementations of IUnknown, IClassFactory, IClassFactory2, and IDispatch; dual interfaces; standard COM enumerator interfaces; connection points; tear-off interfaces; and ActiveX controls.

We are using Visual Studio 2005 for our example. Visual Studio provides ATL project wizard which will help a lot and make the process easy.

Steps to generate COM DLL:

  1. Create a new project in Visual Studio File->New->Project->Visual C++ -> ATL => ATL Project.
    New Project


  2. Give the project name as Calculator as we will be implementing a calculator interface using COM.
    ATL Project

  3. Choose DLL(Dynamic-link library) in the Application Settings -> Finish.

    Application Settings

  4. This structure will be visible once the project is created.

    Project Structure

  5. Now add a class following the image as below. Right click on the project Add->Class.

    Add COM Object

  6. We will now add a COM Object using ATL Simple Object.

    Add COM Object

  7. Give the name of class as CalculatorImpl (This defines the implentation of the interface). You can see under the panel C++ these four areas are updated -> Short Name, .h file, Class, .cpp file to be as CalculatorImpl, CalculatorImpl.h , CCalculatorImpl and CalculatorImpl.cpp respectively . Also in the panel COM below, these four areas are also updated - CoClass, Type, Interface and ProgID. Change these according to the image below.

    ATL Simple Object Wizard

  8. Choose the Options for the COM Object as below, then click on Finish.

    ATL Simple Object Wizard - Options

  9. After these changes, the Calculator.idl file will look like this:

    Calculator.idl




    Here, the declaration for interface ICalculator is added. ICalculator implements IDispatch interface. IDispatch is the interface that exposes the OLE Automation protocol. It is one of the standard interfaces exposed by COM. The IDispatch interface inherits from the IUnknown interface. More information on IDispatch can be found here. You can also see the declaration of the Type Library CalculatorLib which declares a coclass Calculator as well. This CoClass creates a COM object, which can implement a COM interface. The Type library information helps in creating .tlb file which is a binary file that stores information about a COM or DCOM object's properties and methods in a form that is accessible to other applications at runtime. Using a type library, an application or browser can determine which interfaces an object supports, and invoke an object's interface methods. This can occur even if the object and client applications were written in different programming languages. The COM/DCOM run-time environment can also use a type library to provide automatic cross-apartment, cross-process, and cross-machine marshaling for interfaces described in type libraries.
  10. Now our interface is ready to have some methods. Switch to Class View beside Solution Explorer as shown in image below. Right click on ICalculator interface and add a method. Add->Add Method.
    Add Methods

  11. Give the method name as Add , check parameter attributes as 'in', parameter type DOUBLE, parameter name as Input1. Click on Add. You should see the parameter being added to the list box. Similarly add one more input parameter named Input2. Now add an output parameter with parameter type DOUBLE*, parameter name as pOutput, check parameter attributes as 'out' and 'retval'. Click on Add. Click on Next->Finish.

    Add Method Wizard - Add Input

    Add Method Wizard - Add Output

    IDL Attributes

    Similarly add three more methods Subtract, Multiply and Divide.

  12. After adding the methods , you should see the functions' skeleton created in Calculator.idl and CalculatorImpl.cpp like this :

    Calculator.idl

    CalculatorImpl.cpp
  13. We will add the implementation in the skeleton.
  14. Now compile the solution. It will generate Calculator.dll in the project output directory (e.g. for 64 bit Release configuration it is %PROJECT_HOME%\Calculator\Calculator\x64\Release by default)
Now we have our COM DLL. To use it , we need some code.

Steps to create sample C++ code to test COM DLL:

  1. Create an Empty Win32 Console Project in Visual Studio

    Create Win32 Project

  2. Create an Empty cpp file in the project (TestCalc.cpp)

    Create Win32 Project - Application Settings

  3. Add the path to Calculator.h in Additional Include Directories in the project properties. Right click project -> Properties -> Configuration Properties -> C/C++ -> General.

    Add Additional Includes

  4. Add Calculator_i.c in the Project Source.

    Add _i.c file

  5. Populate TestCalc.cpp with the contents given later in the blog post.
  6. Run the executable(F5).

Note: One advantage using the Visual Studio to build our COM dll was that it registers the COM DLL by default. So we do not need to do it manually. This is the reason why we are able to run the test code even without the DLL being present in the System Path or adding it's path to PATH environment variable .

Now as we have been saying that the COM is interoperable between the languages/platform etc. Let's see some code in action.
We are going to use the COM DLL generated in C# application.

Before we go on using DLLs directly , let's be SMART and create .NET wrappers over COM DLL. Now you would ask what's that ??

The COM components has unmanaged code while .NET framework has managed code. Data types, method signatures, and error-handling mechanisms vary between managed and unmanaged object models
Code that executes under the control of the runtime is called managed code and the code that runs outside the runtime is called unmanaged code.

To simplify this, .NET wrappers are generated over existing COM components which allows unmanaged model to be converted to managed.
More information in this link.

Steps to create .NET wrapper from COM DLL:

Run this command from the output directory:

     tlbimp /machine: x64 Calculator.dll /out: Calculator_Wrapper.dll

It will generate Calculator_Wrapper.dll which is nothing but our .NET wrapper over COM.

Steps to create C# application to test COM DLL :


  1. Create a Windows Application TestCalcCCharp from Visual Studio

    Create Winform Application

  2. Add a reference to the wrapper Calculator_Wrapper.dll

    Add reference

  3. Create a simple form as per the code in the blog post.

    Winform

  4. Populate the functions to call the methods from COM.



// CalculatorImpl.cpp : Implementation of CCalculatorImpl

#include "stdafx.h"
#include "CalculatorImpl.h"

// CCalculatorImpl

STDMETHODIMP CCalculatorImpl::Add(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput)
{
 *pOutput = Input1 + Input2;

 return S_OK;
}

STDMETHODIMP CCalculatorImpl::Subtract(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput)
{
 *pOutput = Input1 - Input2;
 
 return S_OK;
}

STDMETHODIMP CCalculatorImpl::Multiply(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput)
{
 *pOutput = Input1 * Input2;

 return S_OK;
}

STDMETHODIMP CCalculatorImpl::Divide(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput)
{
 *pOutput = Input1 / Input2;

 return S_OK;
}




//TestCalc.cpp
#include "Calculator.h"
#include <iostream>
#include <stdexcept>
using std::runtime_error; 

int main()
{
 HRESULT hr ;
 ICalculator      *calc = NULL;

 hr = CoInitialize(0);

 if(SUCCEEDED(hr))
    {
  hr = CoCreateInstance( CLSID_Calculator, NULL, 
            CLSCTX_INPROC_SERVER,
   IID_ICalculator, (void**) &calc);

        // If we succeeded then call the Add 
        // method, if it failed
        // then display an appropriate message to the user.
        if(SUCCEEDED(hr))
        {
            double ReturnValue;
   double a = 4;
   double b = 0;
   calc->Add(a, b, &ReturnValue);
            std::cout << "The answer for "<<a<<" + "<<b<<" is: " 
                << ReturnValue << std::endl;
            calc->Release(); 
        }
        else
        {
            std::cout << "CoCreateInstance Failed." << std::endl;
        }
    }
    // Uninitialize COM
    CoUninitialize();

}



//Form1.Designer.cs
namespace TestCalcCSharp
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.calcPanel = new System.Windows.Forms.GroupBox();
            this.groupBox1 = new System.Windows.Forms.GroupBox();
            this.logTextBox = new System.Windows.Forms.RichTextBox();
            this.label5 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.label1 = new System.Windows.Forms.Label();
            this.divBtn = new System.Windows.Forms.Button();
            this.subBtn = new System.Windows.Forms.Button();
            this.addBtn = new System.Windows.Forms.Button();
            this.mulBtn = new System.Windows.Forms.Button();
            this.textBox3 = new System.Windows.Forms.TextBox();
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.label3 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.calcPanel.SuspendLayout();
            this.groupBox1.SuspendLayout();
            this.SuspendLayout();
            // 
            // calcPanel
            // 
            this.calcPanel.Controls.Add(this.groupBox1);
            this.calcPanel.Controls.Add(this.label5);
            this.calcPanel.Controls.Add(this.label4);
            this.calcPanel.Controls.Add(this.label1);
            this.calcPanel.Controls.Add(this.divBtn);
            this.calcPanel.Controls.Add(this.subBtn);
            this.calcPanel.Controls.Add(this.addBtn);
            this.calcPanel.Controls.Add(this.mulBtn);
            this.calcPanel.Controls.Add(this.textBox3);
            this.calcPanel.Controls.Add(this.textBox2);
            this.calcPanel.Controls.Add(this.label3);
            this.calcPanel.Controls.Add(this.label2);
            this.calcPanel.Controls.Add(this.textBox1);
            this.calcPanel.Location = new System.Drawing.Point(13, 13);
            this.calcPanel.Name = "calcPanel";
            this.calcPanel.Size = new System.Drawing.Size(450, 344);
            this.calcPanel.TabIndex = 0;
            this.calcPanel.TabStop = false;
            this.calcPanel.Text = "Calculator";
            // 
            // groupBox1
            // 
            this.groupBox1.Controls.Add(this.logTextBox);
            this.groupBox1.Location = new System.Drawing.Point(6, 238);
            this.groupBox1.Name = "groupBox1";
            this.groupBox1.Size = new System.Drawing.Size(438, 100);
            this.groupBox1.TabIndex = 34;
            this.groupBox1.TabStop = false;
            this.groupBox1.Text = "Logs";
            // 
            // logTextBox
            // 
            this.logTextBox.Location = new System.Drawing.Point(6, 19);
            this.logTextBox.Name = "logTextBox";
            this.logTextBox.Size = new System.Drawing.Size(432, 81);
            this.logTextBox.TabIndex = 33;
            this.logTextBox.Text = "";
            // 
            // label5
            // 
            this.label5.AutoSize = true;
            this.label5.Location = new System.Drawing.Point(254, 22);
            this.label5.Name = "label5";
            this.label5.Size = new System.Drawing.Size(37, 13);
            this.label5.TabIndex = 32;
            this.label5.Text = "Result";
            // 
            // label4
            // 
            this.label4.AutoSize = true;
            this.label4.Location = new System.Drawing.Point(163, 22);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(37, 13);
            this.label4.TabIndex = 31;
            this.label4.Text = "Input2";
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(83, 22);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(37, 13);
            this.label1.TabIndex = 30;
            this.label1.Text = "Input1";
            // 
            // divBtn
            // 
            this.divBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.divBtn.Location = new System.Drawing.Point(352, 134);
            this.divBtn.Name = "divBtn";
            this.divBtn.Size = new System.Drawing.Size(75, 36);
            this.divBtn.TabIndex = 29;
            this.divBtn.Text = "Divide";
            this.divBtn.UseVisualStyleBackColor = true;
            this.divBtn.Click += new System.EventHandler(this.divBtn_Click);
            // 
            // subBtn
            // 
            this.subBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.subBtn.Location = new System.Drawing.Point(125, 134);
            this.subBtn.Name = "subBtn";
            this.subBtn.Size = new System.Drawing.Size(75, 36);
            this.subBtn.TabIndex = 28;
            this.subBtn.Text = "Subtract";
            this.subBtn.UseVisualStyleBackColor = true;
            this.subBtn.Click += new System.EventHandler(this.subBtn_Click);
            // 
            // addBtn
            // 
            this.addBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.addBtn.Location = new System.Drawing.Point(14, 134);
            this.addBtn.Name = "addBtn";
            this.addBtn.Size = new System.Drawing.Size(75, 36);
            this.addBtn.TabIndex = 27;
            this.addBtn.Text = "Add";
            this.addBtn.UseVisualStyleBackColor = true;
            this.addBtn.Click += new System.EventHandler(this.addBtn_Click);
            // 
            // mulBtn
            // 
            this.mulBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.mulBtn.Location = new System.Drawing.Point(239, 134);
            this.mulBtn.Name = "mulBtn";
            this.mulBtn.Size = new System.Drawing.Size(75, 36);
            this.mulBtn.TabIndex = 26;
            this.mulBtn.Text = "Multiply";
            this.mulBtn.UseVisualStyleBackColor = true;
            this.mulBtn.Click += new System.EventHandler(this.mulBtn_Click);
            // 
            // textBox3
            // 
            this.textBox3.Enabled = false;
            this.textBox3.Location = new System.Drawing.Point(257, 41);
            this.textBox3.Name = "textBox3";
            this.textBox3.Size = new System.Drawing.Size(170, 20);
            this.textBox3.TabIndex = 7;
            // 
            // textBox2
            // 
            this.textBox2.Location = new System.Drawing.Point(166, 41);
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(57, 20);
            this.textBox2.TabIndex = 6;
            // 
            // label3
            // 
            this.label3.AutoSize = true;
            this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.label3.Location = new System.Drawing.Point(228, 44);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(16, 17);
            this.label3.TabIndex = 4;
            this.label3.Text = "=";
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.label2.Location = new System.Drawing.Point(146, 44);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(16, 17);
            this.label2.TabIndex = 3;
            this.label2.Text = "+";
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(83, 41);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(57, 20);
            this.textBox1.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(475, 369);
            this.Controls.Add(this.calcPanel);
            this.Name = "Form1";
            this.Text = "Calculator";
            this.calcPanel.ResumeLayout(false);
            this.calcPanel.PerformLayout();
            this.groupBox1.ResumeLayout(false);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.GroupBox calcPanel;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.TextBox textBox3;
        private System.Windows.Forms.TextBox textBox2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Button mulBtn;
        private System.Windows.Forms.Button divBtn;
        private System.Windows.Forms.Button subBtn;
        private System.Windows.Forms.Button addBtn;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.RichTextBox logTextBox;
        private System.Windows.Forms.GroupBox groupBox1;

    }
}




//Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Calculator_Wrapper;
using System.Runtime.InteropServices;

namespace TestCalcCSharp
{
    public partial class Form1 : Form
    {
        Calculator_Wrapper.Calculator calc = null;

        public Form1()
        {
            InitializeComponent();
            calc = new Calculator();
        }

        private void addBtn_Click(object sender, EventArgs e)
        {
            try
            {
                double inp1 = double.Parse(textBox1.Text);
                double inp2 = double.Parse(textBox2.Text);


                double output = calc.Add(inp1, inp2);

                textBox3.Text = output.ToString();
            }
            catch (Exception ex)
            {
                logTextBox.Text = ex.Message;
            }
        }

        private void subBtn_Click(object sender, EventArgs e)
        {
            try
            {
                double inp1 = double.Parse(textBox1.Text);
                double inp2 = double.Parse(textBox2.Text);


                double output = calc.Subtract(inp1, inp2);

                textBox3.Text = output.ToString();
            }
            catch (Exception ex)
            {
                logTextBox.Text = ex.Message;
            }
        }

        private void mulBtn_Click(object sender, EventArgs e)
        {
            try
            {
                double inp1 = double.Parse(textBox1.Text);
                double inp2 = double.Parse(textBox2.Text);

                double output = calc.Multiply(inp1, inp2);

                textBox3.Text = output.ToString();
            }
            catch (Exception ex)
            {
                logTextBox.Text = ex.Message;
            }
        }

        private void divBtn_Click(object sender, EventArgs e)
        {
            try
            {
                double inp1 = double.Parse(textBox1.Text);
                double inp2 = double.Parse(textBox2.Text);

                if (inp2 == 0) throw new DivideByZeroException();
                double output = calc.Divide(inp1, inp2);
                textBox3.Text = output.ToString();
            }
            catch (Exception ex)
            {
                logTextBox.Text = ex.Message + "\n";
            }
        }
    }
}


Comments

  1. Is it easy to integrate to my MFC screens?

    ReplyDelete
  2. Nice post, things explained in details. Thank You.

    ReplyDelete
  3. Your blog so awesome i loved it. We need more stuff so we can learn more. Thank you

    ReplyDelete
  4. Nice post, things explained in details. Thank You so much!!!

    ReplyDelete
  5. I love you man, I could finally use c++ functions in PHP

    ReplyDelete
  6. I was once stupid after reading your post. i became less stupid by 5%. Thank you for the knowledge.

    ReplyDelete

Post a Comment

Please post your valuable suggestions