Related
I'm using the following code to enumerate the contents of the Recyclebin Shell folder and get the file type of each item.
The code gives the expected results but if I call the function in a loop it looks like there's some memory leak when using the IshellItem2 GetString function (see attached screenshot at the end).
Am I cleaning up everything properly?
Am I misinterpreting the results?
void Test1()
{
// Get recyclebin ishellitem
IShellItem* psiRecycleBin;
if (SUCCEEDED(SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
NULL, IID_PPV_ARGS(&psiRecycleBin))))
{
// Get ishellitem contents enumerator
IEnumShellItems* pesi;
if (SUCCEEDED(psiRecycleBin->BindToHandler(NULL, BHID_EnumItems, IID_PPV_ARGS(&pesi))))
{
IShellItem* psi;
while (pesi->Next(1, &psi, NULL) == S_OK)
{
// Get ishellitem2 from ishellitem
IShellItem2* psi2;
if (SUCCEEDED(psi->QueryInterface(IID_PPV_ARGS(&psi2))))
{
// Get the item file type string
LPWSTR fileType = NULL;
if (SUCCEEDED(psi2->GetString(PKEY_ItemTypeText, &fileType)))
{
CoTaskMemFree(fileType);
}
psi2->Release();
}
psi->Release();
}
pesi->Release();
}
psiRecycleBin->Release();
}
}
And I'm calling it in loop like this:
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
#include <propkey.h>
#include <iostream>
void Test1();
int main()
{
(void)CoInitialize(NULL);
std::cout << "Enumerating recyclebin items..\n";
for (int ii = 0; ii < 5000; ii++)
{
Test1();
}
CoUninitialize();
return 0;
}
When debugging this console program in VS in the memory diagnostics window this is what I get:
Thanks for the help
yes, here really exist memory leak, related to HIDDENRECYCLEBINDATAV2 structure from shell32.dll
partial definition of it:
struct HIDDENRECYCLEBINDATAV2
{
//... some mebers
FILETIME time;
PWSTR pszLocationBeforeDelete = 0; // !!! not released
PWSTR pszLocationInRecycleBin = 0; // !!! not released
HRESULT Serialize(PBYTE *, PUSHORT);
static HRESULT Deserialize(
_In_reads_bytes_opt_(cbStream) const BYTE *pbStream ,
_In_ USHORT cbStream,
_Out_ HIDDENRECYCLEBINDATAV2 ** pphrbd);
static HRESULT Initialize(HIDDENRECYCLEBINDATAV1 const *, HIDDENRECYCLEBINDATAV2**);
};
this structure hold 2 strings - file path from where it deleted ( pszLocationBeforeDelete - this is my name, i don't know original) and current file path in Recycle Bin ( pszLocationInRecycleBin - again my name)
this names allocated inside Deserialize method, by call IStream_ReadStrLong and must be freed with CoTaskMemFree. but how i found - CoTaskMemFree never called for this two strings.
pseudo code for Deserialize :
static HRESULT HIDDENRECYCLEBINDATAV2::Deserialize(
_In_reads_bytes_opt_(cbInit) const BYTE *pbStream ,
_In_ USHORT cbStream,
_Out_ HIDDENRECYCLEBINDATAV2 ** pphrbd)
{
HRESULT hr = E_FAIL;
if (HIDDENRECYCLEBINDATAV2 *phrbd = new HIDDENRECYCLEBINDATAV2)
{
if (IStream *pStream = SHCreateMemStream(pbStream, cbStream))
{
if (0 <= (hr = IStream_ReadStrLong(pStream, &phrbd->pszLocationBeforeDelete)) &&
0 <= (hr = IStream_ReadStrLong(pStream, &phrbd->pszLocationInRecycleBin)))
{
*pphrbd = phrbd, phrbd = 0;
}
pStream->Release();
}
CoTaskMemFree(phrbd); // !! error, need delete phrbd
}
return hr;
}
and it called from CBitBucket::_ValidateItem :
HRESULT InitDeletedItem(PCWSTR pszLocationBeforeDelete, PCWSTR pszLocationBeforeDelete, DELETEDITEM *);
static HRESULT CBitBucket::_ValidateItem(_ITEMIDLIST_RELATIVE const *, DELETEDITEM ** ppdi)
{
HIDDENRECYCLEBINDATAV2 * phrbd;
if (0 <= HIDDENRECYCLEBINDATAV2::Deserialize(pbStream, cbStream, &phrbd))
{
if (DELETEDITEM * pdi = new DELETEDITEM)
{
if (0 <= InitDeletedItem( phrbd->pszLocationBeforeDelete,
phrbd->pszLocationInRecycleBin, pdi))
{
*ppdi = pdi, pdi = 0;
}
if (pdi) delete pdi;
}
CoTaskMemFree(phrbd); // !! error, need delete phrbd
}
}
in both functions - memory for HIDDENRECYCLEBINDATAV2 simply released with CoTaskMemFree api, but memory for strings inside this structure not released. i think need add
HIDDENRECYCLEBINDATAV2::~HIDDENRECYCLEBINDATAV2()
{
CoTaskMemFree(pszLocationInRecycleBin);
CoTaskMemFree(pszLocationBeforeDelete);
}
to this structure and call delete instead CoTaskMemFree
how possible found this ? i hook RtlAllocateHeap and RtlFreeHeap before second call to Test1() (important do this not on first call, because during first call may be additional libs load, some differed initialization, etc.. - all this can distort the real result)and log all alloc/free calls in current thread. also i replace while (pesi->Next..) to if (pesi->Next..) (usually one iteration is enough ). and i found that count of alloc on 2 more than count of free. so i easy found from where this 2 allocations- inside IStream_ReadStrLong. then i set breakpoint here and easy view from where this called :
CBitBucket::_ValidateItem
HIDDENRECYCLEBINDATAV2::Deserialize
IStream_ReadStrLong
CoTaskMemAlloc
partial demo code for log:
struct AI
{
PVOID BaseAddress;
PVOID From;
ULONG Size;
ULONG Flags;
};
struct TID
{
AI *pi;
ULONG nAlloc, nFree, nCells, MaxAllocDelta;
BOOL bNotLog;
TID()
{
RtlZeroMemory(this, sizeof(*this));
}
};
BOOLEAN NTAPI hook_RtlFreeHeap(PVOID HeapHandle, ULONG Flags, PVOID BaseAddress )
{
TID* p = RTL_FRAME<TID>::get();
if (!p || p->bNotLog)
{
return RtlFreeHeap(HeapHandle, Flags, BaseAddress) != 0;
}
p->bNotLog = TRUE;
if (!RtlFreeHeap(HeapHandle, Flags, BaseAddress))
{
__debugbreak();
}
if (BaseAddress)
{
AI* pi = p->pi;
ULONG n = p->nCells;
do
{
if (pi->BaseAddress == BaseAddress)
{
pi->BaseAddress = 0;
p->nFree++;
break;
}
} while (pi++, --n);
if (!n)
{
__debugbreak();
}
}
p->bNotLog = FALSE;
return TRUE;
}
PVOID NTAPI hook_RtlAllocateHeap( PVOID HeapHandle, ULONG Flags, SIZE_T Size )
{
TID* p = RTL_FRAME<TID>::get();
if (!p || p->bNotLog)
{
return RtlAllocateHeap(HeapHandle, Flags, Size);
}
p->bNotLog = TRUE;
if (PVOID BaseAddress = RtlAllocateHeap(HeapHandle, Flags, Size))
{
AI* pi = p->pi;
ULONG n = p->nCells;
do
{
if (!pi->BaseAddress)
{
pi->BaseAddress = BaseAddress;
pi->From = _ReturnAddress();
pi->Size = (ULONG)Size;
pi->Flags = Flags;
p->nAlloc++;
ULONG k = p->nAlloc - p->nFree;
if (k > p->MaxAllocDelta)
{
p->MaxAllocDelta = k;
}
break;
}
} while (pi++, --n);
if (!n)
{
__debugbreak();
}
p->bNotLog = FALSE;
return BaseAddress;
}
return 0;
}
void TestEx()
{
enum { cell_count = 0x1000 };
if (AI* pi = new AI[cell_count])
{
Test1();// first call
// hook RtlAllocateHeap + RtlFreeHeap
{
RtlZeroMemory(pi, cell_count * sizeof(AI));
RTL_FRAME<TID> f;
f.pi = pi;
f.nCells = cell_count;
Test1();// second call
DbgPrint("%x(%x) %x\n", f.nAlloc, f.nFree, f.MaxAllocDelta);
if (f.nAlloc - f.nFree)
{
ULONG n = cell_count;
AI* qi = pi;
do
{
if (qi->BaseAddress)
{
DbgPrint("%p> %x %x\n", qi->From, qi->Size, qi->Flags);
}
} while (qi++, --n);
}
}
delete [] pi;
}
}
I'm trying to edit memory with my custom function in the luaC api but for example when i do like 3 lua_tonumber(LS, -1) it just gets mixed up? Please try to review my code and tell me how to fix this..
lua_State *L;
using namespace std;
DWORD MyGetProcessId(LPCTSTR ProcessName)
{
PROCESSENTRY32 pt;
HANDLE hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
pt.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hsnap, &pt)) {
do {
if (!lstrcmpi(pt.szExeFile, ProcessName)) {
CloseHandle(hsnap);
return pt.th32ProcessID;
}
} while (Process32Next(hsnap, &pt));
}
CloseHandle(hsnap);
return 0;
}
int CustomGetProcessByName(lua_State* Ls) {
DWORD dieman = MyGetProcessId(lua_tostring(Ls, -1));
lua_pushvalue(Ls, dieman);
return 1;
}
int CustomWriteMemInt(lua_State* Ls) {
HANDLE ProcHand = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lua_tonumber(Ls, -1));
int Value = lua_tonumber(Ls, -3);
WriteProcessMemory(ProcHand, (LPVOID)lua_topointer(Ls, -2), &Value, sizeof(Value), 0);
return 1;
}
void Load() {
L = luaL_newstate();
lua_register(L, "GetProcByName", CustomGetProcessByName);
lua_register(L, "WriteMemInt", CustomWriteMemInt);
}
int main() {
Load();
luaL_dostring(L, "a = GetProcByName('ac_client.exe')");
luaL_dostring(L, "WriteMemInt(a, 0x0293AA60, 9999)");
system("Pause");
}
I know the writing memory function works because I did it without this..
lua_topointer returns a pointer to a lua object. You just want to use lua_tonumber instead.
Note that a number is normally a double so will not be able to hold 64-bit addresses, in your example it should work though as the address appears to be 32-bit.
Im using DuiLib and Cef.
and I fond that my _tWinMain called 2 times.
1:When I open app,main() was called;
2 When I Create a Window, main() was called 2nd.
here is some code:
vector<wstring> StartArgs;
map<wstring, wstring> argsMaps;
void GetArgMap() {
for (int i = 1; i < StartArgs.size(); ++i) {
wstring argStr = StartArgs[i];
vector<wstring> argVec;
split(argStr, L':', argVec);
if (argVec.size() >= 2) {
wstring k = argVec[0];
wstring v = argVec[1];
argsMaps.insert(map<wstring, wstring>::value_type(k,v));
}
}
}
wstring GetArgForKey(wstring key) {
wstring ret = argsMaps[key];
if (lstrcmpW(ret.c_str(), L"")) {
return ret;
}
else {
return L"null";
}
}
bool CheckParamAvialble(wstring param) {
if (lstrcmpW(param.c_str(), L"null")) {
return true;
}
else {
return false;
}
}
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
int nCmdShow)
{
ShowMessageBox(L"this is test alert!", L"WARNING");
int argCount = 0;
LPWSTR cmdline = GetCommandLine();
//cmdline is [m:1 h:0 a:500000 n:xxx c:xxx u:usera o:TOKEN]
LPWSTR* szArgList = CommandLineToArgvW(cmdline, &argCount);
for (int i = 0; i < argCount; ++i) {
wstring a = szArgList[i];
StartArgs.push_back(a);
}
if (argCount >= 7) {
wstring mString = GetArgForKey(L"m");
if (!CheckParamAvialble(mString))
{
ShowMessageBox(L"arg m is wrong!", L"Error");
return 0;
}
/*
do something with other args
*/
CPaintManagerUI::SetInstance(hInstance);
InitResource();
HRESULT Hr = ::CoInitialize(NULL);
if (FAILED(Hr)) return 0;
CefMainArgs args(hInstance);
CefRefPtr<SimpleApp> app(new SimpleApp);
int exitCode = CefExecuteProcess(args, app, NULL);
if (exitCode >= 0)
{
return exitCode;
}
CefSettings settings;
CefInitialize(args, settings, app.get(), NULL);
CefRefPtr<CefCommandLine> command_line;
command_line = CefCommandLine::CreateCommandLine();
command_line->AppendSwitch("no-proxy-server");
MainForm *pFrame = new MainForm(_T("Forms\\MA_MainForm.xml"));
if (pFrame == NULL) return 0;
pFrame->Create(NULL, _T("MainForm"), UI_WNDSTYLE_FRAME, WS_EX_STATICEDGE | WS_EX_APPWINDOW, 0, 0, 600, 800);//when progrom go here,i got 2nd MessageBox[this is test alert!]
pFrame->CenterWindow();
CefRunMessageLoop();
CefShutdown();
return 0;
}
else
{
ShowMessageBox(L"something wrong and exit", L"Error");
return 0;
}
}
and the strange thing is
2nd call main() wont trigger my break point, it just show messagebox[this is test alert!] again.
and if i show another window, it will show 3rd messagebox[this is test alert!]
just show the messagebox multiple times,no break,no exception.
thanks a lot.
Have you read over how the Chromium Embedded Framework works? It will create multiple processes, normally by spawning additional copies of the main executable. When that happens, you'll get a different process displaying your "this is a test alert" dialog. Since this is a different process, your breakpoints will not be hit unless you have your debugger configured to debug any spawned child processes.
The result is that what you see is the expected behavior.
I'm in the process of porting a C++ library from Linux to Windows, and am having problems with getuid(), which is not supported in Windows.
Any ideas what I can use in its place?
The Windows equivilent is actually the user's SID. You can get this by using the "GetTokenInformation" call and querying for the TokenUser information class.
To call GetTokenInformation, you need a handle to the users token, which you can get by calling OpenProcessToken (or OpenThreadToken if you're impersonating someone).
You can retrieves the name of the user associated with the current thread with GetUserName :
// ANSI version
string GetWindowsUserNameA()
{
char buffer[UNLEN + 1] = {0};
DWORD buffer_len = UNLEN + 1;
if (!::GetUserNameA(buffer, & buffer_len))
{
// error handling
}
return string(buffer);
}
Windows' closest equivalent of a UID is (probably) a SID. GetUserName followed by LookupAccountName should get you the user's SID.
This is what I came up with.
#include <stdint.h>
#include <stdlib.h>
#include <Windows.h>
#include <sddl.h>
#include <iostream>
#include <iomanip>
#include <memory>
struct heap_delete
{
typedef LPVOID pointer;
void operator()(LPVOID p)
{
::HeapFree(::GetProcessHeap(), 0, p);
}
};
typedef std::unique_ptr<LPVOID, heap_delete> heap_unique_ptr;
struct handle_delete
{
typedef HANDLE pointer;
void operator()(HANDLE p)
{
::CloseHandle(p);
}
};
typedef std::unique_ptr<HANDLE, handle_delete> handle_unique_ptr;
typedef uint32_t uid_t;
BOOL GetUserSID(HANDLE token, PSID* sid)
{
if (
token == nullptr || token == INVALID_HANDLE_VALUE
|| sid == nullptr
)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
DWORD tokenInformationLength = 0;
::GetTokenInformation(
token, TokenUser, nullptr, 0, &tokenInformationLength);
if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
return FALSE;
}
heap_unique_ptr data(
::HeapAlloc(
::GetProcessHeap(), HEAP_ZERO_MEMORY,
tokenInformationLength));
if (data.get() == nullptr)
{
return FALSE;
}
BOOL getTokenInfo = ::GetTokenInformation(
token, TokenUser, data.get(),
tokenInformationLength, &tokenInformationLength);
if (! getTokenInfo)
{
return FALSE;
}
PTOKEN_USER pTokenUser = (PTOKEN_USER)(data.get());
DWORD sidLength = ::GetLengthSid(pTokenUser->User.Sid);
heap_unique_ptr sidPtr(
::HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY, sidLength));
PSID sidL = (PSID)(sidPtr.get());
if (sidL == nullptr)
{
return FALSE;
}
BOOL copySid = ::CopySid(sidLength, sidL, pTokenUser->User.Sid);
if (! copySid)
{
return FALSE;
}
if (!IsValidSid(sidL))
{
return FALSE;
}
*sid = sidL;
sidPtr.release();
return TRUE;
}
uid_t GetUID(HANDLE token)
{
PSID sid = nullptr;
BOOL getSID = GetUserSID(token, &sid);
if (! getSID || ! sid)
{
return -1;
}
heap_unique_ptr sidPtr((LPVOID)(sid));
LPWSTR stringSid = nullptr;
BOOL convertSid = ::ConvertSidToStringSidW(
sid, &stringSid);
if (! convertSid)
{
return -1;
}
uid_t ret = -1;
LPCWSTR p = ::wcsrchr(stringSid, L'-');
if (p && ::iswdigit(p[1]))
{
++p;
ret = ::_wtoi(p);
}
::LocalFree(stringSid);
return ret;
}
uid_t getuid()
{
HANDLE process = ::GetCurrentProcess();
handle_unique_ptr processPtr(process);
HANDLE token = nullptr;
BOOL openToken = ::OpenProcessToken(
process, TOKEN_READ|TOKEN_QUERY_SOURCE, &token);
if (! openToken)
{
return -1;
}
handle_unique_ptr tokenPtr(token);
uid_t ret = GetUID(token);
return ret;
}
uid_t geteuid()
{
HANDLE process = ::GetCurrentProcess();
HANDLE thread = ::GetCurrentThread();
HANDLE token = nullptr;
BOOL openToken = ::OpenThreadToken(
thread, TOKEN_READ|TOKEN_QUERY_SOURCE, FALSE, &token);
if (! openToken && ::GetLastError() == ERROR_NO_TOKEN)
{
openToken = ::OpenThreadToken(
thread, TOKEN_READ|TOKEN_QUERY_SOURCE, TRUE, &token);
if (! openToken && ::GetLastError() == ERROR_NO_TOKEN)
{
openToken = ::OpenProcessToken(
process, TOKEN_READ|TOKEN_QUERY_SOURCE, &token);
}
}
if (! openToken)
{
return -1;
}
handle_unique_ptr tokenPtr(token);
uid_t ret = GetUID(token);
return ret;
}
int main()
{
uid_t uid = getuid();
uid_t euid = geteuid();
std::cout
<< "uid: " << std::setbase(10) << uid << std::endl
<< "euid: " << std::setbase(10) << euid << std::endl
<< std::endl;
return EXIT_SUCCESS;
}
Note that the answer given by Larry Osterman was very helpful. It got me started in the correct direction.
Check out Microsoft's recommendations on porting with the Interix (also known as Services for UNIX 3.0) library. Overkill for what you want though.
in DotNet - Environment.UserName
The right api is SHGetUID(), exported from Shell
We need to programatically burn files to CD in a C\C++ Windows XP/Vista application we are developing using Borlands Turbo C++.
What is the simplest and best way to do this? We would prefer a native windows API (that doesnt rely on MFC) so as not to rely on any third party software/drivers if one is available.
We used the following:
Store files in the directory returned by GetBurnPath, then write using Burn. GetCDRecordableInfo is used to check when the CD is ready.
#include <stdio.h>
#include <imapi.h>
#include <windows.h>
struct MEDIAINFO {
BYTE nSessions;
BYTE nLastTrack;
ULONG nStartAddress;
ULONG nNextWritable;
ULONG nFreeBlocks;
};
//==============================================================================
// Description: CD burning on Windows XP
//==============================================================================
#define CSIDL_CDBURN_AREA 0x003b
SHSTDAPI_(BOOL) SHGetSpecialFolderPathA(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate);
SHSTDAPI_(BOOL) SHGetSpecialFolderPathW(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate);
#ifdef UNICODE
#define SHGetSpecialFolderPath SHGetSpecialFolderPathW
#else
#define SHGetSpecialFolderPath SHGetSpecialFolderPathA
#endif
//==============================================================================
// Interface IDiscMaster
const IID IID_IDiscMaster = {0x520CCA62,0x51A5,0x11D3,{0x91,0x44,0x00,0x10,0x4B,0xA1,0x1C,0x5E}};
const CLSID CLSID_MSDiscMasterObj = {0x520CCA63,0x51A5,0x11D3,{0x91,0x44,0x00,0x10,0x4B,0xA1,0x1C,0x5E}};
typedef interface ICDBurn ICDBurn;
// Interface ICDBurn
const IID IID_ICDBurn = {0x3d73a659,0xe5d0,0x4d42,{0xaf,0xc0,0x51,0x21,0xba,0x42,0x5c,0x8d}};
const CLSID CLSID_CDBurn = {0xfbeb8a05,0xbeee,0x4442,{0x80,0x4e,0x40,0x9d,0x6c,0x45,0x15,0xe9}};
MIDL_INTERFACE("3d73a659-e5d0-4d42-afc0-5121ba425c8d")
ICDBurn : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetRecorderDriveLetter(
/* [size_is][out] */ LPWSTR pszDrive,
/* [in] */ UINT cch) = 0;
virtual HRESULT STDMETHODCALLTYPE Burn(
/* [in] */ HWND hwnd) = 0;
virtual HRESULT STDMETHODCALLTYPE HasRecordableDrive(
/* [out] */ BOOL *pfHasRecorder) = 0;
};
//==============================================================================
// Description: Get burn pathname
// Parameters: pathname - must be at least MAX_PATH in size
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int GetBurnPath(char *path)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
BOOL flag;
if (pICDBurn->HasRecordableDrive(&flag) == S_OK) {
if (SHGetSpecialFolderPath(0, path, CSIDL_CDBURN_AREA, 0)) {
strcat(path, "\\");
}
else {
ret = 1;
}
}
else {
ret = 2;
}
pICDBurn->Release();
}
else {
ret = 3;
}
return ret;
}
//==============================================================================
// Description: Get CD pathname
// Parameters: pathname - must be at least 5 bytes in size
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int GetCDPath(char *path)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
BOOL flag;
WCHAR drive[5];
if (pICDBurn->GetRecorderDriveLetter(drive, 4) == S_OK) {
sprintf(path, "%S", drive);
}
else {
ret = 1;
}
pICDBurn->Release();
}
else {
ret = 3;
}
return ret;
}
//==============================================================================
// Description: Burn CD
// Parameters: None
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int Burn(void)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
if (pICDBurn->Burn(NULL) != S_OK) {
ret = 1;
}
pICDBurn->Release();
}
else {
ret = 2;
}
return ret;
}
//==============================================================================
bool GetCDRecordableInfo(long *FreeSpaceSize)
{
bool Result = false;
IDiscMaster *idm = NULL;
IDiscRecorder *idr = NULL;
IEnumDiscRecorders *pEnumDiscRecorders = NULL;
ULONG cnt;
long type;
long mtype;
long mflags;
MEDIAINFO mi;
try {
CoCreateInstance(CLSID_MSDiscMasterObj, 0, CLSCTX_ALL, IID_IDiscMaster, (void**)&idm);
idm->Open();
idm->EnumDiscRecorders(&pEnumDiscRecorders);
pEnumDiscRecorders->Next(1, &idr, &cnt);
pEnumDiscRecorders->Release();
idr->OpenExclusive();
idr->GetRecorderType(&type);
idr->QueryMediaType(&mtype, &mflags);
idr->QueryMediaInfo(&mi.nSessions, &mi.nLastTrack, &mi.nStartAddress, &mi.nNextWritable, &mi.nFreeBlocks);
idr->Release();
idm->Close();
idm->Release();
Result = true;
}
catch (...) {
Result = false;
}
if (Result == true) {
Result = false;
if (mtype == 0) {
// No Media inserted
Result = false;
}
else {
if ((mflags & 0x04) == 0x04) {
// Writable Media
Result = true;
}
else {
Result = false;
}
if (Result == true) {
*FreeSpaceSize = (mi.nFreeBlocks * 2048);
}
else {
*FreeSpaceSize = 0;
}
}
}
return Result;
}
To complement the accepted answer, we added this helper function to programatically change the burn directory on the fly as this was a requirement of ours.
typedef HMODULE (WINAPI * SHSETFOLDERPATHA)( int , HANDLE , DWORD , LPCTSTR );
int SetBurnPath( char * cpPath )
{
SHSETFOLDERPATHA pSHSetFolderPath;
HANDLE hShell = LoadLibraryA( "shell32.dll" );
if( hShell == NULL )
return -2;
DWORD dwOrdinal = 0x00000000 + 231;
pSHSetFolderPath = (SHSETFOLDERPATHA)GetProcAddress( hShell, (LPCSTR)dwOrdinal );
if( pSHSetFolderPath == NULL )
return -3;
if( pSHSetFolderPath( CSIDL_CDBURN_AREA, NULL, 0, cpPath ) == S_OK )
return 0;
return -1;
}
This is the information for IMAPI in MSDN site http://msdn.microsoft.com/en-us/library/aa939967.aspx
You should be able to use the shell's ICDBurn interface. Back in the XP day MFC didn't even have any classes for cd burning. I'll see if I can find some examples for you, but it's been a while since I looked at this.