Please help: Legacy database programming in Visual C++ with ADODB - c++

I am maintaining a legacy codebase and need to write a small console application in Visual C++ 6 that accesses a SQL Server database via ADODB. I am an experienced C/C++ developer, but haven't spent much time with MS C++, which as far as I can tell is totally different than g++ and chock full of nonstandard language extensions.
Whenever I try to follow a tutorial on the web, I get a zillion errors. It doesn't recognize a whole bunch of identifiers (e.g. CComPtr, HRESULT, _RecordsetPtr, etc.) and I'm pretty sure the problem is I'm somehow setting up my project wrong. I have tried basically every console app AppWizard available, and none will make the demo code I found work.
Can someone explain to me how I set up a Visual C++ 6 application that can, for example, do a count(*) from a DB table? I am thinking my problem has something to do with non properly including various ATL or whatever libraries that ADODB depends on... I've figured out how to #import msado15.dll but still no dice.
Please help!!! Thanks in advance,
Jason
UPDATE: have now gotten it to compile, but am getting a "Debug Assertion Failed" when I try to open my ADO connection. It comes from atlbase.h line 474 and the assertion is "p != 0".
UPDATE 2: Here's my code
#import "C:\Program Files\Common Files\System\ADO\msdo15.tlb" no_namespace rename("EOF","A_EOF")
#include "stdafx.h"
#include <objbase.h>
#include <initguid.h>
#include <comdef.h>
#include <atlbase.h>
#include <adoid.h>
#include <adoint.h>
int main(int argc, char* argv[])
{
HRESULT hr;
CComPtr<ADORecordset> m_pSet;
CComPtr<ADOConnection> m_pConn;
char ret[128];
CComBSTR connstr = (CComBSTR) "driver=SQL Server;server=SQL1;uid=ffffddddd;pwd=aaaasss;database=MyDB";
CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID *) &m_pConn);
CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *) &m_pSet);
printf("Here %d!\n", (int) &m_pConn);
m_pConn->Open(connstr, (CComBSTR) "", (CComBSTR) "", adOpenUnspecified);
//m_pConn->ConnectionString = connstr;
//m_pConn->Open("","","",NULL);
printf("Here!\n");
m_pSet->Open(CComVariant((CComBSTR) "SELECT COUNT(*) AS Cnt FROM VARIANCESWAP_INDIC"), CComVariant(m_pConn), adOpenKeyset, adLockOptimistic, adCmdText);
CComPtr<ADOFields> pFields = NULL;
m_pSet->get_Fields(&pFields);
CComPtr<ADOField> cnt = NULL;
pFields->get_Item(CComVariant(0), &cnt);
CComVariant dbValue;
cnt->get_Value(&dbValue);
sprintf(ret, "%S", dbValue.bstrVal);
if(m_pSet != NULL) m_pSet->Close();
if(m_pConn != NULL) m_pConn->Close();
printf("Hello World!\n");
return 0;
}

If you're not getting a definition for HRESULT then you probably have not #included <windows.h>.
Google is your friend. Search for missing types and generally some kind soul, or MSDN, will tell you what file you need to include in your program to get it working.

Make sure you check the HRESULT return values from your function calls, specifically the CoCreateInstance calls here. I suspect they are failing because it looks like you haven't called CoInitialize, but check what error code you get.

You haven't initialized COM.
Also, you are not checking the return values of COM calls like CoCreateInstance or Open. You are supposed to check the result of each COM call.
Suggested reading
Using ADO with Microsoft Visual
C++
KB183606 ActiveX Data
Objects (ADO) Frequently Asked
Questions

Related

The equivelant code %SystemDrive% in batch translated into C++

