Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

FoxPro 9.0 - Build .exe 1

Status
Not open for further replies.

MajklPan

Programmer
Jan 11, 2023
74
0
0
CZ
Hi,
I don't know how to correctly create the installation file of the project, because if I create only an exe file (see picture) and replace it in the project, it no longer works correctly.
1_neotor.png


How should I create an installation file so that the user can just install the program and it works?

I tried this procedure and it doesn't work, I just keep getting a pjx file (picture 2)
2_jjm6gv.png

In FoxPro, you can create an installation file using the "Setup Wizard" or "Save As" tool.

1.Launch the FoxPro program.
2.From the main menu, select "File" and then "Save As".
3.Choose "Setup" as the file type.
4.Enter a name for the installation file and the location where you want to save it.
5.Click on the "Save" button and then the "Setup Wizard" window will appear or the saving process will begin.
In the Setup Wizard, click on the "Next" button and select the files that you want to include in the installation file.
6.Click on the "Next" button and set up the installation options, such as the target folder or installation preferences.
7.Click on the "Next" button and create informational pages that will be displayed during the installation.
8. Click on the "Finish" button to create the installation file.
Note: It's important that you have write access to the location where you want to save the installation file.

This guide explains how to create an installation file in FoxPro that includes all necessary files, settings, and instructions for installation.

thank you for the advice
 
Just use that instead of the old cCestaProgramu=LEFT(SYS(16),RAT(SYS(16),2)-1). This is obsolete. The expression with LEFT up to the position of a last or next to last backslash is just a complicated way of doing what the inbuilt JUSTPATH function does. And as JUSTPATH removes a trailing backslash calling it repeatedly removes as many directory levels as necessary, one more for the IDE than for the EXE.

And you see, SYS(16) returns the name of the main program (precisely its FXP) when run in the IDE. And that is one directory deeper than the EXE, which is returned when running SYS(16) in the built EXE. Then VFP makes no fuss about where the actual PRG or FXP that executes right now was located, it just takes the EXE path for whatever PRG or class method is inside the EXE. Thus you have the problem this differs in IDE and EXE.

There are more speaking things than SYS() functions, by the way, not only on this topic:
In general PROGRAM() is the current program, but IIRC that never gives you a path just the PRG name or method/event name.
You can use _vfp.activeproject.HomeDir to get the directory of the PJX when running in the IDE - with a little catch, though: If you open more than one PJX in the IDE the one you start may not be the active project and so you get the HomeDir of another project.
And the third thing in that category is _vfp.servername. At runtime, it's the path and file name of your EXE. But within the IDE that will be HOME()+"vfp9.exe", which is not helpful, of course.

None of that is always available, too, In the EXE you don't have _vfp.activeproject, as that's only a property of the IDE. And even what is available in both situations is not the same in IDE and EXE. One thing is workable: If you move the main program into the same folder as the PJX/EXE, then JUSTPATH(SYS(16)) becomes universal for IDE and EXE within the main program.

So, in short, it's more complicated than it should be. You could also program in a way you expect the current/default path to be okay already and do nothing. That way you allow control of your EXE default path from outside, as the directory you set in a shortcut (.lnk) file executing your EXE can determine what directory is the default. On the other hand that's maybe something you actually don't want to be controllable from outside. Sys(16) is the right thing to use in the aspect that it clearly returns what currently executes, it's just that it makes a difference in IDE and EXE. That means you have to handle it depending on what state you're running in, which you get from _vfp.startmode.

And why all the fuss about the current directory anyway? Well, others may just use SET PATH and push in any paths you could find something into it. I like to have a movable base directory everything else is derived from with relative paths, and the one typical base directory you pick is the location of the EXE. Many files like local configuration or reports you want users to be able to modify or local data and of course also DLLs should be in the same dir or in subdirectories of the EXE. So the EXE path is of major interest.

