-1

SOLVED: The external function to create an in-memory datalink between the application and the solver environment was changed for the newer Lingo version and it was called incorrectly. Fixing this fixed the access violation.

In the process of converting a number of old applications from Delphi 5 to Delphi 10.4 and from there from 32 bit to 64 bit, I've come across some weird behavior of the Delphi 10.4 64 bit compiler. At the moment, the code works perfectly within Delphi 10.4 when compiled in 32 bit. Furthermore, I've managed to adjust the code and settings/configuration so that the project compiles in 64 bit mode. Although not yet perfect, I could do some functionality testing and that shows some weird behavior when trying to call an external, 3rd party supplied, library (I don't think it's relevant, but the call is to the Lingo17 64 bit modelsolver).

The library call happens in a separate unit and looks like this:

function LSexecuteScriptLng( pnEnv: Integer; cScript: pAnsiChar): Integer;
 stdCall; external 'Lingd64_17.dll';

When run in debugging mode, a 'Debugger Exception Notification' popup is prompted, with text 'Project MyProject.exe raised exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'' and button options 'Break', 'Continue' and 'Help'. When I choose Break, it goes to the line where the call to LSexecuteScriptLng is made:

intLingoError := LSexecuteScriptLng(intLingoEnvironment,PAnsiChar(strModelExecuteScript));

EDIT: Here is all the code relevant to strModelExecuteScript:

unit unLingoSolver

const
  cCrLf= Chr(10) + Chr(13);     // Carriage return line feed

type TLingoSolver = class(TObject)
  private
    strModelScript : string;

    function Solve(Model : TModel; LingoSettings : TLingoSettings): boolean;

  protected

  public
      property ModelScript : string read strModelScript write SetModelScript;

procedure TLingoSolver.SetModelScript(strModelText: string);
  begin
    strModelScript := 'MODEL:' + cCrLf +
                      'TITLE:COCOS;' + cCrLf +
                      Trim(strModelText) + cCrLf +
                      'END' + cCrLf ;
  end;

function TLingoSolver.Solve(Model : TModel ; LingoSettings : TLingoSettings): boolean;
  var strModelExecuteScript : AnsiString;
      ModelScript : string;
begin
  ModelScript := Model.ModelText;      // Model.ModelText is assigned from a TOracleQuery earlier in the code.
                            // It is basically a long string filled with the definitions and equations of the actual model
  strModelExecuteScript := '';
  strModelExecuteScript := LingoSettings.GetSettings() ;     // See code in different unit below
  strModelExecuteScript := strModelExecuteScript +
                           ModelScript           + cCrLf +
                           'GO'                  + cCrLf +
                           'QUIT'                + cCrLf ;

  intLingoError := LSexecuteScriptLng(intLingoEnvironment,PAnsiChar(strModelExecuteScript));
end;

end.

// The following code is in a different unit

unit unLingoSettings;

interface 

uses sysutils;

const cCr = Chr(10);      // Carriage return

implementation

 function TLingoSettings.GetSettings() : string ;
 var strSettings : string;
 begin
    strSettings :='SET TERSEO ' + IntTostr(Settings.TERSEO) + cCr +
                  'SET ECHOIN ' + IntTostr(Settings.ECHOIN) + cCr +
                  'SET LINLEN ' + IntTostr(Settings.LINLEN) + cCr +
                  //TIMLIM is controlled by callback function. See unLingoSolver.pas
                  //Setting TIMLIM > 0 will cause error "Time limit execeeded" of first calculation after midnigt.
                  //This is a bug in Lingo. Confirmed by Lindo Systems. 
                  //'SET TIMLIM ' + IntTostr(Settings.TIMLIM) + cCr +
                  'SET ITRLIM ' + IntTostr(Settings.ITRLIM) + cCr +
                  'SET NSTEEP ' + IntTostr(Settings.NSTEEP) + cCr +
                  'SET NSLPDR ' + IntTostr(Settings.NSLPDR) + cCr +
                  'SET INFTOL ' + StringReplace(FloatToStr(Settings.INFTOL),',','.',[rfReplaceAll, rfIgnoreCase]) + cCr +
                  'SET FNFTOL ' + StringReplace(FloatToStr(Settings.FNFTOL),',','.',[rfReplaceAll, rfIgnoreCase]) + cCr +
                  'SET NOPTOL ' + StringReplace(FloatToStr(Settings.NOPTOL),',','.',[rfReplaceAll, rfIgnoreCase]) + cCr +
                  'SET DERCMP ' + IntTostr(Settings.DERCMP) + cCr +
                  'SET NCRASH ' + IntTostr(Settings.NCRASH) + cCr +
                  'SET LCRASH ' + IntTostr(Settings.LCRASH) + cCr +
                  'SET MULTIS ' + IntTostr(Settings.MULTIS) + cCr +
                  'SET DUALCO ' + IntTostr(Settings.DUALCO) + cCr +
                  'SET SELCON ' + IntTostr(Settings.SELCON) + cCr +
                  {$IFDEF LINGO17}
                  'SET NLPVER ' + IntTostr(Settings.NLPVER) + cCr +
                  {$ENDIF}
                  '' ;

    result := strSettings;        // Actual values of Settings are irrelevant for the question
 end;