To anyone that can help Please,
(My operating system is Windows XP)
I have looked on the this forum but have not found a similair answer that I could use or adapt to suite this particular situation. I will try to explain (I apologise in advance if my question seems confusing)
I am constructing a batch file that will call a C++ program (.exe) The C++ program is hard coded to the C: drive. By the way I did not write the C++ program as I am incapable of writing in C++ but would like to exchange the C: in C++ for what would be in batch %SystemDrive%. The line of code in C++ reads as follows:
SetSfcFileException(0, L"c:\\windows\\system32\\calc.exe",-1);
// Now we can modify the system file in a complete stealth.
}
The bit of code I would like to alter in the above code is C: or "C" to change it to %systemDrive% but in C++ code language, in effect change the hard coded part of the C++ program to read a System path variable within XP.
I have also looked elsewhere on the net but have not found a suitable answer as I do Not want to break the C++ code you see.
The C++ code was obtained from the folowing website written by Abdellatif_El_Khlifi:
https://www.codeproject.com/Articles/14933/A-simple-way-to-hack-Windows-File-Protection-WFP-u
Many Thanks for any help given,
David
The search term you should be looking for is Known Folders.
Specifically, calling SHGetKnownFolderPath() with the FOLDERID_System identifier, one of the many IDs found here.
That's for Vista or better. For earlier than that (such as XP), you have to use CSIDL values, CSIDL_SYSTEM (see here for list) passed into SHGetFolderPath().
You can still use the pre-Vista ones but I think they're just thin wrappers around the newer ones.
This is the simplest console application I could come up with that shows this in action (Visual Studio 2019):
#include <iostream>
#include <shlobj_core.h>
#include <comutil.h>
int main()
{
PWSTR path = NULL;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_System, 0, NULL, &path);
_bstr_t bstrPath(path);
std::string strPath((char*)bstrPath);
std::cout << "Path is '" << strPath << "'\n";
}
and the output on my system is:
Path is 'C:\WINDOWS\system32'
This is not really answering my own question, well it is but in a alternative manner, many ways to skin a cat so to speak!
Here is one encouraging bit of news though I have stumbled across the very thing I need called WFPReplacer, it is a commandline windows utility that pretty well does what I want & generally in the same manner. it disables WFP for both singular files & can be used for wholesale switching off of WFP if the right file is replaced. All I need to do is write a batch file as a front end to back up the system files I want to disable use WFPReplacer.exe. So if in the event of the proceedings the routine gets stuffed I can revert back to the backed up files. I think this program uses the same type of embedded coding but is written in Delphi/pascal, it is called Remko Weijnen's Blog (Remko's Blog) "replacing Wfp protected files".
I generally like to leave whatever I am doing on a positive note. So just in case someone else lands on this forum & is trying to accomplish a similair exercise here is the code that one can compile (This is not my code it belongs to Remko Weijnen's Blog (Remko's Blog)) Please be advised it is NOT C++ it is a commandline exe Delhi/Pascal found at this link, so all credits belong to him. The link is:
https://www.remkoweijnen.nl/blog/2012/12/05/replacing-wfp-protected-files/
DWORD __stdcall SfcFileException(RPC_BINDING_HANDLE hServer, LPCWSTR lpSrc, int Unknown)
{
RPC_BINDING_HANDLE hServerVar; // eax#2
int nts; // eax#6
__int32 dwResult; // eax#7
DWORD dwResultVar; // esi#9
int v8; // [sp+8h] [bp-8h]#1
int v9; // [sp+Ch] [bp-4h]#1
LOWORD(v8) = 0;
*(int *)((char *)&v8 + 2) = 0;
HIWORD(v9) = 0;
if ( !hServer )
{
hServerVar = _pRpcHandle;
if ( !_pRpcHandle )
{
hServerVar = SfcConnectToServer(0);
_pRpcHandle = hServerVar;
if ( !hServerVar )
return 0x6BA; // RPC_S_SERVER_UNAVAILABLE
}
hServer = hServerVar;
}
nts = SfcRedirectPath(lpSrc, (int)&v8);
if ( nts >= 0 )
dwResult = SfcCli_FileException((int)hServer, v9, Unknown).Simple;
else
dwResult = RtlNtStatusToDosError(nts);
dwResultVar = dwResult;
MemFree(v9);
return dwResultVar;
}
Also as one further warning (Unless you know what you are doing!!!) do not attempt to use this program, ALWAYS ALWAYS ALWAYS backup your system files before deletion or alteration.
What this program will do is disarm WFP for 60 seconds whilst you intercange or amend your files. Example usage for example is:
WfpReplacer.exe c:\windows\Notepad.exe (Errorlevel true or false will be produced on execution).
Best Regards
David

Cannot fix an "invalid handle" error in WinAPI when trying to do anything requiring it

