all groups > dotnet interop > february 2006 >
You're in the

dotnet interop

group:

Unmanaged C++ Shell Extension Handler as client to C# COM Object


Unmanaged C++ Shell Extension Handler as client to C# COM Object gilad
2/28/2006 11:00:31 AM
dotnet interop: Hi, I am trying to get an unmanaged C++ Shell Extension Handler to act
as a COM client to a C# COM object. Since it is recommended to not do
managed Shell Extension Handlers--

http://blogs.msdn.com/junfeng/archive/2005/11/18/494572.aspx

--and since I prefer to program as much in C# as I can, I came up with
the idea to create the shell handler in C++, and register a C# COM
object that the handler can call to do the rest of work.

I've successfully created the C# COM object, and tested it with an
unmanaged C++ executable acting as the COM client (importing the .TLB, etc).

Now that I've incorporated it into my handler code, the "FAILED(hr)"
test after CoCreateInstance() happens. Is there something I'm
missing? Here is what I've got, in abbreviated form:

The C# COM object is simple and exposes a single method whose signature is


void ShowDialog(string sText);


The C++ handler/COM client header file is


#pragma once
#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_ATTRIBUTES
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <atlwin.h>
#include <atltypes.h>
#include <atlctl.h>
#include <atlhost.h>

// Shell features

#include <shlobj.h>



// Here is where I import the Type Libraries,
// including the C# COM TLB.

#import <mscorlib.tlb> raw_interfaces_only

#import "CSharpCOMWin.tlb" no_namespace named_guids



using namespace ATL;




// The module attribute is specified in order to implement DllMain,
// DllRegisterServer and DllUnregisterServer
[ module(dll, name = "MyShellHandler", helpstring = "MyShellHandler 1.0
Type Library") ];
[ emitidl ];




// CMyShellHandler object declaration
[
coclass,
com_interface_entry ("COM_INTERFACE_ENTRY_IID(IID_IContextMenu,
IContextMenu)"),
threading("apartment"),
vi_progid("MyShellHandler.TagSet"),
progid("MyShellHandler.TagSet.1"),
version(1.0),
uuid("8D1733BD-1AB4-4e26-85A8-CFD39C003C3D"),
helpstring("MyShellHandler Class")
]
class ATL_NO_VTABLE CMyShellHandler :
public IShellExtInit,
public IContextMenu
{
public:

LPITEMIDLIST m_pIDFolder; //The folder's PIDL
TCHAR m_szFile[MAX_PATH]; //The file name
IDataObject *m_pDataObj; //The IDataObject pointer
HKEY m_hRegKey; //The file or folder registry key


// Constructor

CMyShellHandler()
{ }



DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{ }


// IShellExtInit
STDMETHOD(Initialize)(
LPCITEMIDLIST pIDFolder, IDataObject *pDataObj, HKEY hRegKey );


// IContextMenu
STDMETHOD(GetCommandString)(
UINT_PTR idCmd, UINT uFlags, UINT *pwReserved,
LPSTR pszName, UINT cchMax );

STDMETHOD(InvokeCommand)( LPCMINVOKECOMMANDINFO pici );

STDMETHOD(QueryContextMenu)(
HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
UINT idCmdLast, UINT uFlags );


};



Then, in the "InvokeCommand()" method of the handler, I have the
following code, which *should* send the C# COM object the path of the
file that has been right-clicked, but instead fails:



STDMETHODIMP
CMyShellHandler::InvokeCommand ( LPCMINVOKECOMMANDINFO pici )
{

// If lpVerb really points to a string, ignore this
// function call and bail out.

if ( 0 != HIWORD( pici->lpVerb ))
return E_INVALIDARG;

// Get the command index - the only valid one is 0.

switch ( LOWORD( pici->lpVerb ))
{
case 0:
{

// Message box test...
TCHAR szMsg [MAX_PATH + 32];


// instantiate my C# COM object

ICSharpCOM *cpi = NULL;


// Initialize COM and create an instance of the
// InterfaceImplementation class:

CoInitialize(NULL);
HRESULT hr = CoCreateInstance(
CLSID_CSharpCOMWin,
NULL, CLSCTX_INPROC_SERVER,
IID_ICSharpCOM, reinterpret_cast<void**>(&cpi));


// This error occurs when the handler is executed

if (FAILED(hr))
{
MessageBox (NULL, "FAILED(hr)" , "err", 0);
}
else
{

// This is what I want to happen:
// (m_szFile is gotten from DragQueryFile() in the Initialize() method)
// of the handler)

cpi->ShowDialog(m_szFile);


cpi->Release();
cpi = NULL;
}

// Be a good citizen and clean up COM:
CoUninitialize();

return S_OK;
}
break;

default:
return E_INVALIDARG;
break;
}


}


The above code is essentially the same as what I did in my initial test
(which worked), so I don't know what might be the problem. It compiles
without problems, registers fine, everything. Any help would be appreciated.

Thanks,

Re: Unmanaged C++ Shell Extension Handler as client to C# COM Object gilad
2/28/2006 2:38:57 PM
[quoted text, click to view]

I don't understand all the interaction here then. The C# COM DLL is a
binary sitting on a filesystem. The unmanaged C++ handler DLL is a
separate binary that gets loaded into the shell. By my estimation the C#
COM object shouldn't get loaded until a user performs the right-click
and selects the menu item.

In the C++ code the InvokeCommand() method has to fire before my C# COM
object is even called. The Initialize() and QueryContextMenu(), which
fire when the shell loads (setting up the menu item for right-click,
etc), make no calls to the C# COM at all. It seems to me that my C# code
has not been inserted into the shell, but a reference to it has.

Are you saying that a COM client forces the COM object to load the
moment that it loads?

Like I said, I must be missing something here.

Re: Unmanaged C++ Shell Extension Handler as client to C# COM Object gilad
2/28/2006 2:43:48 PM
This is what initially got me thinking that my route would work:

Jesse, assuming one did not need the shell extension to be loaded into
non-Explorer processes, one could always write an unmanaged COM shim (as
is commonly done for managed Office add-ins) that would check that the
loading process is EXPLORER.EXE before loading the actual (managed)
extension, right ?

(From http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=125283&SiteID=1)

If I made sure the unmanaged code checked for this, would it work?

JA



[quoted text, click to view]
Re: Unmanaged C++ Shell Extension Handler as client to C# COM Object Willy Denoyette [MVP]
2/28/2006 9:37:47 PM
The point is that you should not introduce the CLR into the shell, what you
are doing is exactly that, the fact that you call C# code through COM
interop or not doesn't matter, you load the CLR into the shell which is what
you shouln't do.

As the blog says:
Unfortunately unmanaged C++ is really the only way to go here.

Willy.

[quoted text, click to view]
| Hi, I am trying to get an unmanaged C++ Shell Extension Handler to act
| as a COM client to a C# COM object. Since it is recommended to not do
| managed Shell Extension Handlers--
|
| http://blogs.msdn.com/junfeng/archive/2005/11/18/494572.aspx
|
| --and since I prefer to program as much in C# as I can, I came up with
| the idea to create the shell handler in C++, and register a C# COM
| object that the handler can call to do the rest of work.
|
| I've successfully created the C# COM object, and tested it with an
| unmanaged C++ executable acting as the COM client (importing the .TLB,
etc).
|
| Now that I've incorporated it into my handler code, the "FAILED(hr)"
| test after CoCreateInstance() happens. Is there something I'm
| missing? Here is what I've got, in abbreviated form:
|
| The C# COM object is simple and exposes a single method whose signature is
|
|
| void ShowDialog(string sText);
|
|
| The C++ handler/COM client header file is
|
|
| #pragma once
| #define STRICT
| #ifndef _WIN32_WINNT
| #define _WIN32_WINNT 0x0400
| #endif
| #define _ATL_ATTRIBUTES
| #define _ATL_APARTMENT_THREADED
| #define _ATL_NO_AUTOMATIC_NAMESPACE
| #include <atlbase.h>
| #include <atlcom.h>
| #include <atlwin.h>
| #include <atltypes.h>
| #include <atlctl.h>
| #include <atlhost.h>
|
| // Shell features
|
| #include <shlobj.h>
|
|
|
| // Here is where I import the Type Libraries,
| // including the C# COM TLB.
|
| #import <mscorlib.tlb> raw_interfaces_only
|
| #import "CSharpCOMWin.tlb" no_namespace named_guids
|
|
|
| using namespace ATL;
|
|
|
|
| // The module attribute is specified in order to implement DllMain,
| // DllRegisterServer and DllUnregisterServer
| [ module(dll, name = "MyShellHandler", helpstring = "MyShellHandler 1.0
| Type Library") ];
| [ emitidl ];
|
|
|
|
| // CMyShellHandler object declaration
| [
| coclass,
| com_interface_entry ("COM_INTERFACE_ENTRY_IID(IID_IContextMenu,
| IContextMenu)"),
| threading("apartment"),
| vi_progid("MyShellHandler.TagSet"),
| progid("MyShellHandler.TagSet.1"),
| version(1.0),
| uuid("8D1733BD-1AB4-4e26-85A8-CFD39C003C3D"),
| helpstring("MyShellHandler Class")
| ]
| class ATL_NO_VTABLE CMyShellHandler :
| public IShellExtInit,
| public IContextMenu
| {
| public:
|
| LPITEMIDLIST m_pIDFolder; //The folder's PIDL
| TCHAR m_szFile[MAX_PATH]; //The file name
| IDataObject *m_pDataObj; //The IDataObject pointer
| HKEY m_hRegKey; //The file or folder registry key
|
|
| // Constructor
|
| CMyShellHandler()
| { }
|
|
|
| DECLARE_PROTECT_FINAL_CONSTRUCT()
|
| HRESULT FinalConstruct()
| {
| return S_OK;
| }
|
| void FinalRelease()
| { }
|
|
| // IShellExtInit
| STDMETHOD(Initialize)(
| LPCITEMIDLIST pIDFolder, IDataObject *pDataObj, HKEY hRegKey );
|
|
| // IContextMenu
| STDMETHOD(GetCommandString)(
| UINT_PTR idCmd, UINT uFlags, UINT *pwReserved,
| LPSTR pszName, UINT cchMax );
|
| STDMETHOD(InvokeCommand)( LPCMINVOKECOMMANDINFO pici );
|
| STDMETHOD(QueryContextMenu)(
| HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
| UINT idCmdLast, UINT uFlags );
|
|
| };
|
|
|
| Then, in the "InvokeCommand()" method of the handler, I have the
| following code, which *should* send the C# COM object the path of the
| file that has been right-clicked, but instead fails:
|
|
|
| STDMETHODIMP
| CMyShellHandler::InvokeCommand ( LPCMINVOKECOMMANDINFO pici )
| {
|
| // If lpVerb really points to a string, ignore this
| // function call and bail out.
|
| if ( 0 != HIWORD( pici->lpVerb ))
| return E_INVALIDARG;
|
| // Get the command index - the only valid one is 0.
|
| switch ( LOWORD( pici->lpVerb ))
| {
| case 0:
| {
|
| // Message box test...
| TCHAR szMsg [MAX_PATH + 32];
|
|
| // instantiate my C# COM object
|
| ICSharpCOM *cpi = NULL;
|
|
| // Initialize COM and create an instance of the
| // InterfaceImplementation class:
|
| CoInitialize(NULL);
| HRESULT hr = CoCreateInstance(
| CLSID_CSharpCOMWin,
| NULL, CLSCTX_INPROC_SERVER,
| IID_ICSharpCOM, reinterpret_cast<void**>(&cpi));
|
|
| // This error occurs when the handler is executed
|
| if (FAILED(hr))
| {
| MessageBox (NULL, "FAILED(hr)" , "err", 0);
| }
| else
| {
|
| // This is what I want to happen:
| // (m_szFile is gotten from DragQueryFile() in the Initialize() method)
| // of the handler)
|
| cpi->ShowDialog(m_szFile);
|
|
| cpi->Release();
| cpi = NULL;
| }
|
| // Be a good citizen and clean up COM:
| CoUninitialize();
|
| return S_OK;
| }
| break;
|
| default:
| return E_INVALIDARG;
| break;
| }
|
|
| }
|
|
| The above code is essentially the same as what I did in my initial test
| (which worked), so I don't know what might be the problem. It compiles
| without problems, registers fine, everything. Any help would be
appreciated.
|
| Thanks,
|
| JA

Re: Unmanaged C++ Shell Extension Handler as client to C# COM Object Willy Denoyette [MVP]
3/1/2006 12:10:47 AM
No, I'm afraid it won't, this is not about other processes loading shell
extensions, it's about loading the framework and the FileOpen dialog.
Whenever you load managed code into the shell (explorer.exe), you are
effectively loading the CLR and the framework dll used by the extension,
more exactly you are loading the version of the CLR and framework libraries
used to build your extension. Now, once you have your extension loaded (in
explorer.exe) every process (managed or unmanaged!)that uses the FileOpen
dialog will load the same version of the CLR and the framework library used
by your extension, this is because the FileOpen dialog now is depending on
the CLR and the framework libraries used by the extension. This is done
automatically, you can't prevent this to happen.
That mean that every process now loads the CLR, this produces memory
overhead, while unmanaged process don't need the CLR to be loaded at all.
And it can possible prevent other managed applications build against a
different version of the framework to run or to run without failures, or it
can possibly crash the shell when an another managed applications (built
against a different version of the framework) has previously opened the
FileOpen dialog before the shell has loaded the extension.

Willy.




[quoted text, click to view]
| This is what initially got me thinking that my route would work:
|
| Jesse, assuming one did not need the shell extension to be loaded into
| non-Explorer processes, one could always write an unmanaged COM shim (as
| is commonly done for managed Office add-ins) that would check that the
| loading process is EXPLORER.EXE before loading the actual (managed)
| extension, right ?
|
| (From
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=125283&SiteID=1)
|
| If I made sure the unmanaged code checked for this, would it work?
|
| JA
|
|
|
[quoted text, click to view]
| >
| >> The point is that you should not introduce the CLR into the shell,
| >> what you are doing is exactly that, the fact that you call C# code
| >> through COM interop or not doesn't matter, you load the CLR into the
| >> shell which is what you shouln't do.
| >
| >
| > I don't understand all the interaction here then. The C# COM DLL is a
| > binary sitting on a filesystem. The unmanaged C++ handler DLL is a
| > separate binary that gets loaded into the shell. By my estimation the C#
| > COM object shouldn't get loaded until a user performs the right-click
| > and selects the menu item.
| >
| > In the C++ code the InvokeCommand() method has to fire before my C# COM
| > object is even called. The Initialize() and QueryContextMenu(), which
| > fire when the shell loads (setting up the menu item for right-click,
| > etc), make no calls to the C# COM at all. It seems to me that my C# code
| > has not been inserted into the shell, but a reference to it has.
| >
| > Are you saying that a COM client forces the COM object to load the
| > moment that it loads?
| >
| > Like I said, I must be missing something here.
| >
| > Thanks, JA

Re: Unmanaged C++ Shell Extension Handler as client to C# COM Object gilad
3/1/2006 8:55:59 AM
[quoted text, click to view]

Okay, thanks for the help.

AddThis Social Bookmark Button