The FAQ list is organized into several categories. In each category, you will see a list of
commonly asked questions in short form. Please click on a question that interests you to get the full form
of the question and the related answer. Note, however, that some questions/answers are related to each other, and some
answers are logical consequences of previous ones. So, after reading the answer, it is recommended
to browse through surrounding quesion/answer pairs too, to get some more information.
Also, please visit the TIGCC Programming Message Board.
A lot of interesting topics about TIGCC programming are discussed there.
cout << "Hello world";
which produces 5 Kb long code...
parse error before 'void'
but the statement on which the error is reported was
int cexp;
I am really confused. First, I don't see any errors here, and second, I can't see any 'void' keywords here!
'cexp' is a function defined in
timath.h header file, and you can not use this name as a name
of a variable. Now you can say why you got such strange error message? See, the most of
functions in TIGCCLIB are translated by the preprocessor into constructions which perform
indirect function calls through a TIOS jump table. In other words, 'cexp' will
be replaced by the preprocessor into the indirect function call constructor whenever it is
used. So, the innocent statement like
int cexp;
will be converted into
int (*(void(**)(float,float,float*,float*))(*(long*)0xC8+1316));
which is a syntax error. And the error is just before 'void'.
I can not do anything against such hidden errors. Whenever you encounter strange errors without
obvious reasons, check whether you used reserved library name for your identifier. The chance
of making such errors is much smaller if you include only the necessary header files than if you
include the general header file tigcclib.h.
Undefined reference to ...
although my program seems correct. What's a problem?
'__'). For example, although
TIGCC supports very long (64-bit) integers ('long long' type, which is a
GNU C extension), the support for multiplying and dividing double
longs is not supported yet. For example, if you try to divide two double-long numbers, you
will get an undefined reference to '__udivdi3'. Sorry, there is no simple help
for this. You must live without 64-bit division for now. It will be implemented in
the future.
// Sort a list of floating point values
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Comparison Function
CALLBACK short flt_comp(const void *a, const void *b)
{
return fcmp (*(const float*)a, *(const float*)b);
}
// Main Function
void _main(void)
{
float list[5] = {2.5, 3.18, 1.42, 4.0, 3.25};
int i;
clrscr ();
qsort (list, 5, sizeof (float), flt_comp);
for (i = 0; i < 5; i++)
printf ("%f\n", list[i]);
ngetchx ();
}
int a = 3, b = 4, c = 5, d = 6;
int array[4] = {a, b, c, d};
is quite legal. That's why
int a = 3, b = 4, c = 5, d = 6;
SCR_RECT myScr = {{b + a, b - a, d + c, d - c}};
is quite legal too. Second, GNU C has one very nice extension in addition to ordinary C: cast constructors. This is a method for constructing structures, arrays, unions etc. "on fly" by using a typecasting of an initializer to an appropriate data type, for example
(SCR_RECT){{10, 10, 50, 50}}
So, you can use
SCR_RECT myScr;
...
myScr = (SCR_RECT){{10, 10, 50, 50}};
which is impossible in ordinary C (ANSI C). You can even use
myScr = (SCR_RECT){{a, b, c, d}};
where a,b,c,d are expressions. Well, but what is now the problem?
See, C has two type of objects: lvalues and non-lvalues. lvalues
are objects which may appear on the left size of an assignment.
For example, a variable is an lvalue and a constant is not an lvalue,
because 'x=5' is legal and '5=x'
(or '5=3') is not legal. Not only variables are lvalues;
for example, dereferenced pointers are also lvalues, so this is legal
for example (store 100 at address 0x4c00):
*(char*)0x4c00 = 100;
So, '*(char*)0x4c00' is an lvalue. Now, about the problem. In GNU C,
cast constructors are lvalues only if the initializer is completely
constant. I.e. '(SCR_RECT){{10,10,50,50}}' is an lvalue, but
'(SCR_RECT){{a,b,c,d}}' is not. As C language accepts unary '&'
operator (i.e. "address of") only on lvalue objects, this means
that, for example,
&(SCR_RECT){{10, 10, 50, 50}}
is legal, but
&(SCR_RECT){{a, b, c, d}}
is not! This is the real cause of the problem!!!
What you can do if you need an address of non-constant cast constructor? You need
to declare an auxilary variable. For example, declare one
SCR_RECT variable, say myScr,
SCR_RECT myScr;
and instead of
ScrRectFill (&(SCR_RECT){{a, b, c, d}}, ScrRect, A_XOR);
use:
myScr = (SCR_RECT){{a, b, c, d}};
ScrRectFill (&myScr, ScrRect, A_XOR);
Note that '&myScr' is legal, because 'myScr' is
an lvalue (it is an ordinary variable). I hope that this helps a lot
understanding of cast constructors and lvalues.
'#') from
TI-Basic in C programs...
'#') exists in any compiling
language (like C), since the variable names do not appear in the compiled
program. You need to make up your mind to avoid this operator.
For indirect references to variables in C, you can use
pointers. However, usually you can
re-express the code using multiple
if-else
statements or arrays. Don't be afraid, C will process it 1000 times faster
than TI-Basic processes indirections.
'when()' function from TI-Basic.
when (condition, true_val, false_val)
is translated to C as
condition ? true_val : false_val
For example,
sign = x >= 0 ? 1 : -1;
Happy?
printf ("%d", sizeof (something));
The ANSI standard proposes that the sizeof operator returns a value of type size_t, which is in fact long integer in this implementation. So, the result is pushed on the stack as a long integer, but the format specifier "%d" expects an ordinary integer, so it pulls from the stack just one word, which is zero in this case. You need to write
printf ("%ld", sizeof (something));
Alternatively, you can use a typecast to convert the result to a short integer
printf ("%d", (short) sizeof (something));
assuming that no object would be longer that 32767 bytes.
'sizeof(function)', and such
construction will be rejected by the most of C compilers.
GNU C (like TIGCC is) uses extended pointer arithmetic
on such way that 'sizeof(function)' is always 1. If you are a dirty
hacker (as I am), and if you really need to determine the number of bytes occupied by
function, I used the following method:
void MyFunction(void)
{
// The function body...
}
void End_Marker(void);
asm("End_Marker:");
...
...
num_of_bytes = (char*)End_Marker - (char*)MyFunction;
Note however that this method is not absolutely reliable, because it depends of the ordering of functions in the program. But, the compiler is free to change the order of functions if such reorganization may lead to a better code.
'xyz' is an ASM program, 'xyz(3,2)+5' or
'xyz(3,2)->a'
is not legal in AMS 2.xx. Fortunately, there is a solution. Read what I wrote about this
problem in the section How to return values to the TI-Basic.
short show_picvar (SYM_STR SymName, short x, short y, short Attr)
{
SYM_ENTRY *sym_entry = SymFindPtr (SymName, 0);
if (!sym_entry) return FALSE;
if (peek (HToESI (sym_entry->handle)) != PIC_TAG) return FALSE;
BitmapPut (x, y, HeapDeref (sym_entry->handle) + 2, ScrRect, Attr);
return TRUE;
}
The usage of this function is straightforward, for example:
show_picvar (SYMSTR ("testpic"), 30, 30, A_NORMAL);
assuming that "testpic" is the name of the wanted PIC variable. This function returns TRUE if the operation was successful, else returns FALSE (i.e. the picvar does not exist, or it is not a PIC variable).
void progrun(const char *name)
{
char fname[25];
HANDLE h;
strcpy (fname, name);
strcat (fname, "()");
push_parse_text (fname);
h = HS_popEStack ();
TRY
NG_execute (h, FALSE);
FINALLY
HeapFree (h);
ENDFINAL
}
The usage of it is straightforward, for example:
progrun ("testprog");
Note that the program you call may throw errors. If you understand this function, you can easily expand it to accept arguments, etc. Principally, using NG_execute you can execute any particular sequence of TI-Basic statements.
See also: How can I create a program that is bigger than 24K and works on AMS 2.xx?
a = GetIntArg (top_estack);
It seems that it works fine, but you always use an auxilary variable...
a = GetIntArg (top_estack);
you will also change the value of TIOS system variable top_estack, and I am not sure that you really want this. So, I strictly recommend using an auxilary variable, like in the following example:
ESI argptr = top_estack; ... a = GetIntArg (argptr);
Using this method, you will avoid unexpected changes of top_estack.
ESI ptr;
int result;
int i = 1; // Just an example
int j = 2;
push_parse_text ("[[11,12][21,22]]"); // An example matrix
ptr = locate_element (i,j);
result = GetIntArg (ptr); // (assumed that elements are ints)
where 'locate_element' is an user-written function, which may be
implemented as follows:
ESI locate_element (short m, short n)
{
short i;
ESI ptr = top_estack-1;
for (i = 0; i < m-1; i++) ptr = next_expression_index (ptr);
ptr--;
for (i = 0; i < n-1; i++) ptr = next_expression_index (ptr);
return ptr;
}
You can use it as-is, but it will be much better if you can understand how it works.
'foo(n)' which accepts an
argument (a string, for example). I want to make 'foo(n)'
return 'foo(n)' (the call itself) when 'n' is of type
"VAR" (i.e. if nothing has been assigned to 'n' yet)...
'example(n)' if
you type 'example(n)' if
'n' is a variable, and which will return the string "blabla"
(for example) if the argument is something else:
// A function returning itself
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define RETURN_VALUE // Return pushed expression
#define MIN_AMS 101 // Compile for AMS 1.01 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
ESI argptr = top_estack;
if (GetArgType (argptr) <= VAR_Q_TAG) // it means that arg is a variable
// see Tags to see why...
push_expr_quantum (SYMSTR ("example"), USERFUNC_TAG);
else
{
while (ESTACK (top_estack) != END_TAG)
top_estack = next_expression_index (top_estack);
top_estack--;
push_string (SYMSTR ("blabla"));
}
}
Note that this solution is not ideal: if you rename the program name to something else,
the function will still return 'example(n)'. It
is possible to determine the real name of the program in the run time, but this
is very awkward.
char string1[50]; char string2[50]; short int var1; float var2; ... sprintf (string1, "%d", var1); sprintf (string2, "%f", var2);
That's why there is no need for functions like itoa and ftoa in opposite to atoi and atof.
unsigned char sprite [] = {0x38, 0x7C, ...};
assuming that Sprite8 will be used. That's all...
Note: TIGCC also supports binary numbers (0b...).
static unsigned short light_definition [] = {...};
static unsigned short dark_definition [] = {...};
...
Sprite16 (x, y, height, light_definition, GrayGetPlane (LIGHT_PLANE), A_XOR);
Sprite16 (x, y, height, dark_definition, GrayGetPlane (DARK_PLANE), A_XOR);
In other words, sprite routines can handle grayscale sprites, but not natively, meaning you have to take your grayscale sprite, split it into two layers, and draw each one separately on its own plane - the routine does not handle these by itself. As suggested by Scott Noveck, it is possible to make a function of your own to handle this. Assume that your grayscale sprites follow the "standard" format seen in the most of ASM games, with the dark plane data followed immediately by the light plane data. This routine will call Sprite16 twice - once for each plane:
void GraySprite16 (short x, short y, short h, unsigned short *spr, short mode)
{
Sprite16 (x, y, h, *spr, GetPlane (LIGHT_PLANE), mode);
Sprite16 (x, y, h, *spr + h, GetPlane (DARK_PLANE), mode);
}
Don't be afraid about calling GrayGetPlane each time: it is not a waste of time. Its implementation is smart: when the input is a constant, it will simply evaluate to a memory address that contains the pointer; when it is variable, it expands to a simple macro.
// Retrieve and store a bitmap
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
SCR_RECT full_screen = {{0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1}};
char buffer [BITMAP_HDR_SIZE + LCD_WIDTH*LCD_HEIGHT/8]; // or 2004 for a TI-89 and 3844 for a TI-92+/V200 if you like it more
BitmapGet (&full_screen, buffer); // store screen in buffer
clrscr ();
printf ("Press any key to\nrestore screen...");
ngetchx ();
BitmapPut (0, 0, buffer, &full_screen, A_REPLACE);
ngetchx ();
}
Note that this is just an example: for saving/restoring the whole screen, the functions LCD_save and LCD_restore are much more efficient! Moreover, buffer will probably be allocated using malloc in a more realictic example.
void *virtual = malloc (LCD_SIZE); // Allocate the buffer ... if (!virtual) ... // do some error handling - not enough memory! PortSet (virtual, 239, 127); // redirect drawing routines to buffer
or, even simpler, virtual screen may be simply in any local variable which is enough long:
char virtual[3840]; ... PortSet (virtual, 239, 127);
Note that, in this case, virtual memory will be in fact somewhere on the stack.
There is nothing bad in this, but keep in mind that the total amount of the
stack is 16K, so don't put TOO MANY data (like big arrays etc.) on the stack
(i.e. in local variables). If you really need to handle a lot of data, use
malloc instead.
After this, do any drawing you want - it will be redirected to the virtual
screen. To copy this to the regular screen (i.e. to display it) do this:
memcpy (LCD_MEM, virtual, LCD_SIZE);
or even simpler (this is the same):
LCD_restore (buffer);
And, don't forget to do PortRestore before end of the program, else TIOS will be fooled after returning to TI-Basic!
FillTriangle (10, 10, 10, 50, 50, 50, &(SCR_RECT){{5, 5, 90, 70}}, A_NORMAL);
or, using "standard" C (i.e. without GNU extensions):
SCR_RECT area = {{5, 5, 90, 70}}; // somewhere in the declaration part
...
FillTriangle (10, 10, 10, 50, 50, 50, &area, A_NORMAL);
Note that double braces are necessary because SCR_RECT
is an union.
If coordinates are not known in advance, for examples if they are in
integer variables a, b, c and d, you can do this:
SCR_RECT area; ... area.xy.x0 = a; area.xy.y0 = b; area.xy.x1 = c; area.xy.y1 = d; FillTriangle (10, 10, 10, 50, 50, 50, &area, A_NORMAL);
or, much simpler, using GNU C extensions:
FillTriangle (10, 10, 10, 50, 50, 50, &(SCR_RECT){{a, b, c, d}}, A_NORMAL);
'TaskID'
and others!
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
WINDOW wind;
WIN_RECT winr = {20, 20, 80, 50};
WinOpen (&wind, &winr, WF_SAVE_SCR | WF_TTY);
WinActivate (&wind);
WinFont (&wind, F_6x8);
WinStr (&wind, "hello everyone");
ngetchx ();
WinClose (&wind);
}
Example 2: Window is allocated dynamically (called "Window 2"):
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
WINDOW *wind = HeapAllocPtr (sizeof (WINDOW));
WIN_RECT winr = {20, 20, 80, 50};
WinOpen (wind, &winr, WF_SAVE_SCR | WF_TTY);
WinActivate (wind);
WinFont (wind, F_6x8);
WinStr (wind, "hello everyone");
ngetchx ();
WinClose (wind);
HeapFreePtr(wind);
}
Note that synonyms for HeapAllocPtr and
HeapFreePtr are
malloc and free (like in ANSI C).
Example 3: Both "window" and "rect" are allocated dynamically (called "Window 3"):
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
WINDOW *wind = HeapAllocPtr (sizeof (WINDOW));
WIN_RECT *winr = HeapAllocPtr (sizeof (WIN_RECT));
winr->x0 = 20; winr->y0 = 20;
winr->x1 = 80; winr->y1 = 50;
WinOpen (wind, winr, WF_SAVE_SCR | WF_TTY);
WinActivate (wind);
WinFont (wind, F_6x8);
WinStr (wind, "hello everyone");
ngetchx ();
WinClose (wind);
HeapFreePtr (wind);
HeapFreePtr (winr);
}
Example 4: How to use MakeWinRect to avoid "winr" (called "Window 4"):
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
WINDOW *wind = HeapAllocPtr (sizeof (WINDOW));
WinOpen (wind, MakeWinRect (20, 20, 80, 50), WF_SAVE_SCR | WF_TTY);
WinActivate (wind);
WinFont (wind, F_6x8);
WinStr (wind, "hello everyone");
ngetchx ();
WinClose (wind);
HeapFreePtr (wind);
}
Example 5: This is what I do in my programs (called "Window 5"):
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 100 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Main Function
void _main(void)
{
WINDOW wind;
WinOpen (&wind, &(WIN_RECT) {20, 20, 80, 50}, WF_SAVE_SCR | WF_TTY);
WinActivate (&wind);
WinFont(&wind, F_6x8);
WinStr (&wind, "hello everyone");
ngetchx ();
WinClose (&wind);
}
Don't forget to close a window before exiting. If you forget to do so, the
TI may crash later, when window manager tries to refresh a still active window
which ceased to exist due to end of the program!
In general, I prefer static allocation instead of dynamic. It is good if you
know in advance how many open windows you have in the program (this is often a
case on TI). Dynamic allocation is the only method if you don't know in advance
how many open windows you need (then, you can keep them in linked list). I
don't think that there is a lot of use for this on the TI. :-)
ICON i = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
...
DrawIcon (50, 50, &i, A_NORMAL);
Of course, change 1, 2, 3... to the real definition of the icon.
pICON is necessary only for using with icons which are dynamically allocated
(forget it if you are not familiar with dynamic structures in C), like in:
pICON p = malloc (32); ... // Here is a code which fill up the icon structure ... DrawIcon (50, 50, p, A_REPLACE);
Look operator "&" in first example, it is omitted in second example. In fact, "&" converts a variable of ICON type to pICON type (in general, it converts any type to corresponding pointer type).
// Example of a fast line-drawing routine
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Draws a line from (x1,y2) to (x2,y2).
void DrawLineFast(short x1, short y1, short x2, short y2)
{
short x = x1, y = y1;
short dx = abs (x2 - x1), dy = abs (y2 - y1);
short ystep = (y1 < y2) ? 1 : -1, pystep = 30 * ystep;
short mov = dx ? 0 : -1;
unsigned char *ptr = (char*)LCD_MEM + 30 * y + (x >> 3);
short mask = 1 << (~x & 7);
if (x1 < x2)
while (x != x2 || y != y2)
{
*ptr |= mask;
if (mov < 0) y += ystep, ptr += pystep, mov += dx;
else
{
mov -= dy;
if (++x & 7) mask >>= 1;
else ptr++, mask = 0x80;
}
}
else
while (x != x2 || y != y2)
{
*ptr |= mask;
if (mov < 0) y += ystep, ptr += pystep, mov += dx;
else
{
mov -= dy;
if (x-- & 7) mask <<= 1;
else ptr--, mask = 1;
}
}
}
// Main Function
void _main(void)
{
DrawLineFast (10, 10, 60, 70);
ngetchx ();
}
If you need a line erasing or line inverting routine, replace
'*ptr |= mask' with
'*ptr &= ~mask' or '*ptr ^= mask'
respectively.
INT_HANDLER save_int_1; ... save_int_1 = GetIntVec (AUTO_INT_1); SetIntVec (AUTO_INT_1, DUMMY_HANDLER); // redirect auto-int 1 to "nothing" // enable grayscale // do your code // disable grayscale SetIntVec (AUTO_INT_1, save_int_1);
This method is much more elegant than in previous releases of TIGCCLIB.
void myInterruptHandler(void)
{
asm ("movem.l %d0-%d7/%a0-%a6,-(%sp)");
// do something here...
asm ("movem.l (%sp)+,%d0-%d7/%a0-%a6; rte");
}
asm("movem.l %d0-%d7/%a0-%a6,-(%sp)");
something will be pushed on the stack. Read this as "open brace '{' is not only the marker, it also generates an entry procedure code". So, when "rte" is encountered, the return address on the stack is trashed. Second, even if the procedure does not use any local vars, the entry code for the procedure is usually
link #something,%a6
and the stack is again not suitable for executing "rte". Before release 2.2 of TIGCCLIB, the only universal and correct method for defining interrupt handlers was is to define a pure assembly auxilary procedure as follows:
void InterruptHandler(void); // Just a prototype
asm ("myInterruptHandler:
move.w #0x2700,%sr
movem.l %d0-%d7/%a0-%a6,-(%sp)
jbsr MainHandler
movem.l (%sp)+,%d0-%d7/%a0-%a6
rte");
void MainHandler(void)
{
// Put everything you want here...
}
To make life easier, starting from release 2.2, I introduced a new language extension called DEFINE_INT_HANDLER in the intr.h header file. Now, it is enough to write
DEFINE_INT_HANDLER (myInterruptHandler)
{
// Put everything you want here...
}
; compress () ; Function: compress data ; Input: A0 = Pointer to uncompressed data ; A1 = Pointer to where the compressed data ; should be stored ; D0.W = Length of datas which will be compressed ; ziplib::compress equ ziplib@0004
So, how to interface this with TIGCC? There is a lot of solutions. One solution is to use an interface function which accepts parameters via stack then to use embeded assembler to call library function:
void compress (void *src, void *dest, unsigned short len)
{
asm ("move.l (%a6,8),%a0
move.l (%a6,12),%a1
move.w (%a6,16),%d0
jsr ziplib__0004");
}
This works, but maybe it is awkward to know where the parameters are stored on the stack. GNU C has some extensions for interfacing with assembler, so the following solution is more elegant:
void compress (void *src, void *dest, unsigned short len)
{
asm ("move.l %0,%%a0" :: "g"(src));
asm ("move.l %0,%%a1" :: "g"(dest));
asm ("move.w %0,%%d0" :: "g"(len));
asm ("jsr ziplib__0004");
}
If you don't understand this (which is probably the case if you are not
familiar with GNU C extensions), accept this as as-is template.
Both solutions have two bad points: first, usage of stack is awkward if you
want very fast calling. Second, this interface function will always be
inserted in the code (even if not called in the program), so it is not suitable
for making universal header files. As GNU C allows interfacing arbitrary C
expressions with assembler, and allows building smart safe macros using
so-called "statement expressions", the following solution avoids both the stack
and "embedding" problem:
#define compress(src, dest, len) \
({ asm ("move.l %0,%%a0" :: "g"(src)); \
asm ("move.l %0,%%a1" :: "g"(dest)); \
asm ("move.w %0,%%d0" :: "g"(len)); \
asm ("jsr ziplib__0004"); })
In any case, you can call function (or macro) "compress" using, for example,
compress (LCD_MEM, buffer, LCD_SIZE);
etc.
The third solution has one drawback: the impossibility of compile-time checking of
parameters type. This can be solved using the following construction (don't be
afraid by introducing extra variables; the compiler will remove them during the
optimization, and it will produce the same code as in previous example, but with
type-checking):
#define compress(src, dest, len) \
({ void *__src = (src), *__dest = (dest); \
unsigned long __len = (len); \
asm ("move.l %0,%%a0" :: "g"(__src)); \
asm ("move.l %0,%%a1" :: "g"(__dest)); \
asm ("move.w %0,%%d0" :: "g"(__len)); \
asm ("jsr ziplib__0004"); })
There is also fifth, nearly "ideal" solution,
using GNU cast constructors which constructs a function during the compilation
(look how functions in stdio.h are implemented, etc.).
But, I will not present this here, because you will not understand anything
if you are not familiar with cast constructors.
Note: TIGCC v0.94 and later support explicit register specification.
asm keyword allows one to include assembly instructions in C
source code. But how would would you write an assembly function that C code could
utilize?
printf (strcat ("Hello ", "World!"));
strcat appends the second argument to the first argument and returns the augmented first argument, but it does not allocate any extra space for doing this task. So, if you do
strcat ("Hello ", "World!");
string "World!" will be copied over bytes which follows immidiately
after bytes "Hello " in the memory (whatever is there), which will
nearly surely cause a crash (because there is probably a part of code or other
data there). So, strcat may be used only when
the first argument is enough long buffer, like in:
char buffer[50]; strcpy (buffer, "Hello "); strcat (buffer, "World!");
In other words, C language does not support dynamic string manipulations which is present in some other languages like Basic, Turbo Pascal, etc.
char *IntToStr (unsigned long an_integer)
{
static char result [] = " 0円"; // 10 spaces and 0円
char *ptr = result + 10;
while (an_integer)
{
*ptr-- = an_integer % 10 + '0';
an_integer/=10;
}
return ptr;
}
Note that 'static' before char in the first line is essential: without it, the
variable 'result' will be allocated of the stack, so it will not live
too long after this function returns. Returning any pointers which points to
structures allocated on the stack is extremely dangerous (it is not only dangerous; it is
almost completely nonsense, except if you performs some really nasty and bizzare hacks).
The another solution (but less elegant) is to make 'result' global (i.e. to
define it out of the function).
"main\var", the compiler gives me an error. Help!
\) is used as an escape character in the C language.
Because of this, you need to modify your string to read like this: "main\\var".
Now, compiler will interpret the \\ as a single backslash, which is what you want.
OldHandler = EV_captureEvents (NewHandler);
then you call OldHandler from NewHandler. There would be nothing
wrong in doing so, but function EV_captureEvents
returns NULL when there is no any user handlers previously
installed, which is very common case. So, you can call OldHandler only if
it is not null. In other words, it is illegal to call an event handler when it is not
actually installed. From the other side, function EV_defaultHandler
calls the TIOS handler which is used for default dispatching of some common events. It works independently of
which handler is currently installed and whether it is installed at all. This is a function
which you probably need to call in your event handler to process all unprocessed events.
// A simple text editor example
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
TEXT_EDIT te;
// Typical event handler for text editing
CALLBACK void EventHandler(EVENT *ev)
{
if (ev->Type == CM_KEYPRESS && ev->extra.Key.Code == KEY_ESC)
ER_throw (1);
if (!TE_handleEvent (&te, ev))
EV_defaultHandler (ev);
}
// Main Function
void _main(void)
{
char *base_addr;
SYM_ENTRY *sym = SymFindPtr (SYMSTR ("mytext"), 0);
if (!sym) return; // Exit if file not found...
// First, you need to remove the garbage data at the begining of
// the text variable, because the text editor expects raw data:
base_addr = HeapDeref (sym->handle);
memmove (base_addr, base_addr + 4, peek_w(base_addr));
// Now, do the editing. This is straightforward...
WinClr (DeskTop);
TE_open (&te, DeskTop, MakeWinRect (0, 16, 159, 92), sym->handle, 1, 0, 7);
CU_start ();
EV_captureEvents (EventHandler);
TRY
EV_eventLoop ();
ONERR
EV_captureEvents (NULL);
ENDTRY
// Finally, you must transform raw editor data into the proper
// format of the text variable. This is not so straightforward:
base_addr = HeapDeref (HeapRealloc (sym->handle, te.CurSize + 10));
memmove (base_addr + 4, base_addr, te.CurSize);
poke_w (base_addr, te.CurSize + 4);
poke_w (base_addr + 2, te.CursorOffset);
poke (base_addr + te.CurSize + 4, 0);
poke (base_addr + te.CurSize + 5, TEXT_TAG);
}
It is important to understand how this program works if you plan to do any serious application of text editing functions.
WIN_RECT myRect = {0, 16, 159, 92};
WINDOW myWin;
TEXT_EDIT te;
...
WinOpen (&myWin, &myRect, WF_NOBORDER);
myWin.Flags &= ~WF_DIRTY;
TE_open (&te, &myWin, &myRect, ...);
Anyway, there is no strong reasons to use any windows other than DeskTop as a parent window, except if you want to use the whole screen for the editing area (the desktop window is clipped in the toolbar area). But, note that using whole screen for editing is not so good idea. The editor expects that the menu is on the top. So, if you press F1 etc. while doing "full-screen" editing, your screen will be trashed, because the editor will open the menu, and it will expect that the toolbar is at the top, etc. etc. Try to see. The solution? Disable all keys like F1, etc. in the event handler (e.g. do not pass them to TE_handleEvent) if you really want to do full screen editing...
WinFont (DeskTop, F_8x10);
However, the editor will work fine with both 6x8 and 8x10 fonts, but not with 4x6 font (try and see), because it is proportional, so the editor will be fooled (editors usually expects fixed-size fonts). This is a pity.
PopupDo (handle, ...)
you need to do something like:
printf_xy (0, 50, "Address=%lp", HeapDeref (handle)); ngetchx();
Then, while waiting for a keypress, open the VTI debugger and go to the displayed address. Take a pencil and write a sequence of bytes starting from this address. Tenth and eleventh byte in this sequence will tell to you how many bytes you need to pick. After this, put these bytes in the array, and pass such array as an argument to the MenuPopup function. As an exercise, try this on an example given in the documentation.
SYM_ENTRY *sym;
...
sym = SymFindPtr (SYMSTR ("foo"), 0);
Then, do the following to find the size and type:
unsigned short size; ESQ type; ... size = ((MULTI_EXPR*) HeapDeref (sym->handle))->Size + 2; type = *(HToESI (sym->handle));
After this, the variables 'size' and 'type' will contain exactly
what do you want.
FILE *fp = fopen ("example", "wb");
// store anything you want in the file here
fputc (0, fp);
fputs ("HSC", fp);
fputc (0, fp);
fputc (OTH_TAG, fp);
fclose (fp);
After this, you will have a file named "example" with type "HSC": you will see it in the VAR-LINK dialog.
Create a new VAT symbol using SymAdd;
Allocate a space for the variable using HeapAlloc;
Dereference the symbol to get a pointer to the VAT entry, then store the handle returned by HeapAlloc to it;
Put the actual file length in first two bytes of allocated space.
To be more concrete, I will show here a simple demo program (called "Create Variable") which creates a string file (I use this internally), but it is easy to adapt to any file type:
// Create a variable using functions from vat.h
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
HANDLE CreateFile (const char *FileName)
// Returns a handle, H_NULL in case of error
{
HANDLE h;
SYM_ENTRY *sym_entry;
char str[30], *sptr = str;
*sptr = 0; while ((*++sptr = *FileName++));
if (!(h = HeapAlloc (HeapMax ()))) return H_NULL;
if (!(sym_entry = DerefSym (SymAdd (sptr))))
{
HeapFree (h);
return H_NULL;
}
*(long*) HeapDeref (sym_entry->handle = h) = 0x00010000;
return h;
}
void AppendCharToFile (HANDLE h, unsigned char c)
{
char *base = HeapDeref(h);
unsigned short len = *(unsigned short*)base;
if (len > HeapSize(h) - 10) return;
*(unsigned short*)base = len + 1;
base[len+2] = c;
}
void AppendBlockToFile (HANDLE h, void *addr, unsigned short len)
{
unsigned short i;
for (i = len; i; i--) AppendCharToFile (h, *((char*)addr)++);
}
void CloseFile (HANDLE h)
{
AppendCharToFile (h,0); AppendCharToFile (h,0x2D);
HeapUnlock (h);
HeapRealloc (h, *(unsigned short*)HeapDeref(h) + 3);
}
void _main(void)
{
static char s[] = "Hello world!";
HANDLE h;
h = CreateFile ("example");
AppendBlockToFile (h, s, 12);
CloseFile (h);
}
Note that the used method is not the best: it initially
allocates as much space as avaliable, then reallocates the space to
the necessary size on closing, but it is worth to look at it. Note
also that the CreateFile function may be even simpler if you want to use it
like CreateFile(SYMSTR("example")) instead of
CreateFile("example"), i.e. if you avoid the use of ANSI strings.
EM_moveSymToExtMem ("example", HS_NULL);
Instead, you need to write
EM_moveSymToExtMem (SYMSTR ("example"), HS_NULL);
This routine except SYM_STR type strings, not ordinary C strings. See SYMSTR from the vat.h header file for more info.
static int a = 0, b = 0; static char *ptr = NULL;
instead of
static int a, b; static char *ptr;
I expect that this will solve your problems. I hope that Xavier will implement automatic initialization of all static data in the near future.
type name[dimension1][dimension2];
for example:
int a[5][5];
And, to access element of the matrix, you need to use:
name[index1][index2];
for example:
a[2][3] = 10;
or
x = a[1][2];
But note that indices are not from 1 to dimension, but from 0 to dimension-1. So, in the above example, both indices are in range 0 to 4, not 1 to 5. For example to fill all matrix by zeroes, you can use this code:
int a[5][5], i, j; // A 5x5 array of ints, and two single ints
for(i = 0; i < 5; i++) for (j = 0; j < 5; j++) a[i][j] = 0;
although the experienced C programmer will simply use
memset (a, 0, 5 * 5 * sizeof(int));
to make it faster.
static int high_scores[10] = {};
In this case, such array will be kept in the program file itself (instead on the stack),
so it will not go away after you exit the program. This method has only one drawback:
preferences will not be kept if the program is archived.
Be aware of the initializer: static variables must be initialized in
the "nostub" mode. An empty initializer in this example is equivalent to the
static int high_scores[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Don't be confused which such initialization. It seems that the array elements will be set back to 0 any time when you run the program. But, this is not true. The initialization of automatic and static variables is quite different. Automatic (local) variables are initialized during the run-time, so the initialization will be executed whenever it is encountered in the program. Static (and global) variables are initialized during the compile-time, so the initial values will simply be embeded in the executable file itself. If you change them, they will retain changed in the file.
int a = 10;
and if I change its value somewhere in the program to 20 (for example), its initial value will be 20 (not 10) when I run the program next time???
a = 10;
at the begining of the main program!
Note, however, that if the program is archived,
the initial values will be restored each time you run the program, because archived
programs are reloaded from the archive memory to the RAM on each start, similarly
to the programs are reloaded from disks on "standard" computers (PC, etc.) each
time when you start them. The same is true for compressed programs (for obvious
reasons) and if you use a data variable and have TIGCC create a copy every time
it is used.
See also: How do I store variables so they retain their values
.89z/.9xz file itself (more precise, in the area of the memory allocated to
.89z/.9xz file), so they will survive after the end of the program. Local data
are stored on the stack (so they will be deleted after the end of the function in which
they are declared).
.89z (or .9xz) file itself. Sometimes it is good
(to keep permanent data which will survive after the program exits etc.), but very
often it is a wasting of file space (as the file length is limited). Read questions
given further in this document to see how to reduce a file size if you have a
strong reasons for using global arrays. To learn: avoid extensive usage of globals
as much as possible!
'address=0x001fca;' where
address is a pointer to a char. Is it necessary to use the assembler for this?
address = (char*)0x001fca;
This is really a classic usage of the typecast operator. It is extremely powerful in C language, so it may be used to convert nearly everything to anything. This sometimes may be really terrible. For example, in TIGCCLIB implementation, I used very strange typecasting to convert an array to a function. And, nearly all TIGCCLIB functions are implemented using a typecast operator which constructs an indirect function call. For example, when you use something innocent like
DrawStr (0, 0, "Hello", A_NORMAL);
the preprocessor expands it into a really terrible typecast. See _rom_call for more info.
ptr = (long*)((char*)ptr + 30);
Don't be afraid, the compiler will generate just addition: everything other is just to satisfy type checking conventions. Or alternatively, you can use even simpler form:
(char*)ptr += 30;
Although such form is not requested to work in ANSI C standard, the most of compilers (including TIGCC) will accept this.
void *screen_ptr = LCD_MEM; *screen_ptr = 0xFF;
When I do this, I get the error "Invalid use of void expression" at the second line. Can't you assign a value to a dereferenced void pointer? If not, what good is a void pointer?
*(char*)screen = 0xFF;
Or better, if you need to dereference it often, then declare
unsigned char *screen = LCD_MEM;
i.e. avoid void pointers for this purpose (continue reading to see why
such assignment is legal, i.e. assigning LCD_MEM
which is a void pointer to a char pointer).
Void pointers are used mainly as arguments of functions which
represents memory addresses, no matter what is the object located at
that addresses. Then, any pointer type (including arrays, which are
in fact pointers to the first element of the array) may be passed
to such function without warnings and without needness for explicite
typecasting. For example, memcpy is such
function, and it is declared as:
void *memcpy (void *source, void *destination, unsigned long len);
Ignore returned type for a moment. So, you can do
memcpy (LCD_MEM, buffer, 3840);
but you also can do
memcpy (a, b, 10 * sizeof(long));
assuming that you have declared
long a[10], b[10];
somewhere in the program. Second, void pointers may be assigned to any other pointer type and vice versa without and warnings and without needness for explicite typecasting. They are usually returned as the result of functions which don't make any assumptions what will be purpose of returned pointer. For example, malloc is such function. It is declared as
void *malloc (unsigned long len);
So, assuming that you have declared
char *a; int *b; long *c;
you can do
a = malloc (100); b = malloc (30 * sizeof(int)); c = malloc (50 * sizeof(long));
without any problems.
_main function, but then they are not available in the other functions. Is
there a way to make "global" non-initialized screen buffers?
#include <tigcclib.h>
...
void *buff; // Buffer pointer
...
void _main(void)
{
...
buff = malloc (LCD_SIZE); // Alloc buffer, make "buff" point to it
if (!buff) ... // Do some error handling (no memory)
LCD_save (buff);
...
LCD_restore (buff);
free (buff);
}
Simple?
int A[200][100] = {{}};
I will increase the size of my program by about 40K (200*100*sizeof(int)). This is really unacceptable. Obviously, I need to create a matrix dinamically. But I have no any idea how to do this.
int *A[200] = {}; // An array of pointers
...
for (i = 0; i < 200; i++)
A[i] = calloc (100, sizeof (int));
assuming that all memory allocations were sucessfull. Note that the initializer '{}'
is not necessary if 'A' is not global (but we will assume that it is). Of course,
you need to free allocated memory too at the end of the program. We will see a bit later why
this method is not recommended on TI calculators.
Using this method, you will have a global array which is 800 bytes long (200*4, where 4 is the
size of a pointer), which is much smaller than 40000 bytes. And, you need to know a number of
rows in advance. Some books suggests a method which does not use any extra space in the
executable file, and in which you need not to know any dimensions of the matrix in advance.
This method uses a double pointer (pointer to pointer):
int **A = NULL; ... A = calloc (200, sizeof (*A)); for (i = 0; i < 200; i++) A[i] = calloc (100, sizeof (int));
The major drawback of both methods is complicated memory management. In a real program, you
need to be aware of a fact that each allocation may fail eventually, and you need to act
accordingly if this happens. And, these methods are too expensive for TI calculators. As
TIOS memory manager assigns a handle with each allocated block, reserving say 200 handles for
just one matrix is too expensive, if even possible, because the total number of free handles
is limited!
What to do? The best solution is so simple, but rarely documented in books. Instead of using
an array of pointers, use a pointer to an array! Seems strange, but only what you need to
do is:
int (*A)[100] = NULL; // A pointer to an array ... A = calloc (200, sizeof (*A));
So, everything is done with just one calloc! And, you can
free the memory just with one call to free. Wonderful, isn't
it? Of course, whatever method you used, you can access to the elements of a matrix using
an usual syntax like 'A[i][j]'. Note however that the last ("ideal") method
requires that you know the number of columns in advance. Try to understand how all three
methods work: it will help you understanding nontrivial pointers, arrays and relations
between them.
As many users ask me about creating dynamic matrix, I have made a complete demo program
for newbies (called "Dynamic Matrix") which creates and prints elements of dynamically created matrix, using the
third (the best in my opinion) method:
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define OPTIMIZE_ROM_CALLS // Use ROM Call Optimization
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
#define M 10
#define N 5
int (*A)[N] = NULL;
void _main (void)
{
int i,j;
A = calloc (M, sizeof (*A)); // <I>allocation</I>
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
A[i][j] = i*j; // fill 'A' with the multiplication table
clrscr ();
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
printf ("%2d ", A[i][j]); // print out the matrix
printf ("\n");
}
free (A); // free the memory
ngetchx();
}
This method is really ideal if you know dimensions of the matrix in advance (which is usually true). It may be easily extended to create dinamically arrays with more than two dimensions. For example, to create dinamically array which behaves like
int A[M][N][P][Q];
where 'N', 'P' and 'Q' are known in compile-time, you
can do:
int (*A)[N][P][Q]; ... A = calloc (M, sizeof (*A));
However, if you don't know any dimension of the matrix in advance, the second method is preferable.
See also: How can I create variable-size arrays?
int a[n]; // where n is not known in compile-time
you can write:
int *a; ... a = calloc (n, sizeof (int));
The same is not-so-easy for 2-dimensional arrays (see the previous question), and quite hard
(although possible) for n-dimensional arrays where n>2. To do this, you need to have nasty
plays with pointers. It is even possible to make a function named multi_dim which
creates multidimensional variable-sized arrays. I once created such routine for some
internal purposes (note that it is very tricky). Principally, its usage is like this:
if you want to simulate
int a[n1][n2][n3][n4];
where n1, n2, n3 and n4 are not known apriori, you need to do:
int ****a; // yes, quadruple pointer!!! ... a = multi_dim (4, sizeof (int), n1, n2, n3, n4);
However, TIGCC is GNU C, and it has some extensions in addition to "standard" C. For example, it allows variable-size arrays without any tricky plays with pointers!!! (Note that this particular extension, together with a few others, has been added to standard ISO C in 1999, so it is no longer non-standard.) Try this:
void _main(void)
{
int m, n, p;
m = random (5);
n = random (5);
p = random (5);
{
int a[m][n][p];
// do something with a
}
}
and you will see that it works!
int a, ptr_to_a = &a;
When I tried to modify the variable "a" indirectly using the pointer, like in
*ptr_to_a++;
the compiler reports to me "Value computed is not used". What is wrong here?
'++' and '*' have the same
precedence, '++' will be evaluated first, so this expression will be evaluated as
*(ptr_to_a++);
i.e. it increases the pointer, then reads the value from it (which is not used for anything). This is not what do you want, of course. To perform what do you want (i.e. to increase the variable pointed to by the pointer), use parentheses to change the order of evaluation, i.e. use
(*ptr_to_a)++;
This will work as expected.
// Custom string input example
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
// Custom String Input Function
void InputStr(char *buffer, unsigned short maxlen)
{
SCR_STATE ss;
short key;
unsigned short i = 0;
buffer[0] = 0;
SaveScrState (&ss);
do
{
MoveTo (ss.CurX, ss.CurY);
printf ("%s_ ", buffer);
// Note that two spaces are required only if the F_4x6 font is used
key = ngetchx ();
if (key >= ' ' && key <= '~' && i < maxlen)
buffer[i++] = key;
else if (key == KEY_BACKSPACE && i)
i--;
buffer[i] = 0;
} while (key != KEY_ENTER);
}
// Main Function
void _main(void)
{
char s[20];
clrscr ();
InputStr (s, 20);
printf ("\n%s", s);
ngetchx ();
}
Especially, if very good editing facitilities are required, the best idea is to use routines from the textedit.h header file. These routines are extremely powerful and fully customizable. Alternatively, you can also use routines from dialogs.h, especially DialogAddRequest.
See also: Do you have the function that gets called when you do InputStr in TI-Basic?, How can I make a keyboard input function that allows you to bring up a menu?, getsn
See also: How can I get input from the keyboard?, How can I make a keyboard input function that allows you to bring up a menu?, getsn
// Custom string input example enabling the CHAR menu
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
short captured;
CALLBACK void CaptureHandler(EVENT *ev)
{
if (ev->Type == CM_STRING)
captured = *(ev->extra.pasteText);
}
void InputStr(char *buffer, unsigned short maxlen)
{
SCR_STATE ss;
short key;
unsigned short i = 0;
buffer[0] = 0;
SaveScrState (&ss);
do
{
MoveTo (ss.CurX, ss.CurY);
printf ("%s_ ", buffer);
// Note that two spaces are required only if the F_4x6 font is used
do
{
key = ngetchx ();
if (key == KEY_CHAR && i < maxlen)
{
EVENT ev;
captured = 0;
ev.Type = CM_KEYPRESS;
ev.extra.Key.Code = key;
EV_captureEvents (CaptureHandler);
EV_defaultHandler (&ev);
EV_captureEvents (NULL);
}
} while (!captured);
if (key == KEY_CHAR && i < maxlen)
buffer[i++] = captured;
if (key >= ' ' && key <= '~' && i < maxlen) buffer[i++] = key;
if (key == KEY_BACKSPACE && i) i--;
buffer[i] = 0;
} while (key != KEY_ENTER);
}
// Main Function
void _main(void)
{
char s[20];
clrscr ();
InputStr (s, 20);
printf ("\n%s", s);
ngetchx ();
}
It will be good if you can understand how it works (I recommend reading the documentation for the events.h header file). Note that this example used to be broken in previous releases of TIGCC, as it took the address of a nested function, a GNU C Extension which didn't work correctly at that time.
See also: How can I get input from the keyboard?, Do you have the function that gets called when you do InputStr in TI-Basic?, getsn
kb_globals, but is it possible to do something
similar with TIGCCLIB?
GKeyIn (NULL, 0) is the most similar equivalent. ngetchx () is not so similar. Both functions
are defined in kbd.h. Note that idle
defined in system.h is NOT equivalent to idle_loop!
EVENT ev; ev.Type = CM_KEYPRESS; ev.extra.Key.Code = KEY_VARLNK; EV_defaultHandler (&ev);
but getting the name of the file that was selected is a bit harder. After executing the VAR-LINK dialog, the VAR-LINK applet will send the name of the selected file to the current application via the CM_HSTRING message. This message may be captured by a user event handler. Here is the demonstration program (called "Get File Name"):
// Open VAR-LINK dialog and let user select something
#define USE_TI89 // Compile for TI-89
#define USE_TI92PLUS // Compile for TI-92 Plus
#define USE_V200 // Compile for V200
#define MIN_AMS 100 // Compile for AMS 1.00 or higher
#define SAVE_SCREEN // Save/Restore LCD Contents
#include <tigcclib.h> // Include All Header Files
char VarBuffer[20] = "";
CALLBACK void VarLinkHandler (EVENT *ev)
{
if (ev->Type == CM_HSTRING)
{
strncpy (VarBuffer, HeapDeref (ev->extra.hPasteText), 19);
VarBuffer [19] = 0;
}
EV_defaultHandler (ev);
}
void VarLinkDialog (void)
{
EVENT ev;
EVENT_HANDLER OldHandler = EV_captureEvents (VarLinkHandler);
memset (&ev, sizeof (ev), 0);
ev.Type = CM_KEYPRESS;
ev.extra.Key.Code = KEY_VARLNK;
EV_defaultHandler (&ev);
EV_captureEvents (OldHandler);
}
void _main(void)
{
VarLinkDialog ();
printf_xy (0, 50, "You picked: %s", VarBuffer);
ngetchx ();
}
Read more about the events.h header file: incredible miracles may be produced using event-passing techniques!
while (*(unsigned char *)0x600017 != 255);
But, the calculator freezes. I tried to use peek macro, as in
while (peek (0x600017) != 255);
No fortune again (I know that this is, in fact, the same as above). What is wrong?
volatile keyword to make the address 'volatile',
i.e. to say to the compiler that this address may be changed unpredictably. So, the correct
code is
while (*(volatile unsigned char *)0x600017 != 255);
Starting from TIGCCLIB 2.3, a new macro peekIO is introduced, to make life easier. You can do
while (peekIO (0x600017) != 255);
Basically, peekIO works exactly like peek, but prevents any unwanted optimizations
generated by the compiler. Always use peekIO
(or peekIO_w) for reading memory-mapped I/O ports, else you may have
troubles. The _rowread function in TIGCCLIB releases prior to 2.3 also
caused similar troubles, now this is corrected too.
peekIO may be used even for reading bytes into the memory, but peek
will generate better code when working with memory. However, use peekIO to read any memory
location which may change in a way which is unpredictable from the aspect of a normal program
flow (for example, a memory location which is changed in the body of the interrupt handler).
Note: For sending bytes to I/O ports, the macros pokeIO and
pokeIO_w are also introduced, which are more reliable for
this purpose than poke and poke_w.
#define GetBignumArg (ap, bn) \
instead of
#define GetBignumArg(ap, bn) \
Hardly visible, isn't it?
off;
instead of
off ();
This is very common misinterpretation of usage of argument-less function. You may ask why
the compiler did not display any errors? See, whenever you use the function name alone
(without the parenthesis), it is automatically converted to a pointer to the function
(this is very useful feature if used properly). So, 'off' alone is interpreted
as a pointer, so you have an statement which consists of an effect-less expression, similarly
as if you wrote
2 + 3;
This is not illegal in C, but it has no any effect (eventually, you will be warned by the compiler that the statement has no effect, if you enable enough strong warning checking). Such misinterpretations are very often with newbie users, so I will elaborate this in more details. Suppose that you wrote
key = ngetchx;
You probably want to call ngetchx function to get a keypress,
but instead, you will assign the address of this function to the variable 'key',
because the function name without parenthesis is automatically converted to a pointer
(however, you will be warned by the compiler that it is very ugly to assign a pointer to
an integer variable without an explicite typecast). Instead, you need to write
key = ngetchx ();
Sometimes, you will not get even a warning. Look the following example:
void *qptr = kbd_queue;
The intention was probably to put the address of the keyboard queue into the 'qptr'
variable, but instead, the address of the function kbd_queue
itself will be taken! As the function name is converted to a pointer to a function, and as you
assign it to a void pointer (which is assign-compatible with all other pointer types), you will
not get even a warning!
Bytes of the integer in little endian (up to 255 bytes);
The number of bytes occupied by integer (one byte);
POSINT_TAG or NEGINT_TAG, depending on the sign.
These integers are kept on the expression stack, or in TI-Basic variables (of course, in TI-Basic variables, there are two extra bytes at the begining, which represents the length of a variable). In other words, integers are kept in a structure which is similar to the BN structure defined in the rsa.h header file, except the length byte is at the end, not at the begining (because data on the expression stack is always in RPN). So, such a structure can't be strictly represented using valid C syntax, because it requires something like
struct big_int
{
BYTE data[size]; // but 'size' is not known in advance
BYTE size;
BYTE tag;
}
Routines for multiplying and dividing very long integers surely exist in the TIOS, but the strange fact is that such routines are not in the TIOS jump table (read: they are not usable). Anyway, you can always use general eveluating routines like NG_rationalESI and NG_approxESI. For example, to multiply two very-long-integers, you can use the following template:
push two very-long-ints on the estack push_quantum (MULT_TAG); NG_approxESI (top_estack);
However, two routines for working with long integers are in TIOS jump table:
for calculating '(A*B)%N' and '(A^B)%N' where "%" is the "modulus" operation,
and "^" is "raising to a power". These operations are used in the
RSA cryptosystem. Both of them are defined in the rsa.h header file. They
may be used for multiplying and raising to a power: you always can set N to very a big
number, then (A*B)%N = A*B. Also, you can use them for calculating modulus
(if you set B to 1). But, I am not sure how you can simulate division. I
think that TI never uses integer division: everything like 'A/B' is kept as
a fraction, except in approx mode; TIOS then uses NG_approxESI.
#define gotoxy(x,y) MoveTo (6*x-6,8*y-8) // for 6x8 font #define gotoxy(x,y) MoveTo (8*x-8,10*y-10) // for 8x10 font
Here I assumed that top-left corner is (1,1) as on PC. Note that you MUST NOT put a space between gotoxy and left bracket (else the preprocessor will define an argument-less macro). You can also define an universal gotoxy macro which will work regardless of current font setting, using smart GNU C macros:
#define gotoxy(x,y) \
({short __f=2*FontGetSys(); MoveTo((4+__f)*(x-1),(6+__f)*(y-1));})
You will not be able to understand this if you are not familiar with GNU extensions.
// Launcher for program called "example"
#define USE_TI89
#define USE_TI92PLUS
#define USE_V200
#include <tigcclib.h>
#define fatal(s) ({ST_showHelp (s); return;})
void _main (void)
{
char *fptr, *cptr;
unsigned short plen;
SYM_ENTRY *SymPtr = DerefSym (SymFind (SYMSTR ("example")));
HANDLE h;
if (!SymPtr) fatal ("Program not found");
h = SymPtr->handle;
if (HeapGetLock (h))
{
cptr = fptr = HeapDeref (h);
h = 0;
}
else
{
cptr = fptr = HLock (h);
}
plen = *(short*)(cptr) + 3;
if (SymPtr->flags.bits.archived)
{
if (!(cptr = malloc (plen)))
{
if (h) HeapUnlock (h);
fatal ("Out of memory");
}
memcpy (cptr, fptr, plen);
}
enter_ghost_space ();
EX_patch (cptr + 0x40002, cptr + plen + 0x3FFFE);
ASM_call (cptr + 0x40002);
if (h) HeapUnlock (h);
if (cptr != fptr) free (cptr);
}
If you are not an expert, use this program as-is.
For anybody who wants to know more about the 8K limit: on HW1 calcs it is a pure software
limit which can be broken by intercepting some error traps (used in DoorsOS and UniOS),
or by making a short launcher which relocates the called program and jumps to it bypassing
the TIOS check. This is what I used. But, yet another problem must be solved: HW2. Namely, it
has built-in a hardware device which does not allow that PC can be out of certain zone,
so the launcher itself will not help. This device can't be turned off normally by
software, but Julien Muchembled found a "hole" in the protection system which allows
turning out this device (this is what his HW2 patch does): this is a very nasty
method, and I don't want to explain this here. But, this protection device can be fooled in a
simple way: it protects only the "normal" RAM address space (i.e. the first 256K of RAM). As
RAM is partially decoded, address x and x+256K are the same for TI-89, but if you jump to
x+256K instead of x, the protection will not be activated! Look at the launcher to see how
it works, and you now can understand why I added 0x40000 (i.e. 256K) to the address in two
places. First, I relocate the called program as it is located on x+256K instead of x,
then I call the subroutine on x+256K instead of x.
AMS 2.04 and AMS 2.05 introduce yet another level of protection (which existed before, but
caused problems very seldomly): on these AMS versions, even jumping
to the "ghost address space" (above 256K) is not possible if the program counter is out
of a certain area. (This caused the so-called "second launch crash"; if you have AMS 2.05 with
any longer program like TI-Chess, you probably know what I am talking about.) That's why I
introcuced a new function called
enter_ghost_space: its only purpose is to
smoothly bypass this new protection. Note that
enter_ghost_space is
deprecated now; you should use the
EXECUTE_IN_GHOST_SPACE
directive instead in most cases.
SET_FILE_IN_USE_BIT
may solve the problem.