|
AxIThread, ActiveX library
AxIThread can manage as many independent processes as desired running in the background while your application performs another task. Each process will be executed in an independent thread (under AxIThread's control) being your program the responsible of administering its functionality.
For that purpose, the library exposes seven methods to control all the possible actions (creation, removal, priority change, etc.) and two events where to write the code to execute and retrieve errors.
AxIThread can execute any section of code from your program in an independent thread; you only need to set the function that will be the thread's entry point. A thread may have two different entry points, and they could be changed at any moment.
You may also set recursiveness, and the default function will be constantly called; set a timer, and the method will be called when the interval elapses; enable protection, and new notifications will remain locked while a call is being processed, or disable it. And you may always assign a new task to the thread that will be executed with higher preference.
You may create threads with any priority level (within the permitted) or modify its priority later assigning a lower level to less relevant tasks and higher priority to the most critical.
What's new in version 4.0
The library has been totally redesigned. Though function sintax remains as far as posible similar to version 3.0 sintax to facilitate migration, event functionality and execution context is completely different.
You can now create as many threads as desired. The number of threads is only limited by system's resources.
New initialization and termination handlers. Every new thread, whatever its creation parameters are, will execute the init section before entering the run mode, and the term section inmediately this one is exited. Both are guaranteed to be executed in any situation.
There are three events available to implement the initialization, execution, and termination units. You can override them, disabling event notification (see below) and writing your own handler functions.
The scheduling module is now mapped into an independent memory address space. In former versions, this functionality lived in the primary thread address space. When this one couldn't attend new requests (being suspended, locked or doing an intensive peocessing) the scheduler's interuptions weren't processed (being queued) until the primary thread became free. This may cause an undesided behaviour of the scheduler.
Except handlers that are dynamically allocated, all objects and resources are now automatic (allocated in the stack) and they are guaranteed to be freed in any situation by the mechanisms of the language.
I have definitively separated developer and enterprise editions of the library. Though at a functional level they behave the same, the enterprise edition requires two components to use it in design mode, but it's performance is a lot superior in running mode.
Enterprise edition
The server part now runs as an wNT/2000 service. You can have as many servers as desired.
Server configuration/verification is now made through a new user interface.
Each server can now handle 4.294.967.295 clients.
The enterprise edition of AxIThread ver. 4.0 is made up of four modules, divided in two categories: client part and server part:
Server modules a. License manager service b. Administration console
Client modules
c. AxIthread client d. Active X library
The server part must be installed on a system running NT/2000. The client part will run on any windooze based system. You only need the Active X component to use its functionality in running mode, but you need the four modules to use it in design mode. Also, you only need to distribute the Active X library with your application.
Using AxIThread
To use library's functionality, you must add AxIThread's type library to your project, through the References option in the Project menu (in the VB IDE). Choose the 'AxIThread 4.0s type library' reference (developer edition) or 'AxIThread 4.0e type library' (enterprise edition).
To obtain a valid instance, you must declare a 'Thread' object,
Public myThreadObjet as new Thread (declares the object) Public WithEvents myThreadObjet as Thread ... ... Set myThreadObjet = new Thread (creates a new instance of it) ... ... Set myThreadObjet = Nothing (frees it when not more needed)
NOTE: The second declaration enables event notifications.
Recursiveness
A non recursive thread is a passive thread, it remains inactive waiting for a message to process. When it receives a message, it performs a complete cycle to execute the requested task and enters a wait state again until a new message is available.
On the other hand, a recursive thread is an active thread; it processes messages exactly as a non recursive thread does, but when no message is available, it won’t remain waiting, it makes a new call to the default method.
Imagine a function checking if there are data available in a communications port. This function waits a given interval. Once the interval reaches its timeout value (or data become available), this function notifies the fact to its parent process.
This is the function suitable to be called from a recursive thread. This thread invokes the method and notifies results to its parent process. Thus, forever, al least while not receiving a message telling it to perform another action. The parent process is then free from performing a scheduled port checking, and need to attend only the notifications received from the secondary thread.
Recursive threads, will always process first messages posted to its message queue; only when there are no messages posted they will keep invoking the default method. So, requests made from the parent process will always be attended.
Non recursive threads, perform a very efficient wait state, consuming less processor time. Thus, they are suitable to wait for a given event to occur, or perform non scheduled tasks, as it would be read the data (which presence we know thanks to the recursive thread) received at the communications port of the previous sample.
Timers
You may set a time-out interval when creating a thread, thus, a timer will be enabled for it, so it will regularly (depending on the interval set) request the thread to perform a call to a method (which need not to to be the default one).
Such scheduled behaviour is totally independent from the starting mode of the thread (recursiveness state), being also possible, to set different tasks in one case or another; the thread, depending on the message received, will perform one task or another.
Timers may be enabled at thread creation or later; you may disable them, change their time-out interval or the task to perform at any moment.
I must point out that, recursive threads, will always perform either direct or scheduled messages with higher preference.
Running code in the background; the entry point
In an executable file, there aren't descriptions of functions or variables. All references to any of them has been turned into a relative memory address (offset) from the program's entry point. When a program loads into memory, it loads at a very concrete point of it, and any reference to data points to the memory address where it is stored. Such position is the offset from the program's origin address.
Every time a thread is created, it's told to start at a given memory location, that is, its entry point.
AxIThread, exposes an event (ThreadRun) where you may write the code to execute in an independent thread. In the VB IDE, for instance, you will see it as
Private Function Thread1_ThreadRun(ByVal nThreadID As Long, ByVal nUserData As Long) As Long (donde nThreadID será la ID del hilo que hace la llamada)
This will be the thread's entry point by default, but you may set another entry point when creating the thread. The new entry point function must conform the event's prototype, that is, it must accept two long integers by value, and it must return another long integer, p.e,
Private Function myThreadFunc(ByVal tid As Long, ByVal uData As Long) As Long Dim ValorDeRetorno as Long ... ... miThreadFunc = ValorDeRetorno End Function
To set the new thread's entry point, you only need to tell CreateThread, the new memory address
In C/C++, this is as simple as passing a function identifier to the method. In VB, you may use the AddressOf operator; it returns the memory address of the requested data. This operator has the following restrictions,
1- it can only be used in a module (.bas file) 2- it cannot be used directly, it must be used as an argument when invoking a function, so you will need a packing functión to cast the address to a long integer. 3- the AddressOf argument must be in the same module.
For instance,
Private Function PackAddress(FuncAdd As Long) As Long PackAddress = FuncAdd End Function
Public Function GetEntryPoint(whichOne as integer) as long Select case whichOne Case 1 GetEntryPoint = PackAddress AddressOf(ThreadFuncA) Case 2 GetEntryPoint = PackAddress AddressOf(ThreadFuncB) ... ... Case else GetEntryPoint = 0 End select End Sub
Public Function ThreadFuncA(ByVal tid As Long, ByVal uData as Long) As Long
'code to execute
ThreadFuncA = 0 End Function
Public Function ThreadFuncB(ByVal tid As Long, ByVal uData as Long) As Long
'code to execute
ThreadFuncB = 0 End Function
The code above must be in the same 'bas' module where AddressOf is used, but this doesn't imply that you can't call functions in another section of your program
Public Function ThreadFuncX(ByVal tID As Long, ByVal uData) As Long Dim longVar as Long longVar = FormW.FunctionV(ByVal tID, byVal uData) ThreadFuncX = ModuleY.FunctionZ(ByVal uData, longVar, ...) End Function
If you have enabled a timer you may set another entry point to it, so the thread would perform a different task when it is receiving a request from the timer.
Despite of the above, you can change the entry points at any moment. You may also, post a message to request another task (another function), that will be inmediately (and only once) executed, returning then to the default behaviour. Thus, a thread can have three different tasks to perform at a given moment.
All this, allows a great flexibility when running code in the backgroung
So, myThreadObject.CreateThread (No entry point set, default will be used, yourObject_ThreadFunc(...) event)
myThreadObject.CreateThread(1,,-1, GetEntryPoint(2)) myThreadObject.CreateThread(1,,-1, AddressOf(ThreadFuncB)) (only in the same module) (the thread's entry point will be ThreadFuncB)
You also have two new events to implement initialization and termination code for every new thread. These ones will be called inmediately before (and after) entering (and exiting) the execution unit.
Private Function Thread1_ThreadInit(ByVal nThreadID As Long, ByVal nUserData As Long) As Long
Private Sub Thread1_ThreadTerm(ByVal nThreadID As Long, ByVal nUserData As Long, ByVal nReason as Long)
As above, you may also write your own init and term handlers; again, they must conform to the event prototypes.
Some considerations about events
As I mentioned before, AxIThread facilitates three events where to write the code to run in the background:
Private Function objet_ThreadInit(ByVal nThreadID As Long, ByVal nUserData As Long) As Long
Private Function objet_ThreadRun(ByVal nThreadID As Long, ByVal nUserData As Long) As Long
Private Sub objet_ThreadTerm(ByVal nThreadID As Long, ByVal nUserData As Long, ByVal nReason as Long)
ThreadInit will be executed just before entering the run section, so initialization may be performed before passing the flow of execution to it. This event takes two parameters, the thread's ID, and the user defined argument (see CreateThread). Use the return value of this function to signal abnormal circunstances. Any non zero value, will be interpreted as initialization failure, the execution flow will jump directly to the termination handler, and the thread will be removed. If you don't set the return value, it will be by default null (in other words zero), and will be interpreted as initialization success.
ThreadRun the heart of all. Write here the code to process.
ThreadTerm will be processed inmediately after leaving the execution block, so cleaning tasks (such as freeing resources) may be safely performed. It takes a third parameter indicating the reason of termination.
You may use the default events or implement your own handler routines, or use both at the same time, changing dinamycally the code section to process.
So, p.e: Private Function init(ByVal tid as long, ByVal hwnd As Long) As Long init = 1 ' assume error
If someFunction(ByVal hwnd) = 0 Then Exit Function
' initialize data
init = 0 ' success
End Function
Private Function run(ByVal tid As Long, ByVal hwnd as Long) As Long 'code to run
run = IIf( zFunction(ByVal uData) <> 0,-1,0) ' set the return value
End Function
Private Sub term(ByVal tid As Long, ByVal hwnd as Long, ByVal nReason as long) If nReason = &H7FFFFFFA Then Exit Sub ' initialization failed, nothing to do here
For i = 0 To Forms.Count - 1 If Forms(i).hwnd = hwnd Then unload Forms(i) Exit Sub End If Next i
End Sub
On thread creation, set the handler routine for each section you wish to use, or none if they are to be processed in the default events or not proccesed at all: Public Function NewThread() As Long Dim x As Form Set x = new Form
NewThread = CreateThread( , , -1, ByVal x.hwnd, , , AddressOf run, , AddressOf init, AddressOf term) ' set handlers, user defined argument, and return value
NewThread = CreateThread( , , -1, ByVal x.hwnd, , , AddressOf run) ' no initialization nor termination, or they will be handled in the default events
End Function
As you would imagine, the value passed as an entry function, may point to a wrong address. Thus, when a thread raises an exception, the action to be taken will follow the guidelines:
- if the thread is scheduled or recursive and the exception was raised by a user-defined entry point: - in the first case, the timer of the thread is removed - in the secon one it's recursive state is disabled In both cases, thread remains alive; if you have defined a error handler routine, you will get an error notification in it. (see error codes table below). - if the thread is scheduled or recursive and the exception was raised by the default entry point: - if you have enabled event notifications, same as above. - if you haven't enabled it, the thread is removed for it hasn't any valid entry point. You will get the error code in the termination handler.
There is no event defined to handle errors. If you want to process errors, you have to define your own error handler routine, following the prototype: Private Sub errorHandler(ByVal tid As Long, ByVal uData As Long, ByVal nErrorCode as Long)
where,tid, thread's ID . uData, The user-defined data. nErrorCode, error code.
Possible values are (for the error handler): -. 2147483632 (hex 0x7FFFFFF0), non-recursive (default function) -. 2147483634 (hex 0x7FFFFFF2), non-recursive (message parameter) -. 2147483635 (hex 0x7FFFFFF3), scheduled non-recursive -. 2147483636 (hex 0x7FFFFFF4), recursive (default function) -. 2147483638 (hex 0x7FFFFFF6), recursive (message parameter) -. 2147483639 (hex 0x7FFFFFF7), scheduled recursive
In 3 & 6, timer has been disabled. In 4 & 5, thread state has been changed to non-recursive.
Error codes (termination handler): -. 2147483642 (hex 0x7FFFFFFA), initialization failure -. 2147483644 (hex 0x7FFFFFFC), no error; normal execution. -. 2147483647 (hex 0x7FFFFFFF), no entry point for this thread.
NOTE: when the library unloads from memory (for the application is being closed or the reference to it is being freed) many threads may be exiting at the same time and executing their termination handler/s. Avoid to perform time consuming tasks in the term handlers.
Protected mode
The execution of external code may be (or not) protected: only one invocation to a function will be made at a time; any new notification will remain locked while another function call is being processed. If multiple threads may access from within the body of a function to non 'thread-safe' data (a global variable, for instance) you'd better enable protection to avoid data corruption (see below). If protection is disabled, notifications will be made, regardless how many calls are being processed. If data are 'non-sensible' (thread-safe) such as local variables, returned values, indexed arrays, or even a read-only global, you can disable protection. In one hand, you obtain more security, in the other, enhanced performance. Chossing one model or the other depends on how you implement the code in your program. If each thread has a different entry point, or it doesn't work with non thread-safe data, protection won't be necessary, and the execution of your code will be more efficient
Protection is a per-thread property; thus, if multiple threads work in the same section of code, and such space address is sensible to concurrent access, you must enable protection to each one of these threads. Mutual exclusion only works within protected threads, and it's only guaranteed between them; protected threads don't know nothing about non-protected ones, and these nothing know about those.
Imagine three threads accessing the same function body; this function increments a counter, then checks its value and, depending on it, certain operation is performed or not. If you have enabled protection only in two of them, these two can't access the guarded section simoultaneously, but the third one would enter this function regardless any of the protected ones being executing it or not, and one of the protected threads would enter the function regardless the non-protected is executing it or not.
How protection affects running code
If you plan to use multiple threads accessing the same section simultaneously and don´t want to enable protection, don't use global variables in the body of the notification function, except in case they are read-only variables. Look at the following piece of code:
Private Function EntryPointFunction(ByVal nThreadID As Long, ByVAl uData as long) as longGlobalData = GlobalData + 1 If GlobalData = 5 Then... EntryPointFunction = ThreadReturnValue ...End IfEnd Function
Every time this function is called, the value of the GlobalData variable is incremented; then the resulting incremented value is compared to 5. If both values match, certain operations are performed and the thread's default return code is set. If a thread call this function when GlobalData is set to 3 and increments its value but, before checking the result, the system gives control to another thread that also calls this function and increments the value of the variable, when control returns to the first one the content of GlobalData will be 5 (and it will enter the if block), not the expected 4.
This is the kind of situation you must avoid, either using local variables (so each thread will work with its own copy), or using arrays,
Private Function EntryPointFunction(...)GlobalData(nThreadID) = GlobalData(nThreadID) + 1 If GlobalData(nThreadID) = 5 Then... EntryPointFunction = ThreadReturnValue(nThreadID) ...End IfEnd Function
If you absolutely need to check a global variable, make sure it is read-only or implement some kind of synchronization. As a very simple example,
Private Function EntryPointFunction(...)Static Lock as Boolean
Do while LockSleep(1) 'give control to another threadLoop
Lock = true GlobalData = GlobalData + 1 If GlobalData = 5 Then... EntryPointFunction = ThreadReturnValue ...End If Lock = falseEnd Function
If you still experiment problems, then you must enable protection.
Passing arguments
Methods exposed by AxIThread require compulsory and/or optional parameters. You need not to set an optional parameter, thus, the following calls are correct,
nThrID = myThreadOb.CreateThread nThrID = myThreadOb.CreateThread(1,,500,,1,,,nResult) nThrID = myThreadOb.CreateThread(, 1, , , ,25000)
Optional parameters need not to be set, but the arguments you set must be in their position.
Methods
In the format description of methods, arguments in brackets are optional. Also, if a value is indicated, it will be the default value for the argument, when not given.
PauseThread
Suspends thread execution until the thread is resumed again or removed
This method doesn't return any value.
Formats (C & VB) void PauseThread (long nThreadID)
PauseThread (nThreadID as long)
nThreadID, ID of the thread to suspend.
ResumeThread
Resumes a thread previously suspended.
This method doesn't return any value.
Formats (C & VB) void ResumeThread (long nThreadID)
ResumeThread (nThreadID as long)
nThreadID, ID of the thread to resume.
ThreadPriority
Sets thread's priority to the value indicated in nLevel.
This method doesn't return any value.
Formats (C & VB) void ThreadPriority (long nThreadID, [long nLevel = 0])
ThreadPriority (nThreadID as long, [nLevel as long = 0])
nThreadID, thread's ID.
nLevel, new priority level. Posible values are from 0 (lowest) to 4 (highest), being 2 the system's default value (8).
CreateThread
Creates a new thread of execution, according the requested parameters; the new thread will always execute a complete cycle when it starts its execution, except if it is a scheduled one (in this case the first cycle won't take place until the interval elapses).
If there is no error it will return a long integer containing the thread's ID. You must use this ID when calling other methods. Otherwise, it will return 0, and will set an error code in nResult.
Formats (C & VB) long CreateThread([BOOL bProtected = true], [short nPriority = 2],[long nRetCode = 0], [long nUserParam = 0], [long nAutoCallInterval = 0], [BOOL bRecursive = false], [long RunHandler = 0], [long AutoHandler = 0], [long InitHandler = 0], [long TermHandler = 0], [long ErrHandler = 0], [long *nResult])
CreateThread([bProtected As Boolean = true], [nPriority As Integer= 2],[nRetCode As long = 0], [nUserParam As long = 0], [nAutoCallInterval As long = 0], [bRecursive As Boolean= false], [RunHandler As long = 0], [AutoHandler As long = 0], [InitHandler As long = 0], [TermHandler As long = 0], [ErrHandler As long = 0]) As long
bProtected, protection enabled (true), or disabled (false). By default, it will be enabled, (see note).
nPriority, default priority level. Allowed values are from de 0 (lowest) to 4 (highest). Default is normal (2).
nRetCode, thread's return code (value to force thread's exit), (see footer).
nUserParam, user argument (will be sent to all handlers).
AutoCallInterval, scheduler's time-out interval, (see note).
bRecursive, when (true) thread will be started as recursive, (see note).
RunHandler, thread's entry point memory address, (see note).
AutoHandler, scheduler's entry point memory address.
InitHandler, initialization entry point.
TermHandler, termination entry point.
ErrHandler, error handler entry point.
nResult, creation result.
Error codes returned in nResult
StopThread
Stop the execution of thread nThreadID.
If there is no error, it will return 0; or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long StopThread(long nThreadID, [BOOL bIgnoreMsgQueue = false])
StopThread(nThreadID As long , [bIgnoreMsgQueue As Boolean = false]) As long
nThreadID, ID of the thread to stop.
bIgnoreMsgQueue, ignore pending messages. When false, pending messages will be processed before removing the thread. When true, the thread will be removed inmediately, ignoring pending messages.
LoopThread
Post a new message in nThreadID message queue. Optionally, you may also set a memory address where to perform the requested action.
If there is no error, it will return 0; or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long LoopThread(long nThreadID, [long FuncAdd = 0])
LoopThread(nThreadID As long, [FuncAdd As long = 0]) As long
nThreadID, thread's ID.
FuncAdd, memory address where to perform a cycle.
Recursive threads are constantly invoking its default method; programmed (scheduled) ones, every time the time-out interval elapses; recursive-scheduled in both cases. However, non-recursive nor programmed threads, call its default entry point once when they are created, and then remain inactive waiting a request to perform a new cycle.
Thus, this function covers two purposes:
first, to request a recursive and/or scheduled thread to perform an action different from the default, call this function, with the memory address of some function of your program. Thus, the thread may be required to perform up to three different tasks at a given moment.
secondly, to request an inactive thread (non-recursive nor programmed) to perform a new cycle either on its default method, or over the function indicated as FuncAdd.
ModifyThread
Toggles recursiveness state; sets, modifies or removes a scheduler; changes the default's and the scheduler's entry point.
If there is no error, it will return 0; 1 if is not posible to set a timer or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long ModifyThread(long nThreadID, [BOOL bToggleState = 0], [long FuncAdd = 0],[long nNewInterval = 0], [long AutoFuncAdd = 0]) ModifyThread(nThreadID As long, [bToggleState As Boolean= 0],[FuncAdd As long = 0], [nNewInterval As long= 0], [AutoFuncAdd As long = 0])
nThreadID, ID of the thread to modify.
bToggleState, toggle recursiveness state.
FuncAdd, new default entry point, (see note - see footer also).
nNewInterval, new scheduler's interval, (see note).
AutoFuncAdd, new scheduler's entry point, (see footer again).
Values set in FuncAdd and in AutoFuncAdd will be interpreted:
- (-1), no changes. - (0), disable entry point; from now on, calls will be made on the default ThreadFunc event - any other value will be treated as the new entry point.
If the value set as entry point is not correct you will receive an error event indicating the cause and the action taken, (see note).
Installing the software
The product needs to introduce configuration information in the system's registry to work properly. For that reason, you must install the software using the corresponding installation program and/or script.
These are the following:
Developer edition Click over the regAx link to install the software, or the unregAx link to remove it.
Enterprise edition Installing a server Just run the program sv_inst.exe. NOTE: The service must be installed on a NT/2000 based system, any attempt to install it under other platform will fail. Installing a client Run the cl_inst.exe program. NOTE: At some point, the program will request the machine running the service. You must enter the server name or IP address here.
Uninstall Open the Add/Remove programs applet from the Control Panel and select
- AxIThread 4.0 license manager to remove de server, or - AxIThread 4.0 components to remove the client.
Ordering info
I'm not going to tell you what shareware is, if you want you can read the article about shareware at the end of this file. But I'll tell you some considerations about my point of view.
First of all, when designing this library, I was thinking about making use of certain interesting characteristics (only accessible calling the Windooze API) of the 'modern' systems easy. I think you will find it useful and easy to handle.
It took me some time and some work until I got satisfied with the performance and accuracy of a component I hope will help you in your developments.
Secondly, programming is not my only occupation (really, I'm a carpenter), so I must get some kind of compensation for the work I take, and the hours of 'suffering' that my family have to bear with.
I don't like sharing components with unavailable characteristics or time limitation, so AxIThread is totally functional. But I think that if someone gets registered, he must obtain some benefit, despite the little it may be. In this case, the only advantage for registered users (apart from contribute supporting the shareware philosophy) is the removal of the copyright window.
To order, mail me to cmaestra@arrakis.es or the mailing address below, indicating the following data:
Name wanted in the license registration (commercial or particular). Mail address (eMail). Number of licenses (corporate licenses only). Don't forget to indicate the edition you wish to register (enterprise or developer).
Payments must be in EEC (CEE) euros or US dollars, made by international postal money order or by check payable to: Miguel Perancho Hevia San Bartolomeo da Freixa 32514 - Boborás, Ourense Spain
or (preferred) by wire transfer to the account nº: 2038-4028-57-6000036475
indicating in any case the name used to register. Once the payment is effective, you will receive the license key to register the library.
Pricing list: (Jan-Dec 2001)
AxIThread, developer edition, 75 euro, $75 US
AxIThread, enterprise edition,
3 first licenses, 470 euro, $470 US
aditional licenses
3-9, 45 euro, $45 US (p/unit)
10-25, 36 euro, $36 US (p/unit)
25-100, 24 euro, $24 US (p/unit)
100-?, contact me
AxIThread, educational institutions
Contact me for special pricing
About shareware
Not long ago, we had to buy commercial programs totally 'blind', evaluating the product after purchasing it. Today, as a previous step of the final product being released, it's very common that we have the chance to test a pre-release, 'Demo' or 'Beta', limited its use to not all of its characteristics or with time limitation, for evaluation purposes. The shareware is quite responsible of this fact.
The time when shareware or freeware programs were worse software solutions has gone, as an example, evaluate any shareware design program that you can probably install from that cd-rom that comes along with your favorite magazine.
As you will know, the philosophy on which shareware is based is 'try before buy'. So, a user can test the excellence (or its absence) of the software before taking the decision of purchasing it. And, normally, there is less money required. It's true (but not always) that a single programmer, or little group of them, cannot compete against a team formed by many persons and millionaire resources, developing complex projects. But, in the other hand, he may offer us other solutions (components, plug ins or programs) these teams don't care about.
Us, those who stay hours and hours sitting in front of (the evil) the computer, occasionally realize that a marvelous program, which occupies over 200 'tasty' megs of our disk and does lots of 'well done' things, lacks a little detail which will make our work simplier or more efficient, and someone (somewhere in the world) has found a way to solve this detail, offering his solution to us, changing its dedication to it somehow.
Shareware is based in the confidence a developer of an utility which saves some of our time has in we will compensate his/her efforts. It's not unusual to find those who only ask for a letter making them know who we are, where we are and what we think about their work, though the most usual, and in this case I have decided so, is an economic compensation.
This is shareware. I offer something you can take and try on your own. If you, after evaluating, decide to use its functionality, you must accept the conditions under which I offer it.
All this means nothing without the support of the users. If nobody gets registered, the programmer will give up developing, and we will be left in the hands of a few. The advantages (for you) supporting shareware are obvious: you will have thousands of software solutions against (generally more expensive, though generally also more complete) 'commercial' software.
But, al least you will have the chance to select from many possibilities, not from only a few.
Now, it's your decision...
The author (or the one to blame)
|
|