Trouble adding an image

Hi,

I have create a procedure whose allow me to add an image to my excel file.
I used the APIMate to know what to do and it give me the following code :

fs := TFileStream.Create('imagename.jpg', fmOpenRead or fmShareDenyNone);
try
   ImgProps := TImageProperties_Create();
   ImgProps.Anchor := TClientAnchor.Create(TFlxAnchorType.MoveAndDontResize, 3, 7, 4, 26, 6, 178, 5, 576);
   ImgProps.ShapeName := 'Image 2';
   xls.AddImage(fs, ImgProps);
finally
   fs.Free;
end;


So I just modify it a little bit for my project :

procedure InsereImage(xls : TXlsFile; sCheminImage: string; HauteurImage, TopImage: Integer);
var
  fs: TFileStream;
  ImgProps: IImageProperties;
begin
  fs := TFileStream.Create(sCheminImage, fmOpenRead or fmShareDenyNone);
  try
    ImgProps := TImageProperties_Create();
    ImgProps.Anchor := TClientAnchor.Create(TFlxAnchorType.MoveAndDontResize, 2, 102, 4, 883, 6, 38, 6, 38);
    ImgProps.ShapeName := 'Logo';
    xls.AddImage(fs, ImgProps);
  finally
    DetruitObjet(fs);
  end;

sCheminImage is the way to acces to the image, I give the possibility to the user to choose which image he want to put into his file,
and I have kept the value for the anchor just for my test.
HauteurImage and TopImage isn't used for the moment.

The problem now is that everytime I call my procedure, I get the following error and I don't understand why It don't work because on my test project the same code work :


When I press "Arr?ter" (The French for Stop) Delphi stop on the following line :



I don't understand why It don't work, can you please Help me ?
Thanks you in advance.

Hi,
If this is working in the Tast project but not in the real project, the most likely reason is that somewhere above that code you are corrupting the memory somehow. (maybe writing to an array out of bounds, maybe using a freed object).

While it is hard to detect where the exact error could be, I would start by:
1)Turning Range check on in the full project. You can do it with {$R+} in each file, or with Project Options->Delphi Compiler->Compiling->Range Checkcking = true.
You might also turn on Overflow checking and I/O checking just in case.