It's a topic I also don't remember needing to care much for when I developed C and C++, but maybe also because you typically do stuff that has very little to do with any external file. Instead, you often work with stdin/stdout/stderr streams, or even when programming something with ODBC you rely on a DSN configuring anything correctly, that may have to do with pathing like the location of DBFs or an Access database.

Chriss
 
Thanks for the explanation, I hope I got it right with my broken English.
That's what I read This code checks if the application is running in "StartMode" (variable _VFP.StartMode). If it is running in mode 0 (meaning the application was launched using a standard launcher file), it uses the JustPath() function twice on the variable SYS(16), which contains the path to the current launcher file. This gets the path to the directory in which the launcher file is located. If the application is not running in mode 0, it uses the JustPath() function once on the variable SYS(16) and this gets the path to the current launcher file. The result is saved in the variable cCestaProgramu.
So I tried replacing this path with:
5_bll3ka.png

This is how I compiled the file, created an .exe file and put it in the "Progs" folder. When starting the program, it immediately prints a message:
4_ud5qwn.png

It works in the IDE.

Sorry if I misunderstood, I'm really an amateur at this. Thank you for your patience.

EDIT:
If I placed the code as shown in the image, the exe file started without error, but behaves the same as in the beginning. Two buttons do not work.
3_hdylre.png
 
The second image is how you have it now?

You do compute cCestaProgramu in the old way there, at the bottom. No, this should not be done, this is getting the wrong path in the EXE, it only worksin the IDE.

You seem to have problems in the values that are loaded from GPT-memo.mem, but that's just guesswork. The only thing that will bring you forward is establishing error handling correctly, which you didn't manage so far.

The error handling has to be established as one of the first steps, so the ON ERROR line belongs to the top lines of your program.

So do two things:

Put the PROCEDURE errHandler code Mark gave you at the end of your main program.
Put this line at the top:
Code:
ON ERROR DO errHandler WITH ERROR( ), MESSAGE( ), MESSAGE(1), PROGRAM( ), LINENO( )

Chriss
 
Here's a slightly better version of Marks erorhandling:

Code:
PROCEDURE errHandler
   LPARAMETERS tnError, tcMess, tcMess1, tcProg, tnLineno

   _screen.visible = .T.
   Activate Screen
   Clear

   ? 'Error number: ' + LTRIM(STR(tnError))
   ? 'Error message: ' + tcMess
   ? 'Line of code with error: ' + tcMess1
   ? 'Line number of error: ' + LTRIM(STR(tnLineno))
   ? 'Program with error: ' + tcProg

ENDPROC

This just assures a) the _Screen is visible, b) it is activated and cleared before outputting the error information with simple ? commands. There are tons of options you can add to this. First AERRR() will give you an array of more error informations than you can get from the ON ERROR parameters, you could show this in a messagebox instead, which would just be a minor change, but allows you to specif messagebox button as response. You could put SUSPEND at the end of it so you can start debugging when an error happens. You could store the error information into a log file, etc.

But for the start, the essential improvement on the usual system error is that you will get the line number and the program that errored. You only know that for the usual system error as aftermath, if you'd have this error in the IDE and have the suspend button, that's not available in the EXE.

Chriss
 
One thing to teach you that's best put into a separate post:

MEM files, what are they? In short a dump of all memory variables, which you then can restore.

It seems your former colleague used them to load important information into a bulkload of public variables. This is bad style programming, actually, but what you currently have to deal with. The problem you have with MEM files is unless you know what command was used to create it, you don't know what exactly is in them, as such files are binary. Embedded inside the binary gibberish you'll see, if you open a mem file in notepad (or notepad+) will be variable names, and their valules. Numeric variables, for example, will have an 8 byte long float value after the name, I think. It's not documented how exactly mem files are structured, I just know they are not compatible in all VFP versions, which is another reason they are bad style to save states you need to restore later. It is simple solution to SAVE and RESTORE, that's out of question, it's taking more code to establish a DBF or INI for keeping config data, but it has big advantages about managing data in them, or even editing them without having code access, especially ini text files.

