Step 4: Update the dll registration to let MC know about the plugin
We have two classes in our project. The JrmcInterfacePlugin, which is the COM server, and the JrmcPluginCtrl, which is the COM Control/UI.
We don't have to do much with the JrmcInterfacePlugin class, as ATL has done the bulk of the work for us. What we do have to implement is the additional code to tell J River about our plugin. So now that we are ready to modify some code, lets jump back to solution explorer and open JrmcInterfacePlugin.cpp.
You will already have a function for registering the COM server:
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
HRESULT hr = _AtlModule.DllRegisterServer();
return hr;
}
Modify it to look something like this:
STDAPI DllRegisterServer(void)
{
//FIX if not MC 16
static const TCHAR* REGISTRY_PATH_MJ_PLUGINS_INTERFACE = _T("Software\\JRiver\\Media Center 16\\Plugins\\Interface\\");
TCHAR cRegistryPath[1024];
HKEY hk;
DWORD dwDisp;
//FIX plugin name
wsprintf( cRegistryPath, _T("%s%s"), REGISTRY_PATH_MJ_PLUGINS_INTERFACE, _T("<PLUGIN NAME>") );
if ( RegCreateKeyEx( HKEY_LOCAL_MACHINE, cRegistryPath,
0, NULL, REG_OPTION_NON_VOLATILE,
KEY_WRITE, NULL, &hk, &dwDisp ) == ERROR_SUCCESS )
{
//FIX all 6 fields
DWORD nIVersion = 1;
DWORD nPluginMode = 1;
TCHAR* strCompany = _T( "My Company" );
TCHAR* strVersion = _T( "1.0.0" );
TCHAR* strURL = _T( "http://www.<MyWebsite>.com" );
TCHAR* strCopyright = _T( "Copyright (c) 2011, <ME>" );
LPWSTR strID;
//FIX CLSID
StringFromCLSID( CLSID_JrmcPluginCtrl, &strID );
RegSetValueExW( hk, _T("IVersion"), 0, REG_DWORD, (LPBYTE)&nIVersion, sizeof(DWORD) );
RegSetValueExW( hk, _T("PluginMode"), 0, REG_DWORD, (LPBYTE)&nPluginMode, sizeof(DWORD) );
RegSetValueExW( hk, _T("CLSID"), 0, REG_SZ, (LPBYTE)strID, _tcslen(strID)*sizeof(TCHAR) );
RegSetValueExW( hk, _T("Company"), 0, REG_SZ, (LPBYTE)strCompany, _tcslen(strCompany)*sizeof(TCHAR) );
RegSetValueExW( hk, _T("Version"), 0, REG_SZ, (LPBYTE)strVersion, _tcslen(strVersion)*sizeof(TCHAR) );
RegSetValueExW( hk, _T("URL"), 0, REG_SZ, (LPBYTE)strURL, _tcslen(strURL)*sizeof(TCHAR) );
RegSetValueExW( hk, _T("Copyright"), 0, REG_SZ, (LPBYTE)strCopyright, _tcslen(strCopyright)*sizeof(TCHAR) );
}
// registers object, typelib and all interfaces in typelib
HRESULT hr = _AtlModule.DllRegisterServer();
return hr;
}
Note, there are several places with //FIX comments. Those should be changed to match yourself/your plugin. The CLSID will be automatically provided by ATL, but the name will be based on the name of the control you added in Step 2.
At this point, your plugin should compile. Note, I need to build the Release version, otherwise I get an exception. If you've opened up Visual Studio (on Vista/Win7) as an administrator, it will register the server when you build. Otherwise it will fail the registration, and you will need to register manually. Of course we aren't done yet, but this is a nice sanity check.
Step 5: Add code for the interfaces and events
The rest of the work is in the JrmcPluginCtrl files (.h and .cpp). First off, since we will be using the J River automation pointer (otherwise, why are we writing a plugin?), we need to tell C++ about the classes. Add this code somewhere near the top of your header file, modified to point to your J River installation directory if it is different:
#import "C:\Program Files\J River\Media Center 16\Media Center 16.tlb" no_namespace, named_guids \
rename("DeleteFile","MCDeleteFile") \
rename("MoveFile","MCMoveFile") \
rename("ReportEvent","MCReportEvent")
Next (only if you added the optional event handling) you need to change the event template. The original code should look like this:
public IDispatchImpl<IMJAutomationEvents, &__uuidof(IMJAutomationEvents), &LIBID_MediaCenter, /* wMajor = */ 1>
you need to change it to this:
public IDispEventImpl<1,CJrmcPluginCtrl,&DIID_IMJAutomationEvents,&LIBID_MediaCenter, 1, 0>
This will be part of the class declaration for the control in your header file. Change CJrmcPluginCtrl to match the name you gave the control.
Next, in the COM_MAP section of the header file, you need to change:
COM_INTERFACE_ENTRY2(IDispatch, IMJAutomationEvents)
to
COM_INTERFACE_ENTRY(IDispatch)
and change
COM_INTERFACE_ENTRY(IMJAutomationEvents)
to
//COM_INTERFACE_ENTRY(IMJAutomationEvents)
as we will handle adding the event processing in the Init function.
Now find
BEGIN_SINK_MAP(CJrmcPliuginCtrl)
//Make sure the Event Handlers have __stdcall calling convention
END_SINK_MAP()
and add
SINK_ENTRY_EX(1, DIID_IMJAutomationEvents, 1, MJEvent)
between the BEGIN/END sections
The last change to the header file is to find the line:
STDMETHOD(Init)(IDispatch* pMJ);
and add
STDMETHOD_(void, MJEvent)(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2);
protected:
IMJAutomationPtr m_pMJ;
CComDispatchDriver spDrv;
after it. We use these variables later in the .cpp file. Note that we are using STDMETHOD_, not STDMETHOD (the underscore is intentional).
Now, jump into the JrmcPluginCtrl.cpp file. Given that I want to let Qt do the UI work, there is actually very little to do in the .cpp file. We just need to implement to two methods, Init and MJEvent. Depending on what you want to do, this would be where you would make your custom changes.
Here's what I do for Init:
STDMETHODIMP CJrmcPluginCtrl::Init(IDispatch* pMJ)
{
m_pMJ.Attach((IMJAutomation *) pMJ, TRUE);
HRESULT hr2 = DispEventAdvise(m_pMJ, &DIID_IMJAutomationEvents);
CAxWindow wnd;
wnd.Attach(m_hWnd);
HRESULT hr = wnd.CreateControl(OLESTR("{<Qt Control's GUID}"));
if (FAILED(hr))
AtlThrow(hr);
CComPtr<IDispatch> spDisp;
hr = wnd.QueryControl(&spDisp);
if (FAILED(hr))
AtlThrow(hr);
spDrv = CComDispatchDriver(spDisp);
CComVariant vIn2(pMJ);
hr = spDrv.Invoke1(L"setAuto",&vIn2);
if (FAILED(hr))
AtlThrow(hr);
return S_OK;
}
I need to attach the automation object in order to get the events from J River. I'm sure I could do this in Qt, since I also pass the automation pointer to Qt when I invoke the "setAuto" call. But since I want to leave ATL out of Qt, this seemed the easier. Since it is the responsibility of the server (in this case J River) to delete the pointed to object, I don't think using the pointer twice is a problem (once here and then from Qt). I independently create and register the Qt control, so all I need to get it to show up in J River is add the control's GUID to the CreateControl method. I don't need to generate any UI from Visual Studio, nor do I need to worry about messages - Qt will handle all of that. I just need to make sure the Qt control has a setAuto slot to take the m_pMJ pointer (and a slot to take the events, named "eventHandler", see below). spDrv is one of the variables I added to the class, it is used to pass events to Qt.
Here's the method that does that:
STDMETHODIMP_(void) CJrmcPliuginCtrl::MJEvent(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2)
{
CComVariant vParams[3] = {strParam2, strParam1, strType };
HRESULT hr = spDrv.InvokeN(L"eventHandler",vParams,3);
if (FAILED(hr))
AtlThrow(hr);
}
That's it. Everything else is handled by the Qt control. That will be the subject of a future post.
Of course, you could implement the control in ATL if you want.