DLL Memory Handling
Visual Prolog has a garbage collector, which reclaims memory when it is no longer in use. However, the garbage collector does not know whether a Delphi program uses a piece of memory or not, and therefore it may reclaim the memory even though the Delphi part of the program still needs it (most likely this will result in an access violation at some later point).
Such problems memory always exist between exe and dll's. And therefore you must decide on a memory handling scheme. Basically there are two schemes to choose.
Caller provides memory
This scheme is used in most Windows API calls and in most C/C++ context. The principle is that the caller allocates the memory and the callee copies the result into that memory.
In you getString case the vip declaration would look like this:
class dllExport predicates getString : (string ResultBuffer) language stdcall. end class dllExport
Notice that the string parameter is an input argument. This is because the caller must provide a buffer for the result. (Very often there would also be another argument stating the size of the buffer, such that the DLL will not write outside the buffer).
The corresponding implementation could look like this:
implement dllExport clauses getString(Buffer) :- Result = "ResultString", ByteCount = sizeOfDomain(char) * (sting::length(Result)+1), memory::copy(uncheckedConvert(pointer, Buffer), uncheckedConvert(pointer, Result), ByteCount). end implement dllExport
The last two lines in the clause should be turned into a separate predicate (in fact, it should ought to be in PFC already, but I can't seem to find it).
Callee provices a clean-up routine
An alternative approach is to allow callee memory to float into the caller, and then give the caller a predicate to call once the memory should be released.
In the getString example it could look like this:
class dllExport predicates getString : () -> string ResultBuffer language stdcall. predicates releaseString : (string ToRelease) language stdcall. end class dllExport
In this case the caller will simply receive the string from the DLL, but once the caller is done with the string it must call releaseString to have it released.
In the implementation we have to make sure that the memory is not garbage collected until releaseString has been called. We can achieve this by asserting the string in a class fact and retract it when it is released, because memory that can be reached from a class fact is never garbage collected.
implement dllExport class facts keepAlive : (pointer ToKeepAlive). clauses getString() Result :- Result = "ResultString", assert(keepAlive(ucheckedConvert(pointer, Result))). clauses releaseString(ToRelease) :- retractAll(keepAlive(uncheckedConvert(pointer, ToRelease))). end implement dllExport
The reason that I assert pointers and not strings, is because otherwise the retract might retract a wrong string, because strings are retracted based on their value (i.e. the characters in the string). Pointers are also retracted based on their value, but their value is a memory address.