-5

I have this code in C that I want to port to Delphi, but i can't make it work.

.CPP CODE

#include <Windows.h>

#include <io.h>
#include <stdio.h>

#include "GLibExp.h"
#pragma comment(lib, "GLib.lib")

void MyCFunc(LPCTSTR GStr)
{
    GFile GVar = NULL;
    GVar = GrfLoad(GStr, 1);
    if ( !GVar )
    {
        printf("Error during loading!\n");
    } else
        printf("All fine!\n");

    GrfFree(GVar);
    system("pause");
}

void main()
{
    CHAR StrG[MAX_PATH] = "Test.grf";
    MyCFunc(StrG);
    return;
}

GLibExp.h

#ifndef GLibExpH
#define GLibExpH

#if defined(GRF_DLL)
#define GEXPORT __declspec(dllexport)
#else
#define GEXPORT extern
#endif

class CGFILE;
typedef CGFILE* GFile;
//typedef void* GFile; //Also works like this

#ifdef __cplusplus
extern "C" {
#endif
GEXPORT GFile GrfLoad(const char *GName, unsigned char Mode = 1);
GEXPORT void GrfFree(GFile GVar);
#ifdef __cplusplus
}
#endif

#endif//GLibExpH

The program calls a DLL at runtime to use the GRFLoad and GRFFree functions. I try to port this to Delphi, but without success.

Delphi/Lazarus Code:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;

{$Link GLib.lib}

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

function GrfLoad(const fname: PChar; Modo: Boolean): Pointer; cdecl; external 'GLib.dll';

procedure TForm1.Button1Click(Sender: TObject);
var
  PVar: Pointer;
begin
  PVar:= GrfLoad(PChar('test.grf'),false);
end;

end. 

If I comment out the line {$Link GLib.lib}, the program runs, but it always crashes when I call GRFLoad (the program stops working and then closes). If I leave in the {$Link GLib.lib} line, the program don't compile and reports an error:

project1.lpr(20,1) Error: Illegal COFF Magic while reading GLib.lib

Any hints?

NOTE: I just added a link to a Visual C++ 2010 project with all files needed. In fact, I just made a "New project -> Win32 Console Application" (I mark "empty project" in the Wizard), add a new CPP file and paste the code, and change "Properties -> Configuration Properties -> Linker -> Enable Incremental Linking: NO", that all.