So how do you find out what's in a MEM? Besides loading it into a text editor to see variable names, which leaves you with enough guesswork to get even that wrong, as readable portions can be variable names or variable values. the only other viable way would be to make a before/after comparison and see which variables exist after a RESTORE that didn't exist beforehand. But also, which existing variable values have changed by the restore, as that can also happen.

And how do you know all currently used variable names? When using LIST MEMORY TO FILE vars.txt you get a readable variable dump.
So do this:
Instead of just doing RESTORE FROM (cCestaProgramu)+"\GPT_Memo.mem" ADDITIVE do this:

LIST MEMORY TO FILE variables1.txt
RESTORE FROM (cCestaProgramu)+"\GPT_Memo.mem" ADDITIVE
LIST MEMORY TO FILE variables2.txt

And then compare the two files variable1.txt and variables2.txt to see what came out of the GPT_Memo.mem file. And once you know that you may be able to fix it by changing variables and creating a fixed GPT_Memo.mem file from the changed variable values. It's your only fast path to get a mem file correct.

In the long run, I'd store whats configuration in a DBF or an INI, so it's easy to read and modify even without having VFP at hand.

Chriss
 
One last request: Please, don't post screenshots of code, post the code itself. It's utterly dificult if we need to post a code change, to first type the code in the image that cold just be there as code itself. I dont know why you do this at all, as it needs much more steps to create an image and embed it into a post, instead of just copy&pasting code.

Chriss
 
Thank you for the explanation.
I was just wondering what the GPT_memo.mem file contains and why it is created, or I think it could just be the result of the program not working.
It is a file that contains memo fields (areas for long texts) used in FoxPro tables. Memo fields allow storing large amounts of text or binary data in one field in a table. These files are typically associated with FoxPro tables and can be used to store notes, comments, or other long text data.

Can you please look at the file I sent in attachments? Can be opened in a text editor.

I followed your instructions. I still can't get the program to run.
Sorry for the pictures, I'll post them as code. Thank you for your patience.

 
 https://files.engineering.com/getfile.aspx?folder=239f0e1f-7e6e-4a32-9ec6-dadb9383300b&file=spousteci_program.prg
Have you at least established the error handling? Because that would tell you (and in turn us) in which line of your code you have the error "Function argument value, type or count is invalid." In itself that error won't tell you where to mend what, but if you know in which line, that boils down the possibilities very much, usually.

I don't know what makes you still hesitate, maybe I should give you a bigger motivation: If you finally establish error handling, that will help a lot not only about this one problem you have right now, but about any error. If a build does not show errors, that's no reason there are no errors, as you saw for yourself.

We wouldn't have to guess where maybe problems are, we would have an actual known point of failure to start looking.

I can point out a whole article about the topic of error handling. That will lead you astray even more than just establishing what we already gave to you, which is sufficient to get the necessary knowledge. The code alone won't help us. It's not a compilation problem, it's a problem that only shows at runtime. We already know that much.

So here it is, if you can't establish error handling by our advice, maybe a whole article will give you a better insight into it:

Chriss
 
And yes, please post code and not an image or an attachment of it.

Chriss
 
I see, thanks for the explanation. Please respect that I am an amateur at this. I was tasked with solving this problem and I'm glad this forum is active and the people here are helpful.
Anyway, I studied and implemented it, your instructions. When starting the IDE it gave me the following errors:
4_eysr2r.png

When creating an exe file and copying it to the progs folder, it throws the following errors.
Is this correct what you mentioned?

3_wkirdf.png

Thanks for your advice.
 
Now you have some more information. And it would be even more helpful, if you then follow that information. And, for example, look into the line which the error handler reported.

I see that you put your EXE into the PROGS subfolder. That's wrong, an EXE should always be built into the same folder as the PJX. That's not working out because now all your relative paths changed. You might have done that, as I told you that it's best to have main program and EXE in the same folder. Yet I also told you that doing that would right now possibly cause more pain than relief. And if you move something, then you move the main program into the folder of PJX and EXE, not the other way around. But don't do that now. The code using JUSTPATH twice for the IDE case and only once for the EXE case fixes that problem without moving around anything. Moving around files changes location of anything else in relation to that, and that can introduce more problems than you already have. Don't do that. Only do that in the long run and when you're ready to refactor code about pathing. Not now. You're alrady bogged down enough by the errors you already have.