I am trying to play around with WinAPI to be able to manipulate the console, mostly just be able to write whatever I want, wherever I want without having to rewrite the whole console. I remember that I once got it to work earlier but that was long ago and I seem to have lost that code... whoops.
Anyway, I remember, that I succeeded with much less effort than what it is taking me now.
I am using this MS Docs page for reference and I remember using it earlier, successfully.
Right now, there is truly only a couple of lines that I am trying to get to work:
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hndl = GetStdHandle(STD_INPUT_HANDLE);
if (hndl == INVALID_HANDLE_VALUE)
{
cout << "Invalid handle, error " << GetLastError();
return 0;
}
long unsigned int *chars_written = new long unsigned int;
if (!WriteConsoleOutputCharacter(hndl, "mystring", 8, {20, 30}, chars_written))
{
cout << "Could not write, error " << GetLastError();
return 0;
}
return 0;
}
The result is the console window displaying "Could not write, error 6" and then ending the application.
Error 6, according to System Error Codes is "the handle is invalid".
What am I doing wrong? I must be missing something.
It seems to be important that I am trying to work in Code::Blocks.
BONUS: I tried MS Visual Studio with the full WinAPI SDK (some important parts seem to be missing in Code::Blocks) and, while the main problem is the same, the functions in MS Visual Studio don't seem to all fit the official reference I am using, e.g. WriteConsoleOutputCharacter requires an LPCWSTR as its 2nd argument instead of a LPCSTR as mentioned in the source and as works in Code::Blocks. Windows Data Types
Edit: I found that WriteConsoleOutputCharacter is a macro actually, and is defined differently between Code::Blocks and MS Visual Studio, as two different, existing in both versions functions: WriteConsoleOutputCharacterA() and WriteConsoleOutputCharacterW(), which sadly, is not mentioned in the MS Docs.
Thank you in advance,
Maurice.
First of all WriteConsoleOutputCharacter() requires a wide string as argument, while you're passing a normal string "mystring" as argument. To make it a wide literal, you just add the letter L as a prefix - e.g: L"mystring". And the program is giving you an error because the handle you're retrieving is an input handle (STD_INPUT_HANDLE). Meanwhile, if you want to output to the console through a handle you need to retrieve an output handle (STD_OUTPUT_HANDLE).

How can I query a MS SQL Compact Server 3.5 database in C++ not using the OLE DB API?

