Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Creating an Internet Explorer Add-in Toolbar Button using C++ and ATL

Pete Brown - 22 February 2011

Just about every week, I put together the Windows Client Developer Roundup. This consists of a bunch of links to posts that deal with topics of interest to client developers. One of the more time-consuming tasks in that work is simply copying and pasting the link information into LiveWriter. When I open the URL in IE, I have to manually copy the link and then, either copy and paste special on the title (which is annoying, error prone, and includes a dialog with a setting I have to choose each time), or because I type fast, just retype the post title.

At the same time, I have to clean all the FeedBurner gunk off the end of the URL, because links clicked in the roundup shouldn't show up as towards FeedBurner traffic (that's a debate for another time). An example FeedBurner URL with tracking looks like this:

/blog/2011/02/17/asynchronous-web-and-network-calls-on-the-client-in-wpf-and-silverlight-and-net-in-general?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+PeteBrown+%28Pete+Brown%27s+Blog%29

It doesn't seem like a lot of work, but go ahead and click on that link. Now, in the address bar, click and select just the portion of the URL up to the question mark. Notice all the extra clicking you have to do (or click, then home then move the cursor etc.)? Do that 25 times and you see how it gets tedious.

I've been wanting to write an IE addin to copy and clean this for me, but didn't get around to it until today. It took me almost all day to write this add-in and this blog post, so blame this on the Windows Client Developer Roundup being a day late this week :)

Why did it take so long? I wanted to do this in C++, and my C++ is slightly more rusty than an old tractor left in the back field for most of the century.

Now, you can actually accomplish creating a basic add-in simply by storing a little hunk of script on the drive and adding the right registry keys. However, I specifically wanted to do this in C++. No, not because I hate myself, but because I'm starting to see a resurgence of interest in C++. You can create add-ins using .NET and Script, but both have significant limitations as well as performance concerns. If you want to write an add-in of any complexity, you'll almost certainly want to write it in C++. So, that's what I decided to do.

These two articles were absolutely required reading to figure out how to do this add-in.

Now, on to the project. I used Visual C++ inside Visual Studio 2010 for this project. I also used Internet Explorer 8 on Windows 7.

Project Setup

Visual C++ projects are typically created using wizards. Create a new ATL Project using the ATL Project wizard.

image

Make sure you check off "Allow merging of proxy/stub code" or you'll get compile errors. Also ensure you're creating a DLL.

Create the Add-in Class

Next, we'll need to create a class in our project which will be used to expose the functionality of the toolbar button. For that, we'll use the ATL Simple Object wizard.

image

I called the class BlogUrlSnaggerAddIn. Everything else, except the ProgID, was filled in for me. Leave that blank.

image

Leave the next page with the default (disabled) values. The final page lets you set some of the options. Be sure to select IObjectWithSite so you can work with IE. I also changed "Aggregation" to "No" like the MSDN example.

image

Now, without changing anything, build the project. What? You got a bunch of errors because the component couldn't be registered? Ahh, you'll need to exit Visual Studio then run it as Administrator or enable per-user redirection. To enable per-user redirection, right-click the main project and select properties. Then view the Linker General property page. The setting is there; change it to "Yes". (don't do this now, see note below)

image

Unfortunately, the registration we'll be using won't support that, so while it will get you past the initial compile, the later rgs additions will require that you run in administrator mode.

So, once you exit Visual Studio and re-start in Administrator mode, you'll be able to rebuild and have a base project with no errors.

Adding in IOleCommandTarget Declarations

As the MSDN document instructs us, we'll need to add in references to a couple header files (shlguid and mshtml) as well as add support for the IOleCommandTarget interface. I love that I get intellisense on the include files.

// BlogUrlSnaggerAddIn.h : Declaration of the CBlogUrlSnaggerAddIn

#pragma once
#include "resource.h" // main symbols
#include <ShlGuid.h>
#include <MsHTML.h>

...

// CBlogUrlSnaggerAddIn

class ATL_NO_VTABLE CBlogUrlSnaggerAddIn :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CBlogUrlSnaggerAddIn, &CLSID_BlogUrlSnaggerAddIn>,
public IObjectWithSiteImpl<CBlogUrlSnaggerAddIn>,
public IDispatchImpl<IBlogUrlSnaggerAddIn, &IID_IBlogUrlSnaggerAddIn, &LIBID_BlogUrlSnaggerLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IOleCommandTarget
{
public:
CBlogUrlSnaggerAddIn()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_BLOGURLSNAGGERADDIN)

DECLARE_NOT_AGGREGATABLE(CBlogUrlSnaggerAddIn)

BEGIN_COM_MAP(CBlogUrlSnaggerAddIn)
COM_INTERFACE_ENTRY(IBlogUrlSnaggerAddIn)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY(IOleCommandTarget)
END_COM_MAP()

I've highlighted the new lines in the BlogUrlSnaggerAddIn.h file snippet above.

IObjectWithSite and IOleCommandTarget

Right after the "END_COM_MAP()" statement in the .h file, add in the following code to define the interface methods for IObjectWithSite and IOleCommandTarget (I copied this right out of the MSDN article)

public:
// IObjectWithSite
STDMETHOD(SetSite)(IUnknown *pUnkSite);
// IOleCommandTarget
STDMETHOD(Exec)(const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut);
STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup, ULONG cCmds,
OLECMD *prgCmds, OLECMDTEXT *pCmdText);
private:
void GenerateReport(BSTR filename);
BSTR GenerateReportStylesheet();
BSTR DoImageReport(IHTMLDocument2* pDocument);

