Cómo: Crear un componente COM clásico mediante WRL

Se puede usar la biblioteca de plantillas de C++ (WRL) de Windows Runtime para crear componentes COM clásicos básicos y usarlos en las aplicaciones de escritorio, además de emplearlos en las aplicaciones de la Plataforma universal de Windows (UWP). En la creación de componentes COM, la biblioteca de plantillas de C++ de Windows Runtime puede requerir menos código que ATL. Para obtener información sobre un subconjunto de COM compatible con la biblioteca de plantillas de C++ de Windows Runtime, consulte Biblioteca de plantillas de C++ (WRL) de Windows Runtime.

En este documento se muestra cómo usar la biblioteca de plantillas de C++ de Windows Runtime para crear un componente COM básico. Aunque puede usar el mecanismo de implementación que mejor se adapte a sus necesidades, este documento también muestra una forma básica de registrar y usar el componente COM de una aplicación de escritorio.

Para usar la biblioteca de plantillas de C++ de Windows Runtime para crear un componente COM clásico básico

  1. En Visual Studio, cree un proyecto Solución en blanco. Asigne un nombre al proyecto, por ejemplo, WRLClassicCOM.

  2. Agregue un Proyecto Win32 a la solución. Asigne un nombre al proyecto, por ejemplo, CalculatorComponent. En la pestaña Configuración de la aplicación, seleccione DLL.

  3. Agregue un archivo Midl (.idl) al proyecto. Asigne un nombre al archivo, por ejemplo, CalculatorComponent.idl.

  4. Agregue este código a CalculatorComponent.idl:

    import "ocidl.idl";
    
    [uuid(0DBABB94-CE99-42F7-ACBD-E698B2332C60), version(1.0)] 
    interface ICalculatorComponent : IUnknown
    {
        HRESULT Add([in] int a, [in] int b, [out, retval] int* value);
    }
    
    [uuid(9D3E6826-CB8E-4D86-8B14-89F0D7EFCD01), version(1.0)]
    library CalculatorComponentLib
    {
        [uuid(E68F5EDD-6257-4E72-A10B-4067ED8E85F2), version(1.0)]
        coclass CalculatorComponent
        {
            [default] interface ICalculatorComponent;
        }
    };
    
  5. En CalculatorComponent.cpp, defina la clase CalculatorComponent. La clase CalculatorComponent hereda de Microsoft::WRL::RuntimeClass. Microsoft::WRL::RuntimeClassFlags< ClassicCom> especifica que la clase deriva de IUnknown y no de IInspectable. (IInspectable solo está disponible para componentes de aplicaciones de Windows Runtime). CoCreatableClass crea una fábrica para la clase que puede usarse con las funciones como CoCreateInstance.

    #include "pch.h" // Use stdafx.h in Visual Studio 2017 and earlier
    
    #include "CalculatorComponent_h.h"
    #include <wrl.h>
    
    using namespace Microsoft::WRL;
    
    class CalculatorComponent: public RuntimeClass<RuntimeClassFlags<ClassicCom>, ICalculatorComponent>
    {
    public:
        CalculatorComponent()
        {
        }
    
        STDMETHODIMP Add(_In_ int a, _In_ int b, _Out_ int* value)
        {
            *value = a + b;
            return S_OK;
        }
    };
    
    CoCreatableClass(CalculatorComponent);
    
  6. Use el código siguiente para reemplazar el código de dllmain.cpp. Este archivo define las funciones de exportación del archivo DLL. Estas funciones usan la clase Microsoft::WRL::Module para administrar las fábricas del módulo.

    #include "pch.h" // Use stdafx.h in Visual Studio 2017 and earlier
    #include <wrl\module.h>
    
    using namespace Microsoft::WRL;
    
    #if !defined(__WRL_CLASSIC_COM__)
    STDAPI DllGetActivationFactory(_In_ HSTRING activatibleClassId, _COM_Outptr_ IActivationFactory** factory)
    {
        return Module<InProc>::GetModule().GetActivationFactory(activatibleClassId, factory);
    }
    #endif
    
    #if !defined(__WRL_WINRT_STRICT__)
    STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv)
    {
        return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
    }
    #endif
    
    STDAPI DllCanUnloadNow()
    {
        return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
    }
    
    STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*)
    {
        if (reason == DLL_PROCESS_ATTACH)
        {
            DisableThreadLibraryCalls(hinst);
        }
        return TRUE;
    }
    
  7. Agregue un Archivo de definición de módulo (.def) al proyecto. Asigne un nombre al archivo, por ejemplo, CalculatorComponent.def. Este archivo proporciona al enlazador los nombres de las funciones que se exportarán. Abra el cuadro de diálogo Páginas de propiedades del proyecto, después en Propiedades de configuración>Enlazador>Entrada, establezca la propiedad Archivo de definición de módulo en el archivo DEF.

  8. Agregue este código a CalculatorComponent.def:

    LIBRARY
    
    EXPORTS
        DllGetActivationFactory PRIVATE
        DllGetClassObject       PRIVATE
        DllCanUnloadNow         PRIVATE
    
  9. Agregue runtimeobject.lib a la línea del enlazador. Para obtener información, consulte Archivos .Lib como entrada del enlazador.