I have the dlls and the include files of MS SQL Compact Server 3.5. How can I use it without OLE DB? I just want to load the dlls and invoke the necessary methods myself, no COM please.
Does anyone know the API?
EDIT
If this is not possible is there a fully functional example in C++ demonstrating accessing a database using the MSSQL Compact Server edition?
I just noticed you mentioned no COM. If it weren't for that I would have suggested ADO. Recently I posted some OLEDB code for Raw C++ code to display the names of tables in an SQL compact server using OLE DB that you may find useful.
Otherwise, if you wish want to see my ADODB in C++ answer (that involves COM), I've worked through converting a ADODB VBScript example:
Dim con, rs
Set con = CreateObject("ADODB.Connection")
REM con.Provider = "Microsoft.SQLLITE.MOBILE.OLEDB.3.0"
con.Provider = "Microsoft.SQLSERVER.CE.OLEDB.3.5"
con.Open "InsertYourDatabase.sdf"
Set rs = con.Execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES")
While not rs.EOF
WScript.Echo rs.Fields.Item(0).Value
rs.MoveNext
Wend
To use ADODB in C++ is somewhat arduous, but, it is possible. The following C++ console application shows how to do this by using #import on the ADODB library:
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <oleauto.h>
#include <atlbase.h>
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" raw_interfaces_only, raw_native_types, no_namespace, named_guids
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Open a SQL Server CE 3.5 database.
CComPtr<_Connection> spConnection;
hr = spConnection.CoCreateInstance(CLSID_Connection);
//hr = spConnection->put_Provider(CComBSTR(L"Microsoft.SQLLITE.MOBILE.OLEDB.3.0"));
hr = spConnection->put_Provider(CComBSTR(L"Microsoft.SQLSERVER.CE.OLEDB.3.5"));
hr = spConnection->Open(CComBSTR(L"InsertYourDatabase.sdf"), CComBSTR(L""), CComBSTR(L""), -1);
// Execute a query.
CComPtr<_Recordset> spRecordset;
CComVariant varRecordsAffected;
hr = spConnection->Execute(CComBSTR(L"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"), &varRecordsAffected, -1, &spRecordset);
// Fetch the results.
VARIANT_BOOL bEOF = VARIANT_TRUE;
hr = spRecordset->get_EOF(&bEOF);
while (SUCCEEDED(hr) && bEOF != VARIANT_TRUE)
{
// Fetch the TABLE_NAME.
CComPtr<Fields> spFields;
hr = spRecordset->get_Fields(&spFields);
CComPtr<Field> spField;
hr = spFields->get_Item(CComVariant((int) 0), &spField);
CComVariant varTableName;
hr = spField->get_Value(&varTableName);
// Display the record.
if (varTableName.vt == VT_BSTR)
{
wprintf(L"%s\n", V_BSTR(&varTableName));
}
// Move to the next record.
hr = spRecordset->MoveNext();
bEOF = VARIANT_TRUE;
hr = spRecordset->get_EOF(&bEOF);
}
// Release smart pointers.
spRecordset = NULL;
spConnection = NULL;
CoUninitialize();
return 0;
}
You can extract the IDL of ADODB by using OleView (on my PC it was installed with Visual Studio under C:\Program Files\Microsoft SDKs\Windows\v7.0\Bin\OleView.exe). Once you have OleView running:
File > View TypeLib ...
Navigate to C:\Program Files\Common Files\System\ADO\msado15.dll
Click Open
This will give you a full syntax of the ADODB library which will help you use it in C++. You can also refer to Microsoft MSDN's ADO API Reference.
I do not think that is possible - http://msdn.microsoft.com/en-US/library/ms174579(v=SQL.90).aspx - an OLEDB sample is available here: http://archive.msdn.microsoft.com/sqlce/Release/ProjectReleases.aspx?ReleaseId=3808
For those who are interested in how the issue is resolved.
One does not have to install SqlCE in order to use its COM API.
Here are the steps:
Make sure you have the right dlls.
For SqlCE 3.0 the dlls are:
sqlceca30.dll
sqlcecompact30.dll
sqlceer30xx.dll
sqlceme30.dll
sqlceoledb30.dll
sqlceqp30.dll
sqlcese30.dll
for SqlCE 3.5:
sqlceca35.dll
sqlcecompact35.dll
sqlceer35EN.dll
sqlceme35.dll
sqlceoledb35.dll
sqlceqp35.dll
sqlcese35.dll
and for SqlCE 4.0:
sqlceca40.dll
sqlcecompact40.dll
sqlceer40EN.dll
sqlceme40.dll
sqlceoledb40.dll
sqlceqp40.dll
sqlcese40.dll
You may also need the .h file for the definitions of the respective COM classes. I have these:
sqlce_err.h
sqlce_oledb.h
sqlce_sync.h
Given an sdf Sql CE database file you have to determine which version it belongs to.
Google for it, basically you need to read the first 16 bytes of the file, find the magic number and it determines the version.
Knowing the Sql CE version, locate the directory containing the respective Sql CE dlls (see above).
Call ::LoadLibrary win32 API for the sqlceoledbNN.dll, where NN is 30, 35 or 40. It depends on other dlls, so they must be near it.
Call ::GetProcAddress win32 API to get the address of the exported DllGetClassObject function.
Having at hand the pointer to the DllGetClassObject you are ready to exercise the COM API provided by the Sql CE.
"Enjoy"

TTS C++ program can only run on developing PC