private:
CComPtr<IWebBrowser2> m_spWebBrowser;
CComQIPtr<IOleCommandTarget, &IID_IOleCommandTarget> m_spTarget;
public:

If you leave out the final "public", this will mess up the scope used in the DECLARE_PROTECT_FINAL_CONSTRUCT macro, so make sure you add the "public" at the end of the code above.

Next, we'll actually implement the code for the SetSite method we added to the .h file.

Storing the Browser Reference using SetSite

The next hunk of code we need to add is to the BlogUrlSnaggerAddIn.cpp file. It implements the SetSite method to grab an instance of the browser for us to use in the other methods. This is primarily copied from the MSDN example, with the class name changed.

STDMETHODIMP CBlogUrlSnaggerAddIn::SetSite(IUnknown *pUnkSite)
{
if (pUnkSite != NULL)
{
// Cache the pointer to IWebBrowser2
CComQIPtr<IServiceProvider> sp = pUnkSite;
HRESULT hr = sp->QueryService(IID_IWebBrowserApp,
IID_IWebBrowser2, (void**)&m_spWebBrowser);
hr = sp->QueryInterface(IID_IOleCommandTarget,
(void**)&m_spTarget);
}
else
{
// Release pointer
m_spWebBrowser.Release();
m_spTarget.Release();
}

// Return base implementation
return IObjectWithSiteImpl<CBlogUrlSnaggerAddIn>::SetSite(pUnkSite);
}

Next, we need to implement the QueryStatus method. We'll also stub out the Exec method for us to fill in later. These go in the same .cpp file

STDMETHODIMP CBlogUrlSnaggerAddIn::Exec(
const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
// This is the method where all the action happens

return S_OK;
}

STDMETHODIMP CBlogUrlSnaggerAddIn::QueryStatus(
const GUID* pguidCmdGroup, ULONG cCmds,
OLECMD prgCmds[], OLECMDTEXT* pCmdText)
{
int i;

// Says we can do anything
// Shamelessly snagged from Eli's blog post

for (i=0; i<((int) cCmds); i++)
prgCmds[i].cmdf = OLECMDF_SUPPORTED | OLECMDF_ENABLED;

return S_OK;
}

Note that just like Eli did in his example, I went ahead and indicated that we support all commands. You may want to be more selective in your own implementation.

Anyway, with that in place, the next step is to add in a little bit of registration

Adding the Registration Code

Now, you need to dig up the CLSID for your class. That is stored in this case in the generated "BlogUrlSnagger_i.h" file with the line that looks like this:

#ifdef __cplusplus

class DECLSPEC_UUID("4E17A214-3A1E-44CE-ACA5-09965A675359")
BlogUrlSnaggerAddIn;
#endif
#endif /* __BlogUrlSnaggerLib_LIBRARY_DEFINED__ */