Now about the errors:
As the first error points out a missing variable koncind, the logical thing to do is to look, whether this variable is declared and set in code previous to that line, or not. If there is no variable declaration LOCAL koncind and also no line that sets koncind to something, this code will of course error.

Here's a difference between compilation of C/C++/C# or VFP code: Foxpro doesn't care whether variables are declared or set in code, you can use code that has an uninitialized variable and the compiler will build that without reporting a compile error. One reason for it is, that there always are ways the compiler can't see or tell, that such variables will exist at the time of execution. One way this could become true is when a MEM file is restored. If the variable koncind is stored inside your GPT_Memo.mem file but this file is not found and thus a RESTORE does not happen all code that needs variables from GPT_Memo.mem fails, of course. What's bad about the IF FILE construct is that it avoids an error if GPT_Memo.mem is not found, but preventing that error, it runs into all kind of problems as the variables and their values are all not present.

We have had that in focus, already, didn't we? The essential variable that needs to be correct so GPT_Memo.mem is found is cCestaProgramu, that needs to have the path to the EXE, and the GPT_Memo.mem file has to be in the same directory, too. Since you moved the EXE inside PROGS, you now don't have GPT_Memo.mem in that folder. You're making things worse than they were.

I had a long explanation why a base path is important so all reltive paths are correct. And now you move the EXE and make all relative paths invalid that way. Please, think about the consequences of what you're doing. Not only that you move the EXE instead of the PR, I only told you to mmove the PRG and I told that in Conditionl I, in conjunctive. It's something you would do in a new project, or once you know more about the whole project. It's not iportant to only need one line of code instead of an IF with two branches. What's implortant is that you get the actual problems fixed, don't intriduce further problems.

----------------------------

The next step is to find out how koncind should be set, where it is declared and assigned its value, whether that is part of code before that erroring line or whether it would usually come from the RESTORE of GPT_Memo.mem, if that mem file is found.

Regarding the second error, it would obviously now be helpful to know the code of line 69 in spousteci_program. Then this will hint on what else to look for. Wouldn't it be logical to look into the line the error handler reports? And show it to us? Why did I stress out that knowing the location of an error is so valuable? Because then you can lookup the code.

I know you attached that prg but so you know it: While the forum scans every attachment for viruses, I don't download everything anybody here attaches, when I can avoid it, even though I also have my own antivirus protection in place, too. It's an unnecessary risk. Also, since you made some changes, I can't be sure what line 69 will be right now in your changed code.

You could really just concentrate on the essential line to post it here. Even if you're an amateur at VFP, you said you're a developer, please put just a little thought into what could be useful for us. We can't look over your shoulder, so if the error points out line 69. What do you do as a developer? You look into line 69. And then you let us look into it.

Chriss
 
One more thing on top of all of that, don't start an EXE with DO,
start it as an end user would also start it, double clicking on it in a file explorer or using a shortcut lnk to it.

If you DO an EXE that keeps _vfp.startmode at 0, the IDE mode. The normal mode for an EXE is 4, if you start it as an end user starts it, outside of the IDE.

Chriss
 
Thanks, unfortunately it doesn't have a solution for me, I'm not familiar with it and all the things I know about programming almost don't work here.

You were right, the program works if I don't copy it to the progs folder, because as you wrote, it doesn't find the path to GPT_MEMO.mem.

I just have a problem with the fact that we have an installation file that the end user installs and then just copies the .exe file into the progs folder, because I didn't know how to create an installation. I know it's not right, but it worked for me until now, and I was giving them the adjustments I made in VFP. (Just simple, standard changes, protocol name change, etc.)