I followed the instructions given by the SAPI 5.1 “Text-to-Speech Tutorial”, compiled the sample code given by the tutorial(The instructions and the sample code are the same as those given by SAPI 5.3 TTS Tutorial! and SAPI 5.4 TTS Tutorial!). The compiled program works fine on the XP PC where it was built, but it will not work on other XP and Win 7 PCs. The other 2 PCs have no Speech SDK installed. But the SAPI 5.1 demo program TTSApp and MS Excel 2003 Text-to-Speech function work very well on these 2 PCs.
Why the tutorial program can only run on the development PC and other 2 programs can run on all PCs?
The development PC has XP sp3, Visual Studio 2008 and SAPI 5.1 installed. When I build the project I selected pre-compiled header. In step 1 below, there is no “#endif” in the stdafx.h file so these lines are inserted after "#include ".
Error message from the XP PC: this application failed to start because the application configuration is incorrect. Reinstall the application may fix this problem.
Error message from Win7 PC: The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail
Below is the tutorial.
Microsoft Speech SDK
SAPI 5.1
Text-to-Speech Tutorial
This tutorial covers a very basic text-to-speech (TTS) example. The console application is one of the simplest demonstrations of speech. It is the "Hello World" equivalent for TTS. An equivalent sample for a Windows application using a graphical interface (and event pump) is available in Using Events with TTS.
The sample builds up from the simplest (though nonfunctional) COM framework to speaking a sentence. Steps are provided for each new function. The sample even goes one step beyond demonstrating the use XML tags to modify speech. The Complete Sample Application is at the bottom of the page.
Step 1: Setting Up The Project
Step 2: Initialize COM
Step 3: Setting Up Voices
Step 4: Speak!
Step 5: Modifying Speech
Step 1: Setting up the project
While it is possible to write an application from scratch, it is easier to start from an existing project. In this case, use Visual Studio's application wizard to create a Win32 console application. Choose "Hello, world" as the sample when asked during the wizard set up. After generating it, open the STDAfx.h file and paste the following code after "#include " but before the "#endif" statement. This sets up the additional dependencies SAPI requires.
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override something,
//but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
Code Listing 1
Next add the paths to SAPI.h and SAPI.lib files. The paths shown are for a standard SAPI SDK install. If the compiler is unable to locate either file, or if a nonstandard install was performed, use the new path to the files. Change the project settings to reflect the paths. Using the Project->Settings. menu item, set the SAPI.h path. Click the C/C++ tab and select Preprocessor from the Category drop-down list. Enter the following in the "Additional include directories": C:\Program Files\Microsoft Speech SDK 5.1\Include.
To set the SAPI.lib path:
1. Select the Link tab from the Same Settings dialog box.
2. Choose Input from the Category drop-down list.
3. Add the following path to the "Additional library path":
C:\Program Files\Microsoft Speech SDK 5.1\Lib\i386.
4. Also add "sapi.lib" to the "Object/library modules" line. Be sure that the name is separated by a space.
Step 2: Initialize COM
SAPI is a COM-based application, and COM must be initialized both before use and during the time SAPI is active. In most cases, this is for the lifetime of the host application. The following code (from Listing 2) initializes COM. Of course, the application does not do anything beyond initialization, but it does ensure that COM is successfully started.
#include <stdafx.h>
#include <sapi.h>
int main(int argc, char* argv[])
{
if (FAILED(::CoInitialize(NULL)))
return FALSE;
::CoUninitialize();
return TRUE;
}
Code Listing 2
Step 3: Setting up voices
Once COM is running, the next step is to create the voice. A voice is simply a COM object. Additionally, SAPI uses intelligent defaults. During initialization of the object, SAPI assigns most values automatically so that the object may be used immediately afterward. This represents an important improvement from earlier versions. The defaults are retrieved from Speech properties in Control Panel and include such information as the voice (if more than one is available on your system), and the language (English, Japanese, etc.). While some defaults are obvious, others are not (speaking rate, pitch, etc.). Nevertheless, all defaults may be changed either programmatically or in Speech properties in Control Panel.
Setting the pVoice pointer to NULL is not required but is useful for checking errors; this ensures an invalid pointer is not reused, or as a reminder that the pointer has already been allocated or deallocated
#include <stdafx.h>
#include <sapi.h>
int main(int argc, char* argv[])
{
ISpVoice * pVoice = NULL;
if (FAILED(::CoInitialize(NULL)))
return FALSE;
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
pVoice->Release();
pVoice = NULL;
}
::CoUninitialize();
return TRUE;
}
Code Listing 3. Bold text represents new code for this example.
Step 4: Speak!
The actual speaking of the phrase is an equally simple task: one line calling the Speak function. When the instance of the voice is no longer needed, you can release the object.
#include <stdafx.h>
#include <sapi.h>
int main(int argc, char* argv[])
{
ISpVoice * pVoice = NULL;
if (FAILED(::CoInitialize(NULL)))
return FALSE;
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
hr = pVoice->Speak(L"Hello world", 0, NULL);
pVoice->Release();
pVoice = NULL;
}
::CoUninitialize();
return TRUE;
}
Code Listing 4. Bold text represents new code for this example.
Step 5: Modifying Speech
Voices may be modified using a variety of methods. The most direct way is to apply XML commands directly to the stream. The commands are outlined in XML Schema. In this case, a relative rating of 10 will lower the pitch of the voice.
#include <stdafx.h>
#include <sapi.h>
int main(int argc, char* argv[])
{
ISpVoice * pVoice = NULL;
if (FAILED(::CoInitialize(NULL)))
return FALSE;
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
hr = pVoice->Speak(L"Hello world", 0, NULL);
// Change pitch
hr = pVoice->Speak(L"This sounds normal <pitch middle = '-10'/> but the pitch drops half way through", SPF_IS_XML, NULL );
pVoice->Release();
pVoice = NULL;
}
::CoUninitialize();
return TRUE;
}
Code Listing 5. Bold text represents new code for this example. This is the complete code sample.
You have to install the C++ runtime which comes with an installer (redist).
VC++ 2008 redist
VC++ 2008 SP1 redist
Which one to install depends on the VS service pack - if SP1 then SP1 if not the not.
EDIT: A second way is to link the runtime libraries static. This increases the size of the executable but you don't need additional requirements on the target machine.