There's another GUID for the IDispatch interface which is also useful. However, both are pre-populated for us in the .rgs file we'll need to edit (I just wanted you to see one place where they are defined -- they're also duplicated in the .IDL file, and code in hex form in the _i.c file. Ugh)

Crack open the BlogUrlSnaggerAddIn.rgs file in the ResourceFiles folder. You'll see it has been pre-populated with the class name and the main CLSID and the TypeLib CLSID. Excellent. Now we need to modify that to be appropriate to our class. Don't simply cut and paste my code below, these GUIDs are Mine MINE! Get your own stinkin' GUIDs!

HKLM
{
NoRemove SOFTWARE
{
NoRemove Microsoft
{
NoRemove 'Internet Explorer'
{
NoRemove Extensions
{
ForceRemove {4E17A214-3A1E-44CE-ACA5-09965A675359} = s 'BlogUrlSnaggerAddIn Class'
{
val 'Default Visible' = s 'yes'
val 'ButtonText' = s 'Copy Clean URL'
val 'CLSID' = s '{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}'
val 'ClsidExtension' = s '{4E17A214-3A1E-44CE-ACA5-09965A675359}'
val 'Icon' = s 'C:\Program Files\MyApp\foo.ico'
val 'HotIcon' = s 'C:\Program Files\MyApp\foo_hot.ico'
}
}
}
}
}
}

This block is in addition to the registration block already in the rgs file; don't replace the old one with this one, simply add this one below it. Also keep in mind that all those NoRemove statements are pretty darn important. You don't want to hose your registry when playing with this little project.

The settings are:

Setting Description
Default Visible Set to Yes if you want this to be visible by default
Button Text The text you want to have appear for your button
CLSID {1FBA04EE-3024-11D2-8F1F-0000F87ABD16} (see notes in the MSDN article)
ClsidExtension The CLSID from your add-in class
Icon Full path to the icon file. Can also be a path and resource ID.
HotIcon Path to the icon file for the hover/selected icon.

There must be some way to resolve that Icon and HotIcon to the runtime installation folder. I'm not sure what it is, though. If you know, please tell me in the comments below.

Finally, go into the .rc file and change the CompanyName, FileDescription, LegalCopyright, and ProductName to something that makes sense.

Phew! That's it for the setup.

Adding in Test Functionality

Much like Eli did in his post, I'm going to do a simple "Hello World" test before I try to implement the real functionality. In classic first-timer fashion, this is going to simply display a message box that says "Hello World!". Crack open that add-in .cpp file again and modify the Exec method so it includes a call to MessageBox as shown here

STDMETHODIMP CBlogUrlSnaggerAddIn::Exec(
const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
// This is the method where all the action happens

MessageBox(NULL, _T("Hello, World!"), _T("Greetings"), 0);

return S_OK;
}

Now time to test! Do a build and then open up Internet Explorer. You'll see your add-in in the toolbar. If your icon path is invalid (like mine is) you'll see a little gear icon, at least in IE8.

image

(A message box! Dig the retro button on that message box. That's another post for another day)

Adding in the Actual Functionality

Now, we get to the actual functionality. Just about everything up to this point was just ceremony. Now I need to actually learn how to interface with the browser page.

The interface we'll start with is IWebBrowser2. You can find the specs for this interface in MSDN here. The two functions I'm interested in are get_LocationURL and get_LocationName. I see both take a pointer to a BSTR they fill with the result.

Remember, I'm pretty new to C++. I know what a BSTR is, but I realized I had no idea how to allocate and free one. Luckily, we have some help there too.

Another Test

Once I knew what to do to release the string, I expanded the test code to grab the location URL and location Name, and display it in a MessageBox.

STDMETHODIMP CBlogUrlSnaggerAddIn::Exec(
const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
BSTR locationUrl;
BSTR locationName;

m_spWebBrowser->get_LocationURL(&locationUrl);
m_spWebBrowser->get_LocationName(&locationName);

MessageBox(NULL, locationUrl, locationName, 0);

::SysFreeString(locationUrl);
::SysFreeString(locationName);

return S_OK;
}

When run, the application now displays the following MessageBox

Excellent! That's exactly what I was looking for. The next step is to copy it to the clipboard.

Copying to the Clipboard

I want to copy both the page URL and the page title to the clipboard. The natural choice for format here is HTML. It took me a bit of piecing together from various examples I found in searching. I also happened upon a better way to handle BSTR instances, without worrying about managing their allocation. Finally, I figured out what string type to use for regular string manipulation: ATL::CString. Using that requires adding a #include <atlstr.h> to the top of your BlogUrlShaggerAddIn.h file.

STDMETHODIMP CBlogUrlSnaggerAddIn::Exec(
const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
// This is the method where all the action happens

//MessageBox(NULL, _T("Hello, World!"), _T("Greetings"), 0);

CComBSTR locationUrl;
CComBSTR locationName;

m_spWebBrowser->get_LocationURL(&locationUrl);
m_spWebBrowser->get_LocationName(&locationName);

//MessageBox(NULL, locationUrl, locationName, 0);

// register the HTML Format clipboard data format
static int CF_HTML = 0;
if(!CF_HTML) CF_HTML = RegisterClipboardFormat(_T("HTML Format"));

// Build the HTML String. Yeah, I can probably use the Format* methods and
// this approach is likely very inefficient. My string processing here
// could use some touch-up anyway :)
CString html =
CString("Version:0.9\r\n") +
CString("StartHTML:00000000\r\n") +
CString("EndHTML:00000000\r\n") +
CString("StartFragment:00000000\r\n") +
CString("EndFragment:00000000\r\n") +
CString("<!DOCTYPE>\r\n") +
CString("<HTML>\r\n") +
CString("<BODY>\r\n") +
CString("<!--StartFragment-->\r\n") +
CString("<a href=\"") + locationUrl + CString("\">") + locationName + CString("</a>") +
CString("<!--EndFragment-->\r\n") +
CString("</BODY>\r\n") +
CString("</HTML>");

// I'm making the assumption here that 1 byte == 1 char as we're
// going to work with UTF-8
CString startHtml, endHtml, startFragment, endFragment;

startHtml.Format(_T("StartHTML:%08u"), html.Find(_T("<HTML>")));
endHtml.Format(_T("EndHTML:%08u"), html.GetLength() -1);
startFragment.Format(_T("StartFragment:%08u"), html.Find(_T("<!--StartFragment-->")));
endFragment.Format(_T("EndFragment:%08u"), html.Find(_T("<!--EndFragment-->")));

html.Replace(_T("StartHTML:00000000"), startHtml);
html.Replace(_T("EndHTML:00000000"), endHtml);
html.Replace(_T("StartFragment:00000000"), startFragment);
html.Replace(_T("EndFragment:00000000"), endFragment);


// Allocate global memory for transfer
HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE |GMEM_DDESHARE, html.GetLength() + 4);

// Put your string in the global memory...
char *ptr = (char *)GlobalLock(hClipboardData);

strcpy(ptr, CT2A(html, CP_UTF8));
GlobalUnlock(hClipboardData);

// copy to the clipboard
::OpenClipboard(0); //browserHwnd
::EmptyClipboard();
::SetClipboardData(CF_HTML, hClipboardData);
::CloseClipboard();

//MessageBox(NULL, html, _T("Copied to Clipboard"), 0);

return S_OK;
}

That's a lot of code. However, because I'm inefficient with C++, it should be reasonably easy to follow :) . Here's what's going on:

  1. First, I get the URL and Title from IE
  2. Next, I register the HTML Clipboard format using the known "HTML Format" string
  3. Then, using some ugly string replace code, I create the properly formatted clipboard data. Note that HTML written to the clipboard needs to follow a format that includes context: typically a valid HTML doc, even when you're just pasting a little link.
  4. Once I have all that ugly string replace done, I put the string into global memory
  5. Once in global memory, I open the clipboard with an hWnd of 0 to allow anyone to get at the data
  6. I then empty the current contents (how rude!), add the new data, close the clipboard and return Success.

