Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Compile/Run C programs from AHK w/o temp files


  • Please log in to reply
33 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
There are already scripts posted to the Forum, which pass a program stored in an AHK string to a compiler/interpreter, and retrieve the result. E.g., for Lua the script here works fine, but it uses temporary files, which can cause problems with name conflicts, write permissions, etc. To avoid this problem, we need a compiler, which takes its input from memory, and writes its output also to memory.

The smallest C compiler of this kind I know of is tcc, Fabrice Bellard's Tiny C Compiler. For the easiest use it has to be compiled to a Windows dll, which needs some tweaking, described by Adel Amro. You can also download the version I used from here. Just unzip it to a directory of your choice. I used c:\tcclib. It has two subdirectories, the original "include" and "lib" of tcc, with the compiled libtcc.dll and libtcc.def added. All together about 800KB. (If you install tcc, you only need to copy these two files to its "lib" subdirectory, all others are already there.)

We interface AHK to this dll, with appropriate dllcall's. There are not too many of them:

"LoadLibrary" to keep the dll in memory while needed
"tcc_new" to create a C compiler context (allocate memory, establish internal data structures)
"tcc_add_sysinclude_path" to tell it where its include (header) files are
"tcc_add_library_path" to tell it where the library is
"tcc_compile_string" to compile a program stored as a string
"tcc_run" to run it
"tcc_delete" to remove the compiler context
"FreeLibrary" to unload the dll from memory.

We have to tell the C program, where in memory to write its result. For that we allocate a large enough AHK variable, and write its <address> directly to the C source code as
char* _result = <address>;
The C program has to write its result as a string to the place _result points to, which is our AHK variable, directly available after the program terminates.

The usage is simple:
1. Assign the C source code to an AHK variable, as string. Make sure that all % are escaped with `, there is no ; or ) in the first position in a continuation line.
2. You can have references to AHK variables between % signs (%i%)
3. Include a declaration in main(): "char* _result;". The target of this pointer has to be updated with the result we want to see from the C program.
4. Run the C program with the Run function below. Its parameters are: the path to the directory containing libtcc.dll, the AHK string containing the C program and optionally an upper bound on the length of the returned AHK string:
#NoEnv
MsgBox % Run("c:\tcclib",p:="main(){char*_result;strcpy(_result,""Hello World"");}")

i = 101
C_prog =       ; floating point support
(
#include <math.h>
double f(double x) { return sqrt(x); }
main() {
  char* _result; // needed to export the result
  int i = %i%;   // AHK varible reference %i%
  sprintf(_result,"f(`%u) --> `%0.17g",i,f(i)); } // Escape `%
)
MsgBox % Run("c:\tcclib",C_Prog)

h = 0x12345678
PROG =         ; assembler support
(
typedef unsigned long UInt32;

UInt32 BSWAP(UInt32 x) { // Byte reversal
    __asm__("bswap `%0" : "=r" (x) : "0" (x));
    return x; }

main() {
  char* _result;
  sprintf(_result,"ByteSwap of `%x = `%x",%h%,BSWAP(%h%)); }
)
MsgBox % Run("c:\tcclib",PROG)


Run(libpath, ByRef prog, ResultLen=999) {
   If (0 = hModule := DllCall("LoadLibrary",Str,libpath . "\lib\libtcc.dll")) {
      MsgBox Cannot load libray %libpath%\lib\libtcc.dll
      Return
   }
   Context := DllCall("libtcc\tcc_new", "cdecl UInt")
   DllCall("libtcc\tcc_add_sysinclude_path", UInt,Context, Str,libpath . "\include", "cdecl UInt")
   DllCall("libtcc\tcc_add_library_path", UInt,Context, Str,libpath . "\lib", "cdecl UInt")

   VarSetCapacity(x,ResultLen)
   prog := RegExReplace(prog,"(char\s*\*\s*_result)([^;]*)", "$1 = " . &x, Count)
   If (Count = 0)
      MsgBox Found NO char* _result...;
   Else {
      If (DllCall("libtcc\tcc_compile_string", UInt,Context, Str,prog, "cdecl UInt") || ErrorLevel)
         MsgBox Compile error
      Else DllCall("libtcc\tcc_run", UInt,Context, "cdecl UInt")
   }
   DllCall("libtcc\tcc_delete", UInt,Context, "cdecl")
   DllCall("FreeLibrary", UInt, hModule)
   VarSetCapacity(x,-1) ; set correct length
   Return x
}
The tcclib.zip file contains a TEST.ahk file in the topmost level (similar to the script above). Unzip it anywhere, with maintaining the relative paths of files. Double clicking on TEST.ahk (or running it otherwise) should pop up a Hello World message box, and one with
f(101) --> 10.04987562112089
testing sprintf and the floating point library. When everything works, you can delete this TEST.ahk, or keep it for a quick reference.

