Hello. I no longer know how to get rid of this error ...

Error periodically comes out in the second step

Step2 Accesse violation at address 0AC95985 in module ... Read of address FFFFFFFC, high (a) = 31

Library hash_sha256; { Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } uses System.SysUtils, System.Classes, System.Hash, Vcl.Dialogs; {$R *.res} function MyHash(AKey, AData: PAnsiChar): Pchar; stdcall; var a: TArray<byte>; i: integer; s: ShortString; begin try s:=''; Result:=''; try a:=THashSHA2.GetHMACAsBytes(String(AData), String(AKey)); except on E : Exception do ShowMessage('Step1'+E.Message); end; try for i:=0 to high(a) do s:=s+PChar(inttohex(a[i],2)); except on E : Exception do ShowMessage('Step2 '+E.Message+', high(a)='+IntToStr(high(a))); end; try Result:=Pchar(s+''); except on E : Exception do ShowMessage('Step3'+E.Message); end; except on E : Exception do begin Result:=Pchar(AnsiString(E.Message)); end; end; end; exports MyHash name 'hash_sha256'; begin end. 

The nuance is that the library is written on 10 delphi (delphi XE 10), and the program that uses it is written on the old Delphi7 ... Apparently, therefore, the AnsiString variant with BORLNDMM.DLL and ShareMem led constantly to Accesse violation but only when connected to the library.

  • one more again :) strings are numbered starting from 1. This is inherited from pascal. And the loop needs to be done for i:=1 to Length(a) . For 100% compatibility, you must specify PAnsiChar everywhere, not PChar. - kami
  • @kami But not a string. - Igor
  • @kami Originally, I also sinned for it ... But when I replaced ci: = 0 with i: = 1, the result of the function was shorter by one character ... (by the first character) - VeryBadUser
  • String (AData), String (AKey) - I would try to write AData and AKey - each in a variable of a string type. Well, also allocate memory for "a" with setlength. This is dancing with a tambourine, of course, but it will increase the controllability of memory. - Albert Fomin
  • If you are given an exhaustive answer, mark it as correct (a daw opposite the selected answer). - Nicolas Chabanovsky

2 answers 2

Item 0.

 function MyHash(AKey, AData: PAnsiChar): Pchar; stdcall; 

Why do you have two different PChar 's in the declaration of the function? If one of the modules is compiled in Delphi 7, put PAnsiChar everywhere.

Paragraph 1.

Why is a ShortString suddenly used?

Point 2.

Try to answer the following question - who (which module) allocates and frees the memory, the pointer to which you are returning, and at what points this (distribution and release) occurs.

You return a pointer to the given string, which is a local (compiler-generated) variable inside your function. Memory for this line is released at the moment of exiting the function. If you are lucky and this piece of memory remains intact, the calling code may have time to copy the data from there. And maybe not in time.

You should allocate a buffer for the result on the side of the calling code and give a pointer to this buffer and the size of the buffer by the third and fourth parameters. Your function should return the required buffer size. If the submitted buffer is less than the required one, do not write anything to it.

Point 3.

Why these dances with castes s:=s+PChar(inttohex(a[i],2)); ?

  • Regarding the 0 point - rightly, corrected), Point 1 - If you use an ordinary String, then 1 character is transferred to the main program, from where the library is called instead of a string. Regarding point 2 - corrected. Point 3 - crawled warnings Implicit string cast from 'ShortString' to 'string' - VeryBadUser
  • @ user6802100 Add i to the error message to find out at which iteration it occurs. - Igor

It is very important not to mix PChar and PAnsiChar in the code. You cannot use PChar in the API dll at all, because it is an alias and depending on the Delphi version this type is equal to either PAnsiChar or PWideChar .

Further, it is necessary to adhere to the rule: "Who allocates memory, it releases it," especially when writing dll. You must be clear about when and by whom memory is released and when and by whom it is released. Delphi hides from us the entire memory management routine when working with strings, but when transferring values ​​from / to dll it only hurts, especially to beginners.

If your function allocates memory for the string to return the result, then you should have an additional API. function to release this memory. And you must take care that Delphi does not automatically destroy this line when exiting the function.

There is another option - the calling function must allocate a buffer of sufficient size for the result and pass a pointer to it and the size of the buffer to your function.

Since the length of the resulting string with SHA256 is known in advance (64 characters), it is possible in the documentation to declare an agreement that the buffer must be at least 64 bytes:

 const c_SHA256_StrLen = 64; procedure MyHash(const AKey, AData: PAnsiChar; const AHash: PAnsiChar); stdcall; var I: Integer; VByteStr: AnsiString; VKey, VData, VHash: TBytes; begin if System.AnsiStrings.StrLen(AHash) >= c_SHA256_StrLen then begin VKey := BytesOf(AKey, System.AnsiStrings.StrLen(AKey)); VData := BytesOf(AData, System.AnsiStrings.StrLen(AData)); VHash := THashSHA2.GetHMACAsBytes(VData, VKey); for I := Low(VHash) to High(VHash) do begin VByteStr := VByteStr + AnsiString(LowerCase(IntToHex(VHash[I], 2))); end; System.AnsiStrings.StrLCopy(AHash, PAnsiChar(VByteStr), c_SHA256_StrLen) end; end; 

For the convenience of calling this function from the dll, in the application you can write a wrapper:

 function CalcSHA256(const AKey, AData: AnsiString): AnsiString; var VHash: PAnsiChar; VHashLen: Integer; begin VHashLen := c_SHA256_StrLen; VHash := AllocMem(VHashLen + 1); // +1 для завершающего нуля try FillChar(VHash^, VHashLen, '0'); MyHash(PAnsiChar(AKey), PAnsiChar(AData), VHash); Result := AnsiString(VHash); finally FreeMem(VHash); end; end; 

Test:

 CalcSHA256('key', 'The quick brown fox jumps over the lazy dog') // f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 

Please note that the application must be allocated memory with a margin of 1 byte to store the terminating zero. And, accordingly, this null character should be written there, which is achieved using the AllocMem function, which allocates and clogs the allocated memory with zeros.