@roy nelson You can control the memory usage when an rtf file contains images by implementing the IRichEditOleCallback interface. I haven't tested every type of image, but in a minimal test that loaded and reloaded an rtf file containing two images the memory leak appeared to be eliminated.
I added some modifications to your posted code, including using the right mouse button to initiate the reload of the rtf file.
Give this a try -
#include <windows.h>
#include <richedit.h>
#include <RichOle.h>
#include <psapi.h>
#include <crtdbg.h>
class RichCallback : public IRichEditOleCallback
{
public:
RichCallback();
// *** IUnknown methods ***
STDMETHOD(QueryInterface) (REFIID riid, LPVOID FAR* ppvObj)
{
HRESULT hr = E_NOINTERFACE;
if (ppvObj == NULL)
hr = E_POINTER;
else
{
*ppvObj = NULL;
if (riid == IID_IRichEditOleCallback || riid == IID_IUnknown)
{
*ppvObj = (LPVOID)this;
AddRef();
hr = S_OK;
}
}
return hr;
}
STDMETHOD_(ULONG, AddRef) ()
{
m_ref++;
return m_ref;
}
STDMETHOD_(ULONG, Release) ()
{
ULONG l = --m_ref;
if (m_ref == 0)
delete this;
return l;
}
// *** IRichEditOleCallback methods ***
STDMETHOD(GetNewStorage) (LPSTORAGE FAR* ppstg);
STDMETHOD(GetInPlaceContext) (LPOLEINPLACEFRAME FAR* lplpFrame,
LPOLEINPLACEUIWINDOW FAR* lplpDoc,
LPOLEINPLACEFRAMEINFO lpFrameInfo) { return E_NOTIMPL;};
STDMETHOD(ShowContainerUI) (BOOL fShow) { return E_NOTIMPL; };
STDMETHOD(QueryInsertObject) (LPCLSID lpclsid, LPSTORAGE lpstg, LONG cp) { return S_OK; };
STDMETHOD(DeleteObject) (LPOLEOBJECT lpoleobj) { return S_OK; };
STDMETHOD(QueryAcceptData) (LPDATAOBJECT lpdataobj,
CLIPFORMAT FAR* lpcfFormat, DWORD reco,
BOOL fReally, HGLOBAL hMetaPict) { return E_NOTIMPL; };
STDMETHOD(ContextSensitiveHelp) (BOOL fEnterMode) { return E_NOTIMPL; };
STDMETHOD(GetClipboardData) (CHARRANGE FAR* lpchrg, DWORD reco,
LPDATAOBJECT FAR* lplpdataobj) { return E_NOTIMPL; };
STDMETHOD(GetDragDropEffect) (BOOL fDrag, DWORD grfKeyState, LPDWORD pdwEffect) { return E_NOTIMPL; };
STDMETHOD(GetContextMenu) (WORD seltype, LPOLEOBJECT lpoleobj, CHARRANGE FAR* lpchrg, HMENU FAR* lphmenu) { return E_NOTIMPL; };
//RichCallback Methods
HRESULT ClearAndReallocate();
private:
~RichCallback();
ULONG m_ref, m_stgNum;
IStorage* m_pStorage{};
ILockBytes* m_pLockBytes{};
};
RichCallback::RichCallback() : m_ref(1), m_stgNum(0)
{
HRESULT hr = ClearAndReallocate();
_ASSERTE(SUCCEEDED(hr));
}
RichCallback::~RichCallback()
{
m_pStorage->Release();
m_pLockBytes->Release();
}
STDMETHODIMP RichCallback::GetNewStorage(LPSTORAGE FAR* ppstg)
{
HRESULT hr{ E_FAIL };
WCHAR stgName[31]{}, wstgNum[4]{};
wcscpy_s(stgName, _countof(stgName), L"rcbStg");
_ultow_s(++m_stgNum, wstgNum, _countof(wstgNum), 10);
wcscat_s(stgName, _countof(stgName), wstgNum);
hr = m_pStorage->CreateStorage(stgName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_DIRECT,
0, 0, ppstg);
_ASSERTE(SUCCEEDED(hr));
return hr;
}
HRESULT RichCallback::ClearAndReallocate()
{
HRESULT hr;
if (m_pStorage)
{
m_pStorage->Release();
m_pStorage = nullptr;
}
if (m_pLockBytes)
{
m_pLockBytes->Release();
m_pLockBytes = nullptr;
}
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, 1048576 * 20);
if (hMem)
{
hr = CreateILockBytesOnHGlobal(hMem, TRUE, &m_pLockBytes);
if (SUCCEEDED(hr) && m_pLockBytes)
hr = StgCreateDocfileOnILockBytes(m_pLockBytes, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_DIRECT,
0, &m_pStorage);
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
_ASSERTE(SUCCEEDED(hr));
return hr;
}
static const CHAR RTFFilename[] = "C:\\Users\\RLWA32\\Documents\\PicDoc.rtf";
static const TCHAR RTFFilename_t[] = TEXT("C:\\Users\\RLWA32\\Documents\\PicDoc.rtf");
HWND RichEditHandle, MainWindowHandle;
DWORD CALLBACK MyRead(DWORD_PTR dwCookie, LPBYTE pbBuffer, LONG cb, LONG *pcb)
{
HFILE hf = (HFILE) dwCookie;
if(hf == HFILE_ERROR)
return (DWORD) E_FAIL;
*pcb = _lread(hf, pbBuffer, cb);
return (DWORD) (*pcb >= 0 ? NOERROR : (*pcb = 0, E_FAIL));
}
SIZE_T GetCurrentProcessMemoryInfo()
{
PROCESS_MEMORY_COUNTERS_EX pmc;
pmc.cb = sizeof(pmc);
GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS)&pmc,sizeof(pmc));
return (pmc.PrivateUsage / 1024);
}
void ReadRTFFile(HWND hwndRE, LPCSTR szFile)
{
EDITSTREAM es;
DWORD dwFormat;
es.dwCookie = (DWORD_PTR) _lopen(szFile, OF_READ);
if(es.dwCookie == (DWORD_PTR) HFILE_ERROR) return;
dwFormat = SF_RTF;
es.dwError = 0;
es.pfnCallback = MyRead;
SendMessage(hwndRE,EM_STREAMIN,dwFormat, (LPARAM) &es);
_lclose((HFILE) es.dwCookie);
SendMessage(hwndRE, EM_SETMODIFY, (WPARAM) FALSE, 0);
InvalidateRect(hwndRE, NULL, TRUE);
UpdateWindow(hwndRE);
}
void ShowMemoryUser(HWND WindowH)
{
TCHAR szT[256];
wsprintf(szT, TEXT("Loaded... %s memory used: %dkb"), RTFFilename_t,GetCurrentProcessMemoryInfo());
SetWindowText(WindowH, szT);
}
void ShowLoading(HWND WindowH)
{
TCHAR szT[256];
wsprintf(szT, TEXT("Loading... %s memory used: %dkb"), RTFFilename_t, GetCurrentProcessMemoryInfo());
SetWindowText(WindowH, szT);
}
#ifndef UNICODE
#undef MSFTEDIT_CLASS
#define MSFTEDIT_CLASS "RICHEDIT50W"
#endif
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
static RichCallback *pRichCallback = new RichCallback;
switch (msg) {
case WM_CREATE: {
LoadLibrary(TEXT("Msftedit.dll")); // Ensure the RichEdit DLL is loaded
RichEditHandle = CreateWindowEx(0, MSFTEDIT_CLASS, TEXT(""),
WS_VSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_AUTOVSCROLL,
10, 10, 750, 550, hwnd, NULL, GetModuleHandle(NULL), NULL);
SendMessage(RichEditHandle, EM_SETOLECALLBACK, 0, (LPARAM)pRichCallback);
ReadRTFFile(RichEditHandle,RTFFilename);
}
break;
case WM_NCRBUTTONDOWN:
{
if (wp == HTCAPTION)
{
pRichCallback->ClearAndReallocate();
ShowLoading(MainWindowHandle);
ReadRTFFile(RichEditHandle, RTFFilename);
ShowMemoryUser(MainWindowHandle);
}
else
return DefWindowProc(hwnd, msg, wp, lp);
}
break;
case WM_DESTROY:
pRichCallback->Release();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wp, lp);
}
return 0;
}
int PASCAL
WinMain(HINSTANCE hinstCurr, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hinstCurr;
wc.lpszClassName = TEXT("MyLeaktestWindowClass");
RegisterClass(&wc);
OleInitialize(NULL);
MainWindowHandle = CreateWindow(wc.lpszClassName, TEXT("Click here.."),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
800, 600, NULL, NULL, hinstCurr, NULL);
ShowWindow(MainWindowHandle, nCmdShow);
UpdateWindow(MainWindowHandle);
ShowMemoryUser(MainWindowHandle);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
OleUninitialize();
return 0;
}