end.

At this point, I thought the error resembled this SO post: Delphi 10.3.1 compiler generates code that issues an exception when compiled to 64 bits, but the weird assembly code from that post does not show up in my project when I check CPU-VIEW. Just to be sure, I tried the accepted solution from that post by adding const before the cScript parameter in the LSexecuteScriptLng function, but that does not change anything.

It gets even more weird when I place a breakpoint on the codeline where the Debugger breaks to. From there I can step into the LSexecuteScriptLng function in the separate unit, so the ACCESS_VIOLATION when calling that function is gone apparently? But the results returned by the .dll call make no sense; no actual model calculation results and status/logging text message that seem to randomly contain characters from an Asian alphabet? (I'm not trying to be racist/ignorant here, I just simply don't know the characters I'm seeing, here's an example from the Lingo logfile:

    㸀  伀䈀䨀                  >  MI⁐                 㸀  䴀䤀倀06-09 10:48:40:739  Mode                 㸀 䰀椀渀最漀 渀漀 猀漀氀甀琀椀漀渀 猀琀愀                  㸀 䔀砀攀挀 㘀ⴀ 㤀 ㄀ 㨀㐀㠀㨀㐀 㨀㜀㌀㤀  䰀椀渀最漀 猀漀氀瘀椀渀最 攀爀爀漀爀㨀 㘀ⴀ 㤀 ㄀ 㨀㐀㤀㨀㐀㜀㨀㐀㜀㄀  䰀椀

This is from a logfile that is otherwise comprised of perfectly comprehensible English.

J. Dough
  • 77
  • 7
  • Almost certainly a bug in your code relating not to 64 bit but to text encoding. But we have nowhere enough code to know for sure. You'll want to add some proper detail. – David Heffernan Sep 06 '22 at 10:22
  • What is the definition of the variable/function _strModelExecuteScript_, since you hard cast it to PAnsiChar? – HeartWare Sep 06 '22 at 10:53
  • @DavidHeffernan: See my edit. I've tried to compactly add all the relevant code wrt how strModelExecuteScript is constructed. Does this help? – J. Dough Sep 06 '22 at 12:35
  • A bit. At least the string cast to PAnsiChar makes sense, now. But are you sure that the 64-bit version of the library expects an ANSI string and not a UNICODE string? It would be unusual, but not unheard of... – HeartWare Sep 06 '22 at 12:38
  • @HeartWare: You make a very solid point about knowing what the library expects, because actually I don't. They supplied an interface .pas file with the 32 bit version, but not with the 64 bit version. I contacted them about that and they said I could simply use the same interface file, just change the lingd17.dll external call to lingd64_17.dll. So I did and I cross-referenced all the argument and return types with the .cs 64 bit interface file that wás supplied. Seemed fine at first glance, but then again I'm new to 32-64 bit conversions. I will send another support email to them. – J. Dough Sep 06 '22 at 12:53
  • 1
    Your `cCrLf` is wrong: it is `#10#13` but should be `#13#10`. – Andreas Rejbrand Sep 06 '22 at 13:14
  • @AndreasRejbrand: I changed the code to your suggestion, but it did not solve the issue unfortunately. – J. Dough Sep 06 '22 at 13:21

2 Answers2

3

From the online documentation:

#define pLSenvLINGO void*

int LSexecuteScriptLng( pLSenvLINGO pL, char* pcScript)

This routine is the main workhorse of the LINGO DLL that processes LINGO command scripts. The script may be contained entirely in memory, or it may contain one or more TAKE commands to load scripts from disk.

Arguments: pL Pointer to a LINGO environment created by a previous call to LScreateEnvLng(). pcScript Pointer to a character string containing a LINGO command script. Each line must be terminated with a linefeed character (ASCII 10), and the entire script must be terminated with a NULL (ASCII 0).

Return Value:

Returns 0 if no error occurred. Otherwise, one of the nonzero error codes listed in section LINGO DLL Error Codes is returned.

You are passing an Integer rather than a pointer for the first argument and in 64-bit a pointer is 8 bytes.

Function LSexecuteScriptLng( pnEnv: Pointer; cScript: pAnsiChar): Integer;
 stdCall; external 'Lingd64_17.dll';

You will also need to update other functions using the environment pointer such as LScreateEnvLng

Remko
  • 7,214
  • 2
  • 32
  • 52
  • You are absolutely correct that the code is not in line with the documentation. The main reason for that is that the documentation has been like that since *at least* Lingo9, while the actual source code that Lindo provides to interface with their .dll's has been like the code I provided in the OP. But since you mentioned it, I went and checked the .cs interfacing file that is provided with the Lingo17 64bit installation and it does actually follow the documentation this time. I will rewrite my code to be in line with the documentation to see if that changes things! – J. Dough Sep 06 '22 at 13:28
  • Looks like they've used an `int` in the 32-bit version in past version to represent a pointer which worked but was of coursed already incorrect. – Remko Sep 06 '22 at 14:14
  • I have updated the interface file and my own calling/handling code. Two versions actually, because the .cs interface file (supplied with installation) and the documentation still differ on the typing of the ```cScript``` parameter: char* in documentation, string in .cs file. Both versions compile, but when the external call is made, the application gets stuck in that call endlessly. – J. Dough Sep 08 '22 at 10:21
  • In C# the string will be marshalled, in Delphi it should still be an `PAnsiChar` as far as I can see. I don't have an actual installation to test against but unpacked the DLL's from the installer and tested like this: `intLingoError := LSexecuteScriptLng(0, 'test'#10#0);` and that didn't crash and returned (0). – Remko Sep 09 '22 at 11:37
  • Step by step I got some functions to work, mostly by handling the strings in those calls correctly, so your answer certainly helped. But when I tried to do the same with the LSexecuteScriptLng function, I can not reproduce your results. The external call looks like this now: `function LSexecuteScriptLng( pnEnv: PInteger; cScript: pAnsiChar): Integer; stdCall; external 'Lingd64_17.dll';`. When I make the exact same call as you `intLingoError := LSexecuteScriptLng(0, 'test'#10#0);`, I still get the original AV, followed by an AV in module dbkdebugide270.bpl, write of address 44204D54. – J. Dough Sep 14 '22 at 09:27
  • When I make the same function call with a valid lingo environment pointer (but the same modeltext), the lingo dll makes a callback to the callbackerror function, with error code 117 (from documentation: Invalid command') which is correct, considering the modeltext. I only know this because of a breakpoint in my callbackerror function. I suppose the dll should stop running after this error, but it does not, it keeps running endlessly like mentioned before. – J. Dough Sep 14 '22 at 09:41
-1

My best guess is that your definition of the function for 64-bit is wrong. It's unlikely that a 64-bit library uses ANSI encoding of text, so it should probably be defined as

function LSexecuteScriptLng( pnEnv: Integer; cScript: pChar): Integer;
 stdCall; external 'Lingd64_17.dll';

and at the call site, you should remove the hard cast to PAnsiChar:

intLingoError := LSexecuteScriptLng(intLingoEnvironment,{$IFDEF CPU32BITS} PAnsiChar {$ENDIF }(strModelExecuteScript));

assuming that strModelExecuteScript is a STRING / UnicodeString type.

(if you use other than 32/64 bit Windows, you might need to adjust the $IFDEF accordingly)

Also, double-check your 32-bit version. If strModelExecuteScript is a STRING / UnicodeString type then you should use

PAnsiChar(AnsiString(strModelExecuteScript))

at the call site, and either

intLingoError := LSexecuteScriptLng(intLingoEnvironment,{$IFDEF CPU32BITS} PAnsiChar(AnsiString {$ELSE } ( {$ENDIF }(strModelExecuteScript));

or

{$IFDEF CPU32BITS }
intLingoError := LSexecuteScriptLng(intLingoEnvironment,PAnsiChar(AnsiString(strModelExecuteScript));
{$ELSE }
intLingoError := LSexecuteScriptLng(intLingoEnvironment,strModelExecuteScript);
{$ENDIF }

to handle both cases.

Addendum: If the 32-bit version of the library expects ANSI string and the 64-bit version UNICODE, and the strModelExecuteScript variable is AnsiString, then I suggest that you either update the strModelExecuteScript to UNICODE string, or do the following:

function LSexecuteScriptLng( pnEnv: Integer; cScript: pChar): Integer;
 stdCall; external 'Lingd64_17.dll';

{$IFDEF CPU32BITS }
intLingoError := LSexecuteScriptLng(intLingoEnvironment,PAnsiChar(strModelExecuteScript);
{$ELSE }
intLingoError := LSexecuteScriptLng(intLingoEnvironment,PChar(UnicodeString(strModelExecuteScript));
{$ENDIF }
HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • Your assumption that strModelExecuteScript is a string is sort of correct. I've tried it both as string (and then with PAnsiChar(AnsiString()) cast in the calling line and as AnsiString, with PAnsiChar() cast in the calling line. In your suggested solution, the (64 bit) code does not compile because you're feeding a string into a pChar parameter. When I cast the string to a pChar, the code compiles but runs endlessly without result. – J. Dough Sep 06 '22 at 12:41
  • You should be able to pass a STRING/UnicodeString to a PChar parameter without casting. You can of course not pass an AnsiString to a PChar or a STRING/UnicodeString to a PAnsiChar. – HeartWare Sep 06 '22 at 12:42
  • I updated the calling code with your edit, but the result is the same, endless running without results or updates. – J. Dough Sep 06 '22 at 12:55