Now if you run it, you should be able to click the toolbar button and get the title and URL pasted into LiveWriter (or Word or whatever).

Cleaning up the URL

Ok, now that copy & paste works, it's time to do the cleaning. Here's a reminder of what FeedBurner URLs with tracking look like:

/blog/2011/02/17/asynchronous-web-and-network-calls-on-the-client-in-wpf-and-silverlight-and-net-in-general?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+PeteBrown+%28Pete+Brown%27s+Blog%29

What I need to do is get rid of anything following utm_source up to the next ampersand or end of string. Then the same with utm_medium and utm_campaign. I'd just as soon get rid of everything after the ? but that will break a few blogs that don't yet use SEO-friendly URLs. I also don't want to be dependent on the specific order of those parameters.

To help, I added an additional private function to the class. In the .h, I added:

CString RemoveParameter(CString url, CString parameter);

Then in the class itself, I modified the Exec function and added the implementation of the new function.

CString CBlogUrlSnaggerAddIn::RemoveParameter(CString url, CString parameter)
{
int paramListStart = 0;

if (paramListStart = url.Find(_T("?")) > 0)
{
int paramStart = url.Find(parameter, paramListStart);
int paramEnd = url.Find(_T("&"), paramStart);
if (paramEnd <= 0) paramEnd = url.GetLength() -1;

return url.Mid(0, paramStart) + url.Mid(paramEnd + 1);
}
else
{
return url;
}

}


STDMETHODIMP CBlogUrlSnaggerAddIn::Exec(
const GUID *pguidCmdGroup, DWORD nCmdID,
DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
// This is the method where all the action happens

//MessageBox(NULL, _T("Hello, World!"), _T("Greetings"), 0);

CComBSTR locationUrl;
CComBSTR locationName;

m_spWebBrowser->get_LocationURL(&locationUrl);
m_spWebBrowser->get_LocationName(&locationName);

//MessageBox(NULL, locationUrl, locationName, 0);

// register the HTML Format clipboard data format
static int CF_HTML = 0;
if(!CF_HTML) CF_HTML = RegisterClipboardFormat(_T("HTML Format"));

// remove the FeedBurner parameters from the URL
CString cleanedUrl = locationUrl;
cleanedUrl = RemoveParameter(cleanedUrl, _T("utm_source"));
cleanedUrl = RemoveParameter(cleanedUrl, _T("utm_medium"));
cleanedUrl = RemoveParameter(cleanedUrl, _T("utm_campaign"));

// check for a final ? (meaning no other parameters)
if (cleanedUrl.Right(1) == _T("?"))
cleanedUrl = cleanedUrl.Left(cleanedUrl.GetLength()-1);

CString fragment = CString("<a href=\"") + cleanedUrl + CString("\">") +
locationName + CString("</a>");


// Build the HTML String. Yeah, I can probably use the Format* methods and
// this approach is likely very inefficient. My string processing here
// could use some touch-up anyway :)
CString html =
CString("Version:0.9\r\n") +
CString("StartHTML:00000000\r\n") +
CString("EndHTML:00000000\r\n") +
CString("StartFragment:00000000\r\n") +
CString("EndFragment:00000000\r\n") +
CString("<!DOCTYPE>\r\n") +
CString("<HTML>\r\n") +
CString("<BODY>\r\n") +
CString("<!--StartFragment-->\r\n") +
fragment + CString("\r\n") +
CString("<!--EndFragment-->\r\n") +
CString("</BODY>\r\n") +
CString("</HTML>");

// I'm making the assumption here that 1 byte == 1 char as we're
// going to work with UTF-8
CString startHtml, endHtml, startFragment, endFragment;

startHtml.Format(_T("StartHTML:%08u"), html.Find(_T("<HTML>")));
endHtml.Format(_T("EndHTML:%08u"), html.GetLength() -1);
startFragment.Format(_T("StartFragment:%08u"), html.Find(_T("<!--StartFragment-->")));
endFragment.Format(_T("EndFragment:%08u"), html.Find(_T("<!--EndFragment-->")));

html.Replace(_T("StartHTML:00000000"), startHtml);
html.Replace(_T("EndHTML:00000000"), endHtml);
html.Replace(_T("StartFragment:00000000"), startFragment);
html.Replace(_T("EndFragment:00000000"), endFragment);


// Allocate global memory for transfer
HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE |GMEM_DDESHARE, html.GetLength() + 4);