https://drive.google.com/open?id=1JxW4Wra_kT8gfBda1t0WWqagzazzQVne

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • runtime error. "The program stop working" and then close. – Hermenegildo Gonzalez Aug 10 '18 at 18:17
  • Does `GEXPORT __declspec(dllexport)` correspond to `stdcall`? – MBo Aug 10 '18 at 18:18
  • don't know if corresponds to stdcall. The lib are compiled and don't have the source code. – Hermenegildo Gonzalez Aug 10 '18 at 18:21
  • Edit the question to add a full example in Delphi/Lazarus and this comment: If i comment the line "{$Link GLib.lib}" The program runs but always crash when call "GRFLoad" ("the program stops working" and then close). If i leave the "{$Link GLib.lib}" line the program don't compile and send the error: "project1.lpr(20,1) Error: Illegal COFF Magic while reading GLib.lib" – Hermenegildo Gonzalez Aug 10 '18 at 18:45
  • 3
    You definitely don't need `GLib.lib`. There are some reasons to fail with DLL's - wrong import name due to name mangling, wrong caliing convention, wrong parameter description, memory handling issues (different memury managers, using internal Delphi managed types lke strings). Seems the first one is not your case - static importing reports about wrong name during loading. – MBo Aug 10 '18 at 18:56
  • Assuming Visual C++ compiler, `const char *` would be `PAnsiChar`, `unsigned char` then `Byte`, and calling convention `stdcall` (with default compiler settings). – Victoria Aug 10 '18 at 19:14
  • 1
    @Victoria No, default calling convention with C and C++ compilers will be cdecl – David Heffernan Aug 10 '18 at 19:39
  • @MBo: no, `__declspec(dllexport)` is like the Delphi `exports` directive. It does not denote a calling convention. So the proper calling convention is `cdecl`. But the name may be different (e.g. with a leading underscore). This can be found out with a tool like Dependency Walker. – Rudy Velthuis Aug 10 '18 at 19:48
  • @David, true, my mistake, VC++ 32-bit target's default calling convention is `__cdecl`. So from my last comment just fixture for parameter data types. – Victoria Aug 10 '18 at 19:52
  • I just add a link to a Visual C++ 2010 project with all fiiles need. https://drive.google.com/open?id=1JxW4Wra_kT8gfBda1t0WWqagzazzQVne – Hermenegildo Gonzalez Aug 10 '18 at 20:43
  • It's solved! I just change the declaration from: function GrfLoad(const fname: PChar; Modo: Boolean): Pointer; cdecl; external 'GLib.dll'; to: function GrfLoad(const fname: PChar; Modo: Byte): Pointer; cdecl; external 'GLib.dll'; and the call from: PVar:= GrfLoad(PChar('test.grf'),false); to: PVar:= GrfLoad(PChar('test.grf'),1); and that's all. thanks @Mbo you were right, i don't need the .lib – Hermenegildo Gonzalez Aug 11 '18 at 17:56
  • @Remy: saw your added `<-- language: xyz -->` HTML comments. Does that really work this way? I didn't know. – Rudy Velthuis Aug 13 '18 at 09:55
  • @RudyVelthuis [yes, it does](https://stackoverflow.com/editing-help#syntax-highlighting) (doesn't work very well in the mobile app, but it definitely works on the website) – Remy Lebeau Aug 13 '18 at 15:32

2 Answers2

6

The proper declaration of the functions would be using a PAnsiChar (char is always a single byte type in C and C++):

type
  GFile = Pointer; // alternatively: GFile = THandle;

function GrfLoad(const FName: PAnsiChar; Mode: Boolean): GFile; cdecl; external 'GLib.dll' name 'GrfLoad';
procedure grfFree(GVar: GFile); cdecl; external 'GLib.dll' name 'GrfFree';

But it is well possible that the exported names are not GrfLoad and GrfFree, but different names, e.g. _GrfLoad and _GrfFree. You can find out which names are actually exported using a tool like MS's Dependency Walker or using Delphi's own TDump.exe (look for Exports section), i.e. using

tdump glib.dll

on the command line in the directory where glib.dll resides.

If the names differ, then you'll have to change the name parts of the external declaration, for instance external 'Glib.dll' name '_GrfLoad';, etc.

Some more info in my article: Pitfalls of converting.

Of course it is also possible that the DLL can't find another DLL it is dependent on. Dependency Walker will also tell you about missing imports.

Update

Note that the DLL in the zip file to which you linked is called GrfLib.dll, not GLib.dll. And the names are indeed exported as GrfLoad etc.

It is very well possible that you have a glib.dll on your system too, but that won't contain the functions you are looking for.


Also note that most people don't like to download a zip from an unknown source. They can't know what is really in it.

Community
  • 1
  • 1
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • Thanks for your help. I will try today with all the info and clues that you give me. Also i will take in consideration your comment about upload zip files the next time. – Hermenegildo Gonzalez Aug 10 '18 at 20:35
  • You might fix `unsigned char Mode` as well... ;-) – Victoria Aug 10 '18 at 20:45
  • @Victoria: in this case, I think Boolean is a useful translation. Perhaps ByteBool would be appropriate too. – Rudy Velthuis Aug 10 '18 at 21:51
  • @Rudy, well, I haven't gone deeper into the GLib source, but so long the export parameter is defined so, I'd prefer to keep it as such (even the name of that parameter doesn't clearly point out to be of boolean type). I think you know more, just for me without knowing the internals of that library I'd treat it as `unsigned char` which is `Byte` for Delphi and FPC. – Victoria Aug 10 '18 at 23:11
  • It's solved! I just change the declaration from: function GrfLoad(const fname: PChar; Modo: Boolean): Pointer; cdecl; external 'GLib.dll'; to: function GrfLoad(const fname: PChar; Modo: Byte): Pointer; cdecl; external 'GLib.dll'; and the call from: PVar:= GrfLoad(PChar('test.grf'),false); to: PVar:= GrfLoad(PChar('test.grf'),1); and that's all. thanks Rudy Velthuis and @Victoria for the help. – Hermenegildo Gonzalez Aug 11 '18 at 17:57
  • 1
    So you actually changed false (ordinal 0) to true (ordinal 1) and it worked? There was no need to change the declaration, see Sertac's comment on your answer. – Rudy Velthuis Aug 12 '18 at 00:07
  • @Victoria: apparently Boolean is wrong (and I was wrong). The values for Mode can be 1..3, so it should be declared as a special type, e.g. `type TGrfMode = 1..3;` and `... Mode: TGrfMode);` or simply as a Byte. The former is more type-safe (error when passing anything outside the range -- with range checks on). the latter is closer to the C++ original. – Rudy Velthuis Aug 13 '18 at 14:59
  • @Rudy, hard to say, I didn't know (and still don't know) the meaning of that parameter. The size is same as `Boolean`, just a literal translation would be as `Byte` (or maybe as you suggested some enumeration, or typed range). If you know more about GLib, you might consider updating that ;-) – Victoria Aug 13 '18 at 17:02
  • @Victoria: see Hermenegildo's comment on his answer: "Correct, the valid values are from 1 to 3, so when y pass "false" (0) the program crash". – Rudy Velthuis Aug 13 '18 at 18:48
  • @Rudy, well, it was a coincidence. I was pointing just the right type to be used (according to the presented prototype). I still have no idea what that parameter does ;) Yet I'm trusting you in your nicely written atricles. – Victoria Aug 13 '18 at 21:51
0

It's solved! I just change the declaration from:

function GrfLoad(const fname: PChar; Modo: Boolean): Pointer; cdecl; external 'GLib.dll';

to:

function GrfLoad(const fname: PChar; Modo: Byte): Pointer; cdecl; external 'GLib.dll';

and the call from:

PVar:= GrfLoad(PChar('test.grf'),false);

to:

PVar:= GrfLoad(PChar('test.grf'),1);

And that's all. thanks to Mbo, Victoria and Rudy Velthuis for the help.

  • 1
    A boolean is byte sized. So what you have changed effectively is to pass "true" instead of "false" (or 1 instead of 0 if you wish). And it doesn't crash any more. Doesn't make any sense... – Sertac Akyuz Aug 11 '18 at 22:40
  • @Sertac: indeed. The change in declaration doesn't change anything. Both Boolean and Byte are the same size, and the DLL doesn't see the difference. So a change from false to true seems to have made it work (for now). – Rudy Velthuis Aug 12 '18 at 00:10
  • Correct, the valid values are from 1 to 3, so when y pass "false" (0) the program crash. If i put "true" (1) the function will work but need to change to byte to allow pass values 2 and 3. – Hermenegildo Gonzalez Aug 12 '18 at 01:55
  • Ah, that the valid values are 1..3 was not known yet. But a program that crashes when a wrong value is passed is not really well thought out. Anyway, then it should be a Byte indeed: `... Mode: Byte = 1);`. – Rudy Velthuis Aug 13 '18 at 09:52