Edit 20070429: Added #NoEnv, to avoid possible conflicts with environment variables.
Edit 20070429: Added TEST.ahk to tcclib.zip.
Edit 20070429: Fixed bug: added VarSetCapacity(x,-1) before Return. #NoEnv is not normally needed any more, but safer to keep it.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Really amazing; great resource! Thanks for putting it all together and explaining it so clearly.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
Absolutely fantastic.
I will go to try this imediately :D

If #PP directive is ever added I will make real we have something like asm directive in C. So we could write:

ahk_code...
 ...
 ...
 tcc
 {
    sprintf(...)    
 }
without trouble of calling dll, escaping % etc...
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Looks great.
It's too bad that I don't know C.

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004
Interesting. Thanks :)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
Hm... I get empty msg box .
I extracted archive in C: and run your script. Empty msgbox.
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Please verify if the c:\tcclib directory contains 2 subdirectories, inc and lib. (Unzip has to be performed, so the paths of files are re-created.) If all is fine, add before the Return x command:
MsgBox % "address = " . &x . "`n`nProgram =`n" . prog
The address value has to be the same as the address in the program text " char* _result = ...

If that is OK, too, another problem could be a missing or wrong C runtime library (in my PC it is C:\WINDOWS\system32\msvcrt.dll).

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
If there are no error messages, most likely the sprintf function is not found. If the runtime C library is there, you could try to explicitly include the relevant header file: #include <stdio.h>.

The simplest C program should work, too:
MsgBox % Run("c:\tcclib",p:="main(){char*_result;strcpy(_result,""Hello World"");}")
If not, you could try adding #include <string.h>

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
No changes.

Ok, this is what I did

1. Example tcc\tcc -Iinclude -Llib examples/fib.c works. It produces working exe.

2. I downloaded your archive and extracted it to C so I have the same structure as you.

3. I saved your example in c:\tclib and executed it. I was changing example to something easy like "hello word" sprintf but nothing worked.

4. I downed latest AHK, didn't help

5. I checked program you are sending after regExp. The _result line is correct, memory is the same. Furhtermore I checked variable with HexView. This is the code I was using:

x := "o my god"
x := Run("c:\tcclib",p:="#include <string.h>; main(){char*_result;strcpy(_result,""Hello World"");}")

HexView(x)

This is what I got
00 68 20 6D   79 20 67 6F   64 00 00 00   | .h my god...

This code works from command line
main(){char s[128]; sprintf(s, "Hello World");printf(s);}

So, I don't know what happened....
ALso, when I make syntax error, I got "compile error", when I fix it I don't, so it seems like this part is working correctly.

Can you make me standalone thing that can be extracted and runned ?
So, if it is possible for you, use relative names in includes. I will run all examples from the root of the project so my ahk script will be executed from tcclib folder. So the bottom line is that you send me script, I extract it and click ahk script without having to tweak anything.

I tryed this and it seems to work like that:
Run(A_ScriptDir, ...)

I checked on 2 different computers with different service packs, and the same thing happens.
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Add #NoEnv in front of the script. Maybe, some environment variables conflict with internal names.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I updated the tcclib.zip file. It contains now a TEST.ahk file in the topmost level. Unzip it anywhere, with maintaining the relative paths of files. Double clicking on TEST.ahk (or running it otherwise) should pop up a Hello World message box, and one with
f(101) --> 10.04987562112089
testing sprintf and the floating point library.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006

Add #NoEnv in front of the script. Maybe, some environment variables conflict with internal names.

ROFL :D

It was that. I don't see why though... I checked what variable could do that and I don't see anything.