2)using FASTMM in full debug mode. For that you need to download the full FASTMM from here:
https://github.com/pleriche/FastMM4
(the FASTMM version that is bundled with Rad Studio doesn't have fulldebugmode).

Then in the file fastmm4Options.inc ( https://github.com/pleriche/FastMM4/blob/master/FastMM4Options.inc ), uncomment the line:
{.$define FullDebugMode}
changing it to
{$define FullDebugMode}

Once you have this you need to use FASTMM4 as the first unit in your project (right click your app in the project manager, select "view source", then add FASTMM4 as the first unit).

You will also have to copy the FastMM_FullDebugMode.dll to the folder where your exe is.

Once you have this configured, try running your app again, and let me know if there is any error reported before the xls.AddImage line.

Remember to always disable the full debug mode for release, it can make your app really slow.But for debugging this kind of issues, it can be very helpful.

Hi,


So i tried as you suggest me with the fullDebugMode of FastMM ( I asked some help from a collegue who is agile with FastMM).

There are an error when I launch the project, here what I get :




--------------------------------2017/1/24 10:51:37--------------------------------
FastMM a d?tect? une erreur pendant un appel ? FreeMem. La fin du bloc a ?t? corrompue. 


La taille du bloc est: 9


This block was allocated by thread 0x1DF0, and the stack trace (return addresses) at the time was:
407815 
40F493 
60A9336 [GetRawStackTrace]
40F5D6 
5402B5 [Unknown function at TMethodImplementationIntercept]
826A55 [Unknown function at TMethodImplementationIntercept]
420508 [Unknown function at __dbk_fcall_wrapper]
552F4D [Unknown function at TMethodImplementationIntercept]
40C475 
5413B8 [Unknown function at TMethodImplementationIntercept]
5416AA [Unknown function at TMethodImplementationIntercept]


Le bloc ?tait actuellement utilis? pour un objet de la classe: Inconnu


Le nombre d'allocations est: 712069


The block was previously freed by thread 0x1DF0, and the stack trace (return addresses) at the time was:
4077C2 
40F730 
40E342 
826B13 [Unknown function at TMethodImplementationIntercept]
420508 [Unknown function at __dbk_fcall_wrapper]
552F4D [Unknown function at TMethodImplementationIntercept]
40C475 
5413B8 [Unknown function at TMethodImplementationIntercept]
5416AA [Unknown function at TMethodImplementationIntercept]
11A04EF [Unknown function at TMethodImplementationIntercept]
41116B 


The current thread ID is 0x1DF0, and the stack trace (return addresses) leading to this error is:
4077C2 
40F730 
40E342 
826B13 [Unknown function at TMethodImplementationIntercept]
420508 [Unknown function at __dbk_fcall_wrapper]
552F4D [Unknown function at TMethodImplementationIntercept]
40C475 
5413B8 [Unknown function at TMethodImplementationIntercept]
5416AA [Unknown function at TMethodImplementationIntercept]
11A0529 [Unknown function at TMethodImplementationIntercept]
41116B 


Contenu des 256 octets commen?ant ? l'adresse 7F4243C8:
00 00 00 00 01 00 00 00 01 00 23 2D 73 80 80 80 00 00 00 00 20 FA 41 7F 00 00 00 00 00 00 00 00
BC 02 42 00 00 00 00 00 79 DD 0A 00 15 78 40 00 93 F4 40 00 D6 F5 40 00 0D 56 82 00 49 90 B6 76
E7 C5 82 00 E4 11 43 00 69 9A B6 76 C5 6A 82 00 46 EF 41 00 4D 2F 55 00 F0 1D 00 00 F0 1D 00 00
C2 77 40 00 30 F7 40 00 59 F3 40 00 87 A1 4E 00 33 A5 40 00 18 09 4E 00 AC 0B 4E 00 D6 0B 4E 00
88 19 54 00 0A 0A 4E 00 36 05 4E 00 0A 00 00 00 00 00 00 00 C6 96 4C 73 01 00 00 00 02 00 00 00
01 00 39 69 B3 8C 80 80 80 80 00 00 20 FA 41 7F 00 00 00 00 00 00 00 00 BC 02 42 00 00 00 00 00
75 DD 0A 00 15 78 40 00 93 F4 40 00 D6 F5 40 00 9D 2D 55 00 46 47 82 00 2C 1E 55 00 DB 1E 55 00
28 48 82 00 93 1D 55 00 CF 1B 55 00 AE 04 1A 01 F0 1D 00 00 F0 1D 00 00 1D 06 42 00 E3 77 40 00
.  .  .  .  .  .  .  .  .  .  #  -  s  ?  ?  ?  .  .  .  .     ?  A    .  .  .  .  .  .  .  .
?  .  B  .  .  .  .  .  y  ?  .  .  .  x  @  .  ?  ?  @  .  ?  ?  @  .  .  V  ?  .  I  ?  ?  v
?  ?  ?  .  ?  .  C  .  i  ?  ?  v  ?  j  ?  .  F  ?  A  .  M  /  U  .  ?  .  .  .  ?  .  .  .
?  w  @  .  0  ?  @  .  Y  ?  @  .  ?  ?  N  .  3  ?  @  .  .  .  N  .  ?  .  N  .  ?  .  N  .
?  .  T  .  .  .  N  .  6  .  N  .  .  .  .  .  .  .  .  .  ?  ?  L  s  .  .  .  .  .  .  .  .
.  .  9  i  ?  ?  ?  ?  ?  ?  .  .     ?  A    .  .  .  .  .  .  .  .  ?  .  B  .  .  .  .  .
u  ?  .  .  .  x  @  .  ?  ?  @  .  ?  ?  @  .  ?  -  U  .  F  G  ?  .  ,  .  U  .  ?  .  U  .
(  H  ?  .  ?  .  U  .  ?  .  U  .  ?  .  .  .  ?  .  .  .  ?  .  .  .  .  .  B  .  ?  w  @  .



Hi,
Can you turn the creation of .map file to "Detailed" and rerun it? The option is at Project Options->Linking->Map file. Set it to Detailed.

It should give a better stack trace this way.

But indeed, this seems like some memory corruption going on. If you run the debugger, does it send you to any suspicious line of code when the error happens?

The debugger don't send me any suspicious line, but we had studies the problem with my problem and it look like the problem come from an another part of the software that I'm not in charge...


One detail that I forget to give you, I use  flexcell into a DLL. Like you write on the documentation, I used FlexCelDllInit and FlexCelDllShutdown when I call the procedure from the DLL.

Nethertheless, with my collegue, we have try to do an another DLL or use a Package but everytime the same code line (xls.addimage) make the code stop.

Hi,
Thanks for the sample project, we were able to reproduce it here. The issue is that you are passing a TXlsFile object from the main program to the dll, and you can't pass complex objects through the dll. Imagine if the dll was compiled with a different version of Delphi: The TXlsFile object you create in your app might have a different memory layout from the TXlsFile in the dll.

When passing parameters to a dll, you can't even pass strings (but you can pass widestrings). Only simple types. the rules for dll are:
1)Never pass more than simple types from one side to the other.
2)Never pass exceptions from one side to the other.

The correct (non crashing :) way to do your test would be with this
In the dll, define a CreateXlsFile and FreeXlsFile methods which return pointers:


library Project2;

{ Remarque importante sur la gestion m?moire de la DLL : ShareMem doit ?tre la
  premi?re unit? de la clause USES de votre biblioth?que ET la clause USES
  (s?lectionner Projet-Voir le source) de votre projet si votre DLL exporte toute proc?dure ou
  fonction qui passe des cha?nes par le biais de param?tres ou de r?sultats de fonctions. Cela
  s'applique ? toutes les cha?nes pass?es vers et depuis votre DLL -- m?me celles qui
  sont imbriqu?es dans des enregistrements et des classes. ShareMem est l'unit? d'interface au
  gestionnaire de m?moire partag?e BORLNDMM.DLL, qui doit ?tre d?ploy?
  avec votre DLL. Pour ?viter l'emploi de BORLNDMM.DLL, passez des informations cha?ne
  par le biais de param?tres PChar ou ShortString. }

uses
  FastMM4,
  Vcl.Dialogs,
  System.SysUtils,
  System.Classes,
  VCL.FlexCel.Core,
  FlexCel.XlsAdapter;

{$R *.res}

type
  PXlsFile = pointer;

function CreateXlsFile: PXlsFile; stdcall;
begin
  try
    Result := TXlsFile.Create(1, true);
  except
    Result := nil;
  end;
end;

procedure FreeXlsFile(const xls: PXlsFile); stdcall;
begin
  TXlsFile(xls).Free;
end;

function yTest(pxls: PXlsFile): boolean; stdcall;
var
  fs: TFileStream;
  ImgProps: IImageProperties;
  xls: TXlsFile;
begin
   Result := True;
   try
      xls := TXlsFile(pxls);
      fs := TFileStream.Create('client-icon.gif', fmOpenRead or fmShareDenyNone);
      try
         ImgProps := TImageProperties_Create();
         ImgProps.Anchor := TClientAnchor.Create(TFlxAnchorType.MoveAndDontResize, 2, 102, 4, 883, 6, 38, 6, 38);
         ImgProps.ShapeName := 'Logo';
         xls.AddImage(fs, ImgProps);
      finally
         fs.Free;
      end;
   except
      on E:Exception do
      begin
         showmessage(E.Message);
         Result := False;
      end;
   end;
end;

function ySave(pxls : PXlsFile): boolean; stdcall;
var
  xls: TXlsFile;
begin
   try
      xls := TXlsFile(pxls);
      xls.Save('..\test.xlsx');

      Result := True;
   except
      on E:Exception do
      begin
         showmessage(E.Message);
         Result := False;
      end;
   end;
end;

exports
   CreateXlsFile,
   FreeXlsFile,
   yTest,
   ySave;

begin

end.



Then, in the program, call it like this:


procedure TForm1.Button1Click(Sender: TObject);
var
   xls : Pointer;
   Crt : function : Pointer; stdcall;
   Fct : function (xls: Pointer): boolean; stdcall;
   DLL : THandle;

begin

   FlexCelDllInit;
   xls := nil;

   DLL := LoadLibrary('dll\Project2.dll');
   if DLL <> 0 then
   try
      try
         @Crt := GetProcAddress(Dll, 'CreateXlsFile');
         if Assigned(Crt) then
            xls := Crt();
         if (xls = nil) then raise Exception.Create('Can''t create Excel file');

      except
         on E:Exception do
         begin
            showmessage(E.Message);
         end;
      end;

      try
         @Fct := GetProcAddress(Dll, 'yTest');
         if Assigned(Fct) then
            Fct(xls);
      except
         on E:Exception do
         begin
            showmessage(E.Message);
         end;
      end;
...
Remember to free the XlsFile object by calling the FreeXlsFile method in the dll, not with xls.Free. You can't do anything directly with FlexCel in your main app: everything should be done in the dll. In fact, you shouldn't be using FlexCel units in the main app at all (And if you do, make sure to never mix it with dll calls.



I am not sure the reasons you have to go with a dll but if I might, I would suggest a couple of things:
1)You might try to do the full work in the dll instead of trying to pass parameters from one side to the other. So you would have a:
DoEverything() in the dll which will create the TXlsFile, do the stuff, then free it. From the app side, you just call DoEverything() in the dll and don't care about FlexCel at all. All FlexCel calls are encapsulated in the dll, and your app doesn't use any FlexCel units.
Remember you can pass widestrings from one side to another, so having a procedure like:
DoEverything(filename: widestring) is ok.

2)If you want a more granular control like in your example (having a method to open the file, other to set cell values, other to save), then you need to use pointers as shown above, but this can be a lot of work soon. I know, I've done it :) http://www.tmssoftware.com/site/flexceldll.asp

And since I have already done it, my suggestion would be to use that instead of creating the full dll. You can mail me and I can send you a free version of FlexCel dll if this approach would work for you.

So to resume I would either do full procedures that encapsulate the full business logic (methods like CreateBusinessReport, etc), or go the full other way with FlexCel dll: Have every FlexCel method encapsulated using pointers. What is best for you depends in what you are doing and the reasons you have to use a dll.