I would probably have to create a new installation, right?
Because after installing an older version, where these modifications were not present, it looks like this:
25_mrljfi.png

exe file is currently located in the progs folder.

But really, the program runs and the buttons also work.
The program doesn't even print any errors.
Now just create the installation process.
 
The code as you have it will need the EXE outside of the progs folder.

If the EXE is in the prog folder GPT_memo.mem would need to be in the PROG filder too, and all kind of location adjustments would likely be necessary for the EXE to find things. So the previous EXE would need to be based on previous code that used cCestaPRogramu+"\..\GPT_MEMo.mem". All these kinds of paths must work, there only is SET PATH that would make VFP (also the EXE) look into other locations, if a file is not found in the place first searched.

But I don't see from here, whether SET PATH has such paths so files not at the location to be expected b the code are found nevertheless.

I would not care what old code worked, I would fix this problem from the ground up and make it right. And for that, the EXE should be in the folder abovve PROGS, and the installation should not need the Progs subfolder at all, as the EXE has all progs embeddded and built into the EXE, at least that's the default for inclusion/exclusion for project items that are PRG. It seems odd, that your working installations are like that.

One question comes up:: Are you having a GPT_MEMo.mem file on that levvel AND within Progs? Maybe the old expression is intentionally computing different directories for IDE and EXE so different mem files are read and set variables in different ways for EXE and IDE mode.

Chriss
 
MajklPan said:
...it worked for me until now...

You mean you created a new EXE and application users copied it into their installation folder and that worked?

I had the impression this is your get-go start of taking over this project and this is your initial problems. If it worked, can you go back to an earlier project state when it still worked and analyze the change that made this defunct? In fact, that's your only really viable option.

If you have a working installation of an older version, it would also surely help to get the mem file and perhaps the whole installation out of it to see what's in there that might have changed.

One thing is clear: A build without error of a VFP project is not telling you don't get a runtime problem. That's better in a strongly typed language as the C/C++/C# family. But also in such programming languages, you can have problems that only show at runtime. Anyway, the idea to let us look into the source code and point out an error the build/compiler did overlook is not getting to the root of the problem.

PS: When it comes to a setup, VFP comes with InstallShield Express to do that and you can use anything else free or commercial on the market to create setups. But that won't help. In a situation the installation already is done, updating an EXE by overwriting the EXE is a normal upgrade path to take. Of course, a user overwriting a file, even iff in the administrator group, will redirect this overwriting by UAC. So they better have permission to really overwrite the EXE.

I have done this in companies allowing such updates and they put the installation outside the system program files. But they implement a whitelisting of allowed executables, so you can't just put any EXE anywhere and rn it, only known EXEs are able to run, and that's implemented by group policies of windows and not by code which validats signatures and checksums, it's something not possible to simply override. But it then allows updates without a dedicated setup and still be safe against viruses manipulating the EXE.

Chriss
 
Here's a short demo of how important the default path even is while you build an EXE:

Code:
#Define BASEDIR Addbs('c:\programming\tek-tips\projects\buildproblems')