I really don't know what may cause this. This is the list of my EnvVars:

_NT_SYMBOL_PATH	srv*DownstreamStore*http://msdl.microsoft.com/download/symbols
ALLUSERSPROFILE	C:\Documents and Settings\All Users
APPDATA	C:\Documents and Settings\neutrino\Application Data
AUDIOROOT	D:\audio
COMMANDER_DRIVE	D:
COMMANDER_INI	D:\Utils\Total Commander\wincmd.ini
COMMANDER_PATH	D:\Utils\Total Commander
CommonProgramFiles	C:\Program Files\Common Files
COMPUTERNAME	NEUTRINO
ComSpec	C:\WINDOWS\system32\cmd.exe
CSOUND_HOME	C:\audio\--- sound hack\Csound\_tools\silence\bin\bin
INCLUDE	C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\include\
JAVA_HOME	C:\Program Files\Java\jre1.5.0_01
LIB	C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Lib\
LOGONSERVER	\\NEUTRINO
NUMBER_OF_PROCESSORS	1
OS	Windows_NT
Path	C:\Perl\site\bin;C:\Perl\bin;C:\Program Files\Windows Resource Kits\Tools\;C:\Program Files\Borland\Delphi7\Bin;C:\Program Files\Borland\Delphi7\Projects\Bpl\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;D:\Utils\_Console;D:\Utils\_Console\Rkit;D:\Utils\_Console\SysInternals;D:\Utils\_Console\SysInternals\PSTools;D:\Utils\_Scripts\Batch;D:\Utils\_Tools;C:\Program Files\Common Files\iZotope\Runtimes;C:\Program Files\Microsoft Visual Studio\Common\Tools\WinNT;C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin;C:\Program Files\Microsoft Visual Studio\Common\Tools;C:\Program Files\Microsoft Visual Studio\VC98\bin;c:\perl\bin
PATHEXT	.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
PROCESSOR_ARCHITECTURE	x86
PROCESSOR_IDENTIFIER	x86 Family 15 Model 3 Stepping 4, GenuineIntel
PROCESSOR_LEVEL	15
PROCESSOR_REVISION	0304
ProgramFiles	C:\Program Files
PYTHONPATH	;C:\audio\--- sound hack\Csound\csound 5
SILENCE_HOME	C:\audio\--- sound hack\Csound\_tools\silence\bin
SSDIR	D:\Work\Code\CSound\SSDIR
STK_HOME	C:\audio\--- sound hack\Csound\_tools\silence\bin
SystemDrive	C:
SystemRoot	C:\WINDOWS
TEMP	C:\WINDOWS\TEMP
TMP	C:\WINDOWS\TEMP
USERPROFILE	C:\Documents and Settings\neutrino
UTILS	D:\Utils
VS71COMNTOOLS	C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\
windir	C:\WINDOWS

The funny thing is that I tried on different computer that has only standard env vars as system was recently reinstalled.

I just tried your new version and it works fine.
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
You are right, it is funny. I filed a bug report.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
If you embed a longer C program, and it has errors, a "Compile error" message is not very helpful. For more information, there is a callback mechanism in libtcc:
/* set error/warning display callback */
void tcc_set_error_func(TCCState *s, void *error_opaque,
                        void (*error_func)(void *opaque, const char *msg));
Ideally, this callback function would be a MsgBox, showing the error found.

Has anyone succeeded with using AHK functions in callback?

There is an ugly workaround: at a compile error we can write the program into a temp.c and run the standalone tcc compiler, which shows its error messages. After reading it, the script can delete the temporary file. The main point of this post was, however, to avoid temporary files, and although error handling is not the normal operation, it was nicer to do the callback. Any ideas?

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006

If you embed a longer C program,

I would make it to work as normal c code first , without AHK in that case. Pretty much the same as your "ugly workaround" but manuely.

Has anyone succeeded with using AHK functions in callback?

One of those things that are not possible to do in AHK. Its pitty Chris don't want to use C/Invoke in AHK which is used in Lua, Java and some other languages for FFI and it has very nice, dllcall, struct and callback encapsulation.

So, no ideas for this case, except if Chris support callback which is IMO very needed as it will allow hooks that can be used to automate almost everything in the system.
Posted Image