The only way (from what I know) to create a progress bar in a taskbar button is to use the ITaskbarList3 interface, but that could only be used in C++, not C. Is there any way to do it in C, interacting only with winapi functions?
COM is designed to be language-agnostic. The introduction to The Component Object Model explains this:
To understand COM [...], it is crucial to understand that it is not an object-oriented language but a standard. [...]. [COM objects] can be written in different languages, and they may be structurally quite dissimilar, which is why COM is referred to as a binary standard; a standard that applies after a program has been translated to binary machine code.
The only language requirement for COM is that code is generated in a language that can create structures of pointers and, either explicitly or implicitly, call functions through 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 to create and use COM objects.
C is capable of creating structures (or arrays) of function pointers and calling functions through pointers. This means that C can be used to implement and use COM interfaces. The only reason why you won't find a lot of treatment of using COM from C is that it is very cumbersome.
At the binary level, a COM interface pointer is a pointer to a structure with one (or more) function pointer tables. The standard MIDL Compiler generates header files that call the function table pointer lpVtbl
. In C++ this is transparently handled by the compiler, with methods showing up as if they were member functions of the class. In C, on the other hand, you have to manually type out the pointer indirections and pass the (implied in C++) this
pointer.
Assuming that pTaskbarList3
is a pointer to an ITaskbarList3
interface, the following snippets show how to call the ITaskbarList::HrInit
method.
C:
hr = pTaskbarList3->lpVtbl->HrInit(pTaskbarList3);
// or, if COBJMACROS is defined
hr = ITaskbarList3_HrInit(pTaskbarList3);
C++:
hr = pTaskbarList3->HrInit();
The code generated is identical in either case. The C++ compiler merely allows us to be a lot less verbose.
The header files that ship with the Windows SDK generally provide a C++ interface and a C interface, that are conditionally available based on whether __cplusplus
is defined. When using a C compiler you can optionally enable convenience macros via the COBJMACROS
preprocessor symbol. These macros allow us to name the interface pointer only once:
hr = pInterface->lpVtbl->Method(pInterface, ...);
// vs.
hr = IInterface_Method(pInterface, ...);
With the basics covered, here's how you would create and use the ITaskbarList3
interface in C.
static ITaskbarList3* pTaskbarList3 = NULL;
// ...
// Create a TaskbarList object and request its ITaskbarList3 interface
HRESULT hr = CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER,
&IID_ITaskbarList3, &pTaskbarList3);
// Initialize the TaskbarList object
if (SUCCEEDED(hr))
{
hr = ITaskbarList3_HrInit(pTaskbarList3);
}
// Set the progress state to indeterminate
if (SUCCEEDED(hr))
{
hr = ITaskbarList3_SetProgressState(pTaskbarList3, hwnd, TBPF_INDETERMINATE);
}
Below is a full sample program that demonstrates how to use the ITaskbarList3
interface from C:
#define UNICODE
#define COBJMACROS
#include <Shobjidl.h>
#include <objbase.h>
#include <winerror.h>
#include <Windows.h>
static UINT REGM_TASKBAR_BUTTON_CREATED = 0;
static ITaskbarList3* pTaskbarList3 = NULL;
static LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
int main()
{
// Initialize COM library for the current thread
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
return hr;
// Register the taskbar button created message; this needs to be done before the
// application window is created
REGM_TASKBAR_BUTTON_CREATED = RegisterWindowMessageW(L"TaskbarButtonCreated");
if (REGM_TASKBAR_BUTTON_CREATED == 0)
return HRESULT_FROM_WIN32(GetLastError());
// Register window class
WNDCLASSW wc = { .style = CS_HREDRAW | CS_VREDRAW,
.lpfnWndProc = WndProc,
.hInstance = GetModuleHandleW(NULL),
.hCursor = LoadCursorW(NULL, IDC_ARROW),
.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1),
.lpszClassName = L"MainWndCls" };
ATOM cls_atom = RegisterClassW(&wc);
if (cls_atom == 0)
return HRESULT_FROM_WIN32(GetLastError());
// Create window
HWND hwnd
= CreateWindowW(wc.lpszClassName, L"Taskbar Button Progressbar Example", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, NULL);
if (hwnd == NULL)
return HRESULT_FROM_WIN32(GetLastError());
// Run a message loop
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0))
{
DispatchMessageW(&msg);
}
}
static LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == REGM_TASKBAR_BUTTON_CREATED)
{
if (pTaskbarList3 == NULL)
{
// Create a TaskbarList object and request its ITaskbarList3 interface
HRESULT hr
= CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, &pTaskbarList3);
// Initialize the TaskbarList object
if (SUCCEEDED(hr))
{
hr = ITaskbarList3_HrInit(pTaskbarList3);
}
// Set the progress state to indeterminate
if (SUCCEEDED(hr))
{
hr = ITaskbarList3_SetProgressState(pTaskbarList3, hwnd, TBPF_INDETERMINATE);
}
}
}
switch (msg)
{
case WM_DESTROY:
// Clean up
if (pTaskbarList3)
{
ITaskbarList3_Release(pTaskbarList3);
pTaskbarList3 = NULL;
}
// Request breaking out of the message loop
PostQuitMessage(0);
return 0;
default:
break;
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
You can compile it from a Visual Studio command prompt using the following command line:
cl main.c /std:c17 /W4 /O2 /link Ole32.lib User32.lib /SUBSYSTEM:CONSOLE
A few points worth noting:
L"TaskbarButtonCreated"
message so that it gets notified when the taskbar button is ready to be used.CoUninitialize
). We have decades worth of evidence that allowing the COM library to clean up as part of thread termination is the safest option. Do ignore the documentation in this case.