Try
   Mkdir (BASEDIR)
   Mkdir (BASEDIR+"progs\")
Catch
   *
Finally
   If !Directory(BASEDIR)
      Messagebox('please assure directory '+BASEDIR+' exists. Then redo '+Program())
      Quit
   Endif
Endtry

Create Project (BASEDIR+'buildproblems.pjx') Nowait Save
With _vfp.Projects(_vfp.Projects.Count)
   Strtofile('#define CONSTANT 1',BASEDIR+'constants.h')
   Strtofile('#define CONSTANT 2',BASEDIR+'progs\constants.h')
   TEXT To lcMainprg noshow
#include constants.h
MessageBox(CONSTANT)
   ENDTEXT
   Strtofile(LCMAINPRG,BASEDIR+'progs\main.prg')
   oMAINFILE = .Files.Add(BASEDIR+'progs\main.prg')
   .SetMain(oMainFile)
Endwith

Set Default To (BASEDIR)
Build Exe (BASEDIR+'buildproblemsA.exe') From BUILDPROBLEMS
Set Default To (BASEDIR+'progs\')
Build Exe (BASEDIR+'buildproblemsB.exe') From ..\BUILDPROBLEMS RECOMPILE

Copy&PAste that into a prg you save anywhere. Adjust the constant BASEDIR which will be where this prg code generates a new VFP project and builds two exe versions from it.
The two executables buildproblemsA.exe and buildproblemsB.exe will show 1 and 2 in their messagebox.

It shows that an #INCLUDE is not done relative to the location of the PRG it is in, but relative to the default path during the BUILD. It's not demonstrating or pointinng out you have a problem in location of header files and in their constants, but it points out the importannce of pathing, even during the build of an EXE.

I see the original expression of cCestaProgramu needs no difference in ID and EXE, when the EXE is put into the subdirectory progs of the project and in the final installation directory. But it could be important which path is current while you build, and even whether you tell the build to store the EXE directly into progs or whether you compile into the project folder and move the resulting EXE into progrs after the build.

In short: VFP is sensitive to paths. More sensitive than most people realize, as usually things are fine in that regard. When you open a pjx VFP usually puts you into the project home directory automatically. There are tools like environment manager that can influence this, and this can depend on which Windows account you use to open and compile a project, as environment manager information can be in the user profile. That's not likely an issue in your case, because up to now you managed to update the EXE.

Anyway, any reorganization of a project can cause a cascading effect. I still think the content of the gpt_memo.mem is not just texts used by the application but also contains important variables set to corret or incorrect values. If users modify this file to modify the texts, they can render it unusable for a RESTORE, as it's not just an ascii rtext file, it has variable names, and for string variables their string is in there, too, but the information about string length also is in there, binary. So extending a text you a) don't get a longer text, it's cut off at the former length b) you overwrite binary info about other variables and render the mem file unusable.

In the long run you should change from using a mem file to another file to store configuration and meta data. Template texts or text modules belong into a memo field of a dbf, for example, or simply into separate txt files you read in with FILETOSTR(). That makes many things less sensitive and easier to maintain.

Chriss
 
Sorry, I was out of the office. Yes, I also tried putting GPT_memo.mem in the Progs folder together with the exe file, but it no longer worked correctly.
I also think that it shouldn't contain a Progs folder at all, but historically this is how the developer did it and I'm not familiar with it, so I kept this structure.

Answer to the question: If you are wondering where the GPT_memo.mem file is located when installing the previous developer version, then it is located in the main installation folder, and the merge file is in the Progs folder.
-------------------------
Yes, for a simpler program that we still use, I tried it like this and it worked, unfortunately I analyzed it for a long time and tried to figure out what changed and compared both source codes, but it was without result.

Is it really important to insert this code into the prg, when the program already works for me after compiling and running the .exe file? Isn't it enough to create an installation file using "Inno Setup Compiler"?

EDIT: I didn't realize I had InstallShield Express on the Install CD, I had already uninstalled INNoSetup and installed InstallShield.
 
I also noticed that the program detects the current version on the server and compares it in the Progs folder:
2_nfyjnb.png


Here is a code snippet that refers to "KONCID".
Code:
&&SET LIBRARY TO SYS(2004) + "FoxTools.fll" ADDITIVE
SET LIBRARY TO foxtools
DIMENSION aFileVer[12]
nRetVal = GetFileVersion((cCestaProgramu)+"\progs\gpt_tisk_sestav.exe",@aFileVer) 
koncInd=AT(".",aFileVer[4],1)
cisVer1=VAL(SUBSTR(aFileVer[4],1,koncInd-1))
pocInd=koncInd+1
koncInd=AT(".",aFileVer[4],2)
cisVer2=VAL(SUBSTR(aFileVer[4],pocInd,koncInd-1))
cisVer3=VAL(SUBSTR(aFileVer[4],koncInd+1))
IF nRetVal = 0
&&   DISPLAY MEMORY LIKE aFileVer
ENDIF
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top