Why does my code fail to create a directory in "C:\Program Files" under Windows 7?

I am using Windows 7 and I have to run one program in that windows but that program working in Windows XP. This is a Visual C++ program and I am using Visual Studio 2008 for this. When I am running my application, it does not throw any errors, but it does not create a directory in "c:\program files\". So can anyone help me to create directory and exe file?
This is the code I am using:
char szAppPath[MAX_PATH];
char szFileName[MAX_PATH];
DWORD dwResult;
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
dwResult = ExpandEnvironmentStrings( NULL, szAppPath, MAX_PATH); // "%ProgramFiles%"
// do same for NSim directory
strcat(szAppPath,"\\NSim");
hFind = FindFirstFile(szAppPath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
//Directory Does't Exists create New
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
MessageBox("Unable to Create N-SIM directory","NSim Installer");
return ;
}
}
else
{
//check if is directory or not
if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
MessageBox("Can't Create N-SIM directory\n Another file with same name exists","NSim Installer");
return ;
}
FindClose(hFind);
}
//***************************************N-SIM Application****************************
strcpy(szFileName, szAppPath);
HRSRC hRes;
if( bRegister == FALSE)
{
strcat(szFileName,"\\NSim.exe"); //make same name of the Client & Server in program file
hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_LANSIMSERVER),RT_RCDATA);
if(flagUpgrade ==0)
{
CString trial = installationDate(); //----- Detemine Expiry Date -----
setRegistry(trial);
}
}
It's a file permissions issue, plain and simple. Programs can't just go rooting around system directories in Windows 7. That's why it works "properly" in Windows XP, but not in newer versions.
I can't tell for sure, but it looks like you're trying to write an installer. If so, why are you reinventing the wheel? There are tons of great setup utilities available—Visual Studio provides a setup project that you can customize to your needs, or look into Inno Setup, my personal favorite. A Google search will turn up plenty of other options that have already solved this problem for you, and innumerable others.
If this isn't an installer, and you're just trying to store application and/or user data in the Program Files folder, I highly recommend that you look elsewhere. You weren't supposed to shove data into the app folder under earlier versions of Windows, and Windows 7 just cuts you off at the knees if you do this. Your best bet is to follow the recommendations that existed from the beginning: Investigate the user and common Application Data folders carefully. Use the SHGetKnownFolderPath function to retrieve the full path to a known folder using its KNOWNFOLDERID. A couple of suggestions:
FOLDERID_ProgramData (a shared program data directory for all users)
FOLDERID_LocalAppData (a per-user program data directory, non-roaming)
FOLDERID_RoamingAppData (a per-user program data directory, roaming)
Alternatively, you can try running the application as an Administrator. You might want to look into creating a manifest that indicates the application requires administrator-level permissions to execute.
[edit] I edited the code in the question for readability and removed the commented out code (to see the wood for the trees). It is now obvious that nothing initialises szAppPath before calling strcat(), and calling ExpandEnvironmentStrings with NULL as the first argument is undefined (and certainly useless). Calling strcat() on an unitialised string is not likely to have the desired result. This may be an artefact of not posting the real code, or even of other peoples edits (including mine).
CreateDirectory sets the system error code on error; if you want to know what went wrong, check it! Any answer you get here will be an educated guess.
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
DWORD errorcode = GetLastError();
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
return ;
}
If you just want to get the error code and look it up manually, then a complete directory of codes is available on MSDN, here, I would guess that ERROR_ACCESS_DENIED
(5) is most probable. A more elaborate example of error code display is given here.
windows7?
Ok, the problem is not with your program. Its with the file system permissions in Windows 7. User programs cannot create files there.
I think the problem is lack of privileges. You can debug your project to see whether the CreateDirectory function sets an error as ERROR_ACCESS_DENIED, if it does, you should make your program run with an administrator privilege. Add manifest in your project to do so.
It is intended to protect your computer against attack. Well maybe. Or Microsoft deciding to tell you what you are and not allowed to do on your own computer.
In any case you can change your UAC settings if you really have to write there in that way although that obviously exposes you to risk.
Otherwise play nice and do things the Microsoft way, using a proper installer.