Para usar el componente COM de una aplicación de escritorio

  1. Registre el componente COM con el Registro de Windows. Para ello, cree un archivo de entradas de registro, asígnele el nombre RegScript.reg y agregue el siguiente texto. Reemplace <dll-path> con la ruta de acceso del DLL, por ejemplo, C:\temp\WRLClassicCOM\Debug\CalculatorComponent.dll.

    Windows Registry Editor Version 5.00
    
    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{E68F5EDD-6257-4E72-A10B-4067ED8E85F2}]
    @="CalculatorComponent Class"
    
    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{E68F5EDD-6257-4E72-A10B-4067ED8E85F2}\InprocServer32]
    @="<dll-path>"
    "ThreadingModel"="Apartment"
    
    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{E68F5EDD-6257-4E72-A10B-4067ED8E85F2}\Programmable]
    
    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{E68F5EDD-6257-4E72-A10B-4067ED8E85F2}\TypeLib]
    @="{9D3E6826-CB8E-4D86-8B14-89F0D7EFCD01}"
    
    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{E68F5EDD-6257-4E72-A10B-4067ED8E85F2}\Version]
    @="1.0"
    
  2. Ejecute RegScript.reg o agréguelo al evento posterior a la compilación del proyecto. Para obtener más información, consulte el cuadro de diálogo Línea de comandos del evento anterior/posterior a la compilación.

  3. Agregue un proyecto Aplicación de consola Win32 a la solución. Asigne un nombre al proyecto, por ejemplo, Calculator.

  4. Use este código para reemplazar el contenido de Calculator.cpp:

    #include "pch.h" // Use stdafx.h in Visual Studio 2017 and earlier
    
    #include "..\CalculatorComponent\CalculatorComponent_h.h"
    
    const IID IID_ICalculatorComponent = {0x0DBABB94,0xCE99,0x42F7,0xAC,0xBD,0xE6,0x98,0xB2,0x33,0x2C,0x60};
    const CLSID CLSID_CalculatorComponent = {0xE68F5EDD,0x6257,0x4E72,0xA1,0x0B,0x40,0x67,0xED,0x8E,0x85,0xF2};
    
    // Prints an error string for the provided source code line and HRESULT
    // value and returns the HRESULT value as an int.
    int PrintError(unsigned int line, HRESULT hr)
    {
        wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr);
        return hr;
    }
    
    int wmain()
    {
        HRESULT hr;
    
        // Initialize the COM library.
        hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
        if (FAILED(hr))
        {
            return PrintError(__LINE__, hr);
        }
    
        ICalculatorComponent* calc = nullptr; // Interface to COM component.
    
        // Create the CalculatorComponent object.
        hr = CoCreateInstance(CLSID_CalculatorComponent, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&calc));
        if (SUCCEEDED(hr))
        {
            // Test the component by adding two numbers.
            int result;
            hr = calc->Add(4, 5, &result);
            if (FAILED(hr))
            {
                PrintError(__LINE__, hr);
            }
            else
            {
                wprintf_s(L"result = %d\n", result);
            }
    
            // Free the CalculatorComponent object.
            calc->Release();
        }
        else
        {
            // Object creation failed. Print a message.
            PrintError(__LINE__, hr);
        }
    
        // Free the COM library.
        CoUninitialize();
    
        return hr;
    }
    /* Output:
    result = 9
    */
    

Programación sólida

Este documento usa funciones COM estándar para demostrar que se puede usar la biblioteca de plantillas de C++ de Windows Runtime para crear un componente COM y que esté disponible para cualquier tecnología habilitada para COM. También se pueden usar los tipos de la biblioteca de plantillas de C++ de Windows Runtime como Microsoft::WRL::ComPtr en su aplicación de escritorio para administrar la duración de COM y de otros objetos. En el código siguiente se usa la biblioteca de plantillas de C++ de Windows Runtime para administrar la duración del puntero ICalculatorComponent. La clase CoInitializeWrapper es un contenedor RAII que garantiza que la biblioteca COM se libera y también que la duración de la biblioteca COM sobrevive al objeto de puntero inteligente ComPtr.

#include "pch.h" // Use stdafx.h in Visual Studio 2017 and earlier
#include <wrl.h>

#include "..\CalculatorComponent\CalculatorComponent_h.h"

using namespace Microsoft::WRL;

const IID IID_ICalculatorComponent = {0x0DBABB94,0xCE99,0x42F7,0xAC,0xBD,0xE6,0x98,0xB2,0x33,0x2C,0x60};
const CLSID CLSID_CalculatorComponent = {0xE68F5EDD,0x6257,0x4E72,0xA1,0x0B,0x40,0x67,0xED,0x8E,0x85,0xF2};

// Prints an error string for the provided source code line and HRESULT
// value and returns the HRESULT value as an int.
int PrintError(unsigned int line, HRESULT hr)
{
    wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr);
    return hr;
}

int wmain()
{
    HRESULT hr;

    // RAII wrapper for managing the lifetime of the COM library.
    class CoInitializeWrapper
    {
        HRESULT _hr;
    public:
        CoInitializeWrapper(DWORD flags)
        {
            _hr = CoInitializeEx(nullptr, flags);
        }
        ~CoInitializeWrapper()
        {
            if (SUCCEEDED(_hr))
            {
                CoUninitialize();
            }
        }
        operator HRESULT()
        {
            return _hr;
        }

    };

    // Initialize the COM library.
    CoInitializeWrapper initialize(COINIT_APARTMENTTHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }

    ComPtr<ICalculatorComponent> calc; // Interface to COM component.

    // Create the CalculatorComponent object.
    hr = CoCreateInstance(CLSID_CalculatorComponent, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(calc.GetAddressOf()));
    if (SUCCEEDED(hr))
    {
        // Test the component by adding two numbers.
        int result;
        hr = calc->Add(4, 5, &result);
        if (FAILED(hr))
        {
            return PrintError(__LINE__, hr);
        }
        wprintf_s(L"result = %d\n", result);
    }
    else
    {
        // Object creation failed. Print a message.
        return PrintError(__LINE__, hr);
    }

    return 0;
}

Consulte también

Biblioteca de plantillas C++ de Windows en tiempo de ejecución (WRL)