// Put your string in the global memory...
char *ptr = (char *)GlobalLock(hClipboardData);

strcpy(ptr, CT2A(html, CP_UTF8));
GlobalUnlock(hClipboardData);

// copy to the clipboard
::OpenClipboard(0); //browserHwnd
::EmptyClipboard();
::SetClipboardData(CF_HTML, hClipboardData);
::CloseClipboard();

//MessageBox(NULL, html, _T("Copied to Clipboard"), 0);

return S_OK;
}

That's it! It's all working. I tested it with additional parameters and validated that it didn't screw them up. Of course, I may still have to do some slight cleaning of URLs for cases when people (like me) put the site name in the title), but otherwise this should save me a good bit of work every week. Plus, I learned a little C++ along the way. Win!

Keep in mind, I'm relearning C++. If I did something dumb, don't hesitate to (nicely) point it out in the comments, especially if it's something other people shouldn't repeat in their own code.

(note: this was all tested using 32bit IE8 on Win7 x64: Works On My Machine)

*** UPDATE: New source code and an update in this blog post. ***

         

Source Code and Related Media

Download /media/74206/blogurlsnagger.zip
posted by Pete Brown on Tuesday, February 22, 2011
filed under:          

36 comments for “Creating an Internet Explorer Add-in Toolbar Button using C++ and ATL”

  1. Franci Penovsays:
    Great article, Pete!

    I would suggest though to do registration once and then turn off the self-registration and run VS as regular user. The main reason is that if VS is running as administrator and you're launching IE to debug your add-in, IE will be running as admin too. When IE runs as admin, it disables protected mode, so you are debugging your code under different conditions from the ones it'll be running in everyday use.

    While this does not have much impact on your specific scenario, it is important when accessing the registry or the file system, or when launching external processes.
  2. Stefan Gartzsays:
    Tiny improvment, C++ CStrings!
    // Build the HTML String. Yeah, I can probably use the Format* methods and
    // this approach is likely very inefficient. My string processing here
    // could use some touch-up anyway :)
    CString html =
    "Version:0.9\r\n"
    "StartHTML:00000000\r\n"
    "EndHTML:00000000\r\n"
    "StartFragment:00000000\r\n"
    "EndFragment:00000000\r\n"
    "<!DOCTYPE>\r\n"
    "<HTML>\r\n"
    "<BODY>\r\n"
    "<!--StartFragment-->\r\n";
    html += fragment;
    html +=
    "\r\n"
    "<!--EndFragment-->\r\n"
    "</BODY>\r\n"
    "</HTML>";
  3. Thierry FRANZETTIsays:
    Welcome back to the native world !

    I have 3 improvements to your code :

    1. There is no need the interface be Automation-compatible (deriving from IDispatch). In the creation wizard, you could choose the interface as 'Custom' instead of 'Dual'.

    2. When passing the string to the clipboard and copying the data in the global allocated memory, prefer the following way of doing :

    CT2A utf8String(html, CP_UTF8);
    size_t bufferSize = strlen(utf8String) + 1 /* terminating NULL character */;

    HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE |GMEM_DDESHARE, bufferSize);

    // Put your string in the global memory...
    char *ptr = (char *)GlobalLock(hClipboardData);

    strcpy_s(ptr, bufferSize, utf8String);

    GlobalUnlock(hClipboardData);
    ptr = nullptr;

    This prevents :
    * using the unsafe version of strcpy to avoid potential buffer overrun (welcome back to native world, again ;-) )
    * remove the '+ 4' suspicious operation when computing the length of the buffer.

    3. In RemoveParameter method, you should handle properly the case the given parameter is not found in the URL (even if it works in release mode, you reach an assertion in debug mode) :

    int paramStart = url.Find(parameter, paramListStart);
    if (paramStart > 0)
    {
    int paramEnd = url.Find(_T("&"), paramStart);
    if (paramEnd <= 0) paramEnd = url.GetLength() -1;

    return url.Mid(0, paramStart) + url.Mid(paramEnd + 1);
    }
  4. Joesays:
    Where do you see C++ in the code diarrhea you just linked ? COM, MFC, ATL, C yes. Modern C++, no. It's pretty clear why C++ has been getting a bad rap lately, it wasn't C++ !
  5. Petesays:
    @Joe

    Constructive criticism is always welcome. I made it pretty clear in this article that I haven't coded in C++ in a very long time. Your comment was not constructive. In fact, it points to one of the things that has given C++ a bad rap: people who think being douches to noobs is somehow advancing the art.

    @Stefan.

    Cool, thanks! I forgot about continuation for multi-line strings.

    @Thierry

    Excellent. thanks for the changes!
  6. jalfsays:
    I *kind of* agree with Joe's point, but that's not really a criticism, more a statement of fact. You need to jump through a lot of hoops to write an IE plugin, and outside of that, there's really not a lot of actual logic to be written in just C++.

    The only real "plain C++" logic I can see is really the CString bits, and for those, you'd typically prefer the standard library `std::string` class instead. (In general, prefer the standard library over MFC). For the string building specifically, `std::stringstream` might be a good bet.

    Everything else is tied up in some horrendously ugly Microsoft APIs that you probably can't really avoid in this case.

    As for `strcopy` ( in the few cases where you *have* to work with C-style strings, of course, rather than using the std lib string class), consider replacing it with `std::copy` (takes two iterators/pointers delimiting the string) or `std::copy_n` (takes an iterator pointing to the beginning of the string, and the number of chars to copy). That's another good rule of thumb, prefer the C++ std lib over the C std lib.

    The "better way of handling BSTR", btw, is a special case of the RAII (Resource Acquisition Is Initialization) idiom, which is hugely important in modern C++, and you should implement it yourself pretty much whenever you're forced to deal with memory management.

    In short, instead of writing code to "allocate memory, use object, deallocate memory", write a small wrapper object which, in the constructor, allocates the necessary memory, and in the destructor, frees it. Then you can create an instance of that object on the stack, and it will *automatically* clean up after itself (and even if an exception is thrown, destructors are still called so even then, you won't leak memory, saving you a lot of error-handling boilerplate code)

    That's basically what CComBSTR does for you.

    The same pattern can (should) be generalized to any other resource acquisition. For example, opening/closing the clipboard could be encapsulated in a small RAII wrapper class, wich, on construction, opens it, and on destruction closes the clipboard again.

    Then your clipboard code could be replaced by something like this:

    scoped_clipboard cp; // implicitly opensand clears the clipboard
    ::SetClipboardData(CF_HTML, hClipboardData); // could wrap this in a member function on scoped_clipboard if we want to

    and you would be guaranteed that however you leave the function, the clipboard gets cleanly closed again.

    Similarly for GlobalLock/GlobalUnlock: you want to be sure that your lock is released, so rather than relying on a manual Unlock call, write a small helper class which unlocks on destruction. (And then put an extra pair of { }'s around its use to force it to go out of scope immediately, rather than at the end of the function).

    Of course, for quick 'n dirty code like this, doing it your way is no big deal, but in general, you should always use RAII to wrap your resource allocations, whether the resource in question is a file handle, a lock of some sort, a memory allocation or anything else. If it has to be acquired and released, or opened and closed, or locked and unlocked, it calls for RAII.

    Hope this was more constructive. :)
  7. CppJohnsays:
    Great blog post, Pete! Thanks for sharing.
    And thanks for remembering us that we should use the right tools for the task at hand: for some tasks C# is fine, for other ones C++ should be used.

    I'd like to point you a small improvment you could do in your code.
    When you use string literals with CString, you should always use _T("...") or L"..." decorations.
    Use _T() for both Unicode and ANSI/MBCS backward compatibility, and use L"..." for Unicode only builds (which should be the default these days).

    I noted that you correctly used _T("...") in several cases, but you missed some places, e.g.: CString("Version:0.9\r\n"), this should be CString(_T("Version:0.9\r\n")) or CString(L"Version:0.9\r\n").

    I know it compiles and works for you, but this is because of an (IMHO, "evil") CString implicit constructor, which takes an ANSI/MBCS string and concerts it to Unicode. I think this is a waste of CPU cycles, because there are useless calls to MultiByteToWideChar for a string literal that should be Unicode L"..." in the first place.
    (In some cases this behaviour could lead also to ugly bugs, but not when you have pure ASCII text like this.)

    To avoid these implicit CString constructors, you can #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS in your "StdAfx.h" precompiled header file.

    Thanks.
  8. CppJohnsays:
    @jalf:

    >>> The only real "plain C++" logic I can see is really the CString bits, and for those, you'd typically prefer the standard library `std::string` class instead. (In general, prefer the standard library over MFC).


    While I agree that STL containers are better than MFC ones, I think CString is much better than STL std::[w]string.
    CString offers a richer public interface, it's better integrated in Win32 programming environment than STL string, it can easily load strings from resources (good for internationalization), etc.

    Note that it is possible to use the quality STL containers (like std::vector, std::map...), with the quality ATL/MFC CString class.

    Again, use the right tool for the right job ;)
  9. jalfsays:
    @CppJohn: CString definitely has better Win32 integration, but std::string has better standard library integration (and on a related note, CString implies a third-party dependency which could be avoided). As you say, the right tool for the job. I'm not a huge fan of CString though.

    However, I'm curious, apart from the Win32/resource stuff, what do you feel is missing in std::string's interface?
  10. jalfsays:
    Oh, I don't know why I didn't think of this before, but in VS2010, the standard library contains classes for regular expressions (in the `<regex>` header).

    That would probably have simplified your string manipulation quite a bit.
  11. CppJohnsays:
    @jalf:
    >>> However, I'm curious, apart from the Win32/resource stuff, what do you feel is missing in std::string's interface?

    The Win32 integration and resource loading stuff are sufficient to me for using CString in Win32 ATL/MFC C++ code (of course, if I have to write multiplatform C++ code, std::[w]string is a natural choice).

    The implicit LPCTSTR cast operator makes CString convenient when passing strings to Win32 API's (no need to explicitly call .c_str()).

    There is a CString constructor overload that comes in handy when you need to load string resources.

    GetBuffer() is another convenient method of CString, useful for Win32 API's that need a writable buffer.

    CString::FormatMessage() is another useful method lacking in STL strings. (With FormatMessage() you can specify the order of parameters in the format string, it is more powerful than simple printf()-like formatting.)

    There are also other convenient methods like CompareNoCase(), Trim(), Left(), Right(), Mid(), Tokenize(), etc. available in CString but not in STL string.

  12. jalfsays:
    The implicit cast is missing from `std::string` because it's a bad idea. Implicit conversions in general cause more trouble than they're worth.

    Case insensitive compare and FormatMessage and tokenize are all good points (although Boost gives you the same (in portable implementations, of course), if you're willing to take dependencies on external libraries anyway)

    Most of the others are there already:
    Left(N): std::string(str.begin(), str.begin() + N)
    Right(N): std::string(str.end(), str.en() - N)
    Mid(N. M): std::string(str.begin() + N, str.begin() + M)
    GetBuffer(): str.begin() (or, if you need a pointer, rather than an iterator, &*str.begin() or &str[0]

    Of course, you're right about the Win32/resource handling stuff. I just wanted to point out that std::string is nowhere near as crippled as you make it out to be.

    Again, the right tool for the job. :)
  13. CppJohnsays:

    @jalf:

    > The implicit cast is missing from `std::string`
    > because it's a bad idea. Implicit conversions in
    > general cause more trouble than they're worth.

    But in the context of Win32 it allows CString to be passed as input parameters as LPCTSTR to Win32 API's in a very simple and "natural" way.


    > Most of the others are there already:
    > Left(N): std::string(str.begin(), str.begin() + N)
    > Right(N): std::string(str.end(), str.en() - N)
    > Mid(N. M): std::string(str.begin() + N, str.begin() + M)

    I know it's doable with proper combinations of string::begin() and end() and doing some math, but the point is that having methods like Left(), Right() and Mid() available increases the semantic level and the readability of the code.

    In all honesty, in the middle of a C++ code, what do you find more readable? A simple call to CString.Left(N) method, or an expression like std::string(str.begin(), str.begin() + N)? :)
    I think the brain parses "CString.Left(N)" in a faster and easier way.



    > GetBuffer(): str.begin() (or, if you need a pointer,
    > rather than an iterator, &*str.begin() or &str[0]

    There is a subtle difference here between CString and std::string: the STL strings do not interoperate well with legacy C code (like Win32 API's), because STL strings are in general not NUL-terminated strings, e.g.

    // *** Using CString ***
    CString str;
    WCHAR * psz = str.GetBuffer(100);
    psz[0] = 0; // ...or GetWindowText(hwnd, psz, 100)
    str.ReleaseBuffer();
    assert( wcslen(str) == str.GetLength() ); // OK


    // *** Using STL string ***
    std::wstring s;
    s.resize(100);
    s[0] = 0; // ...or GetWindowText(hwnd, &s[0], 100)
    assert( wcslen(s.c_str()) == s.length() ); // BANG!

  14. jalfsays:
    > I know it's doable with proper combinations of string::begin() and end() and doing some math
    You mean by using the same two functions in every case, and by "math", you mean "using the same N that you'd be using in the CString version"?

    Really, I don't see much "math" in the STL string version either.

    > In all honesty, in the middle of a C++ code, what do you find more readable? A simple call to CString.Left(N) method, or an expression like std::string(str.begin(), str.begin() + N)?

    Honestly? The latter. The former doesn't really tell me much. Does it mean "take the N'th character from the left", or "take N characters starting from the left", or "are there N characters left"? "Mid" is even worse, because it doesn't actually operate *from the middle*. You actually have to go and look it up if you don't know off-hand what it does.

    But the std::string version is pretty easily readable, I'd say.
    "Call the string constructor, and pass to it the range of characters that starts with the beginning of str, and ends N characters from the beginning."

    And I consider it a bonus that the *same* interface is used for so many other operations. No matter what you want to do with the string, it's going to look much like this, working on a pair of iterators, which means that there are fewer functions you need to understand, and you're going to encounter the same names more often.

    I think an interface is more readable if it is compact and reusable. If I have to use a different function for every subtly different task, then it's a pain to keep track of. But if the interface contains one or two simple, generic tools which can be used to solve *all* the tasks, then it's easy to remember and much more readable IMO.

    As for the null termination, I think it cuts both ways. Remember how wonderfully inconsistent the Win32 API is. Half the functions will write a null-terminated string into the buffer, the other half won't write the terminating null. So half of the API will work naturally with CString, half would result in a buffer overrun with CString, but work safely with the STL string. ;)

    But of course, it's just a matter of habit. I'm just more used to seeing the `std::string` version.

    In any case, it's hardly a big deal. If there's one constant in C++ development then it's that there's never just one string class. ;) (and that they're all flawed in some way, unfortunately)
  15. Petesays:
    @Stefan

    FWIW, the CString code you posted doesn't compile. You get this error:
    error C2440: 'initializing' : cannot convert from 'const char [148]' to 'ATL::CStringT<BaseType,StringTraits>' d:\documents\docs\projects\blogurlsnagger\blogurlsnagger\blogurlsnaggeraddin.cpp

    Pete
  16. CppJohnsays:
    @Pete:

    Stefan's code does not compile because the string literals are not "decorated" with _T() (or L"...").
    If you properly decorate with _T() (or just L"...", for Unicode-only builds), it should work.

  17. CppJohnsays:
    I mean, something like this:

    CString html =
    _T("Version:0.9\r\n")
    _T("StartHTML:00000000\r\n")
    _T("EndHTML:00000000\r\n")
    _T("StartFragment:00000000\r\n")
    _T("EndFragment:00000000\r\n")
    _T("<!DOCTYPE>\r\n")
    _T("<HTML>\r\n")
    _T("<BODY>\r\n")
    _T("<!--StartFragment-->\r\n");

    (or use L"..." for Unicode-only.)
  18. hamid solatsays:
    hi
    Thank you for this article.Excuse me,I do not speak English well.
    please help me.I Built the above project ,but I want create a standard toolbar for Internet Explorer.
    a example is this:
    http://www.codeproject.com/KB/wtl/rssreaderplugin.aspx
    i do understand no thing from this project . Can you guide me?
    If possible Teach me With Image help.
  19. Tuvyorsays:
    Wonderful and really helpful post.

    I followed it step-by-step and successfully created my first IE toolbar.

    Compared to the MSDN documentation on IE toolbars I've spent the last few hours dealing with, it felt like finding an oasis in a desert.
  20. romansays:
    can you share source code? Thanks

    i Write Ie toolbar for x32 and x64 ОС.

    In x32 all good? toolbar working? but in windows x64 :
    1 - toolbar registration success
    2 - click view -> tool bar -> click my toolbar -> nothing changed


    I see how registration other toolbar. and write in registry like him.
    Fany I can registration and open other toolbar but cant open my
  21. Petesays:
    @Roman

    The source code is attached to the article above; it always has been. I'm not sure what's going on with registration. I'm not deep in the C++ addin world; this was my first attempt.

    Pete
  22. Johnsays:
    IE desperately needs a new scriptable plugin/extension/add-on feature just like FF/Chrome.

    And this is not intended as a IE vs. debate, it's pretty obious that requring C++/ATL/COM for creating add-ons toa browser is completely absurd in these days.

    This is why there are no free ad blcokers for IE that are actually being maintained and updated, it just requires too much.

    At least they should make soem proper .NET classes and make a plugin interface for .NET to interact with IE on all levels.

    It's obvious though that they do not care about making it easier for those making add-ons for IE.
  23. Drewsays:
    i agree with Tuvyor, this is a great post and helped me tremendously.

    not sure if this is still checked, but what would be the best way to begin displaying dialog boxes with a form and perhaps submitting information inside the BHO?
  24. Anand Khatrisays:
    Thanks,

    This page is very useful to me.....
    but I have 1 question...

    here, we place one Icon on the Command bar of a IE....Now I want to add my custom Toolbar on IE instead of one icon on Command bar...so for that what can I do? you can provide me any link for this also....


    Thank you very much.....
  25. YHSsays:
    Hi, I'm making ATL according to this article, but I have got error follows:

    error C2065: 'HTMLDocument' undeclared identifier ( in atlhost.h )

    so I have modified HTMLDocument as IHTMLDocument2. but the other error was occured.

    error C2787: 'IHTMLDocument2' : no GUID has been associated with this object ...

    how do I solve it ? please give me comment.

    thank you.
  26. Jefferykigsays:
    Аентство недвижимости поможет сдать/снять, купить/продать недвижимость любого типа. Квалифицированные агенты всегда готовы Вас проконсультировать, и помочь совершить сделку. Контакты на сайте: [url=http://maximavn.ru/]недвижимость[/url]
  27. LaurieKrsays:
    В АТМОСФЕРЕ РОСКОШИ: ОСЕНЬ-ЗИМА ОТ INCANTO
    http://youtu.be/CABD3ccLmZY

    ТОМНАЯ И МАНЯЩАЯ СЕКСУАЛЬНОСТЬ НОВОЙ КОЛЛЕКЦИИ INCANTO ЗАСТАВИТ ПОЧУВСТВОВАТЬ СЕБЯ ГЕРОИНЕЙ ЛЮБОВНОГО РОМАНА.
    На создание осенне-зимней коллекции дизайнеров Incanto вдохновила главная премьера кинофестиваля в Каннах – картина «Принцесса Монако».
    Так родились несколько самых желанных трофеев сезона.

Comment on this Post

Remember me