0

I am creating a DLL in C++, it would be used in a Delphi 7 project.

This question is related to this one, where I present two functions Validate and GetToken only that now they will be done in C++ and the array of strings GetToken produces would be sent back to Delphi.

The problems is I don't know how to create the function in the dll that will return the array of string in c++, and I don't know how it would be stored for further use in Delphi.

The declaration of the function is as follows:

function GetToken(Chain:string):Arrayofstring;

4
  • 1
    How much do you know about interop? Do you know how to pass integers and strings? Arrays are another level of complexity. Who allocates everything? Caller or callee? How is the memory management done? Commented Mar 6, 2014 at 16:36
  • I have the Validate method in the dll (returns a boolean) working, also tried with other types like strings and integers in other examples and they work too. Commented Mar 6, 2014 at 16:52
  • 1
    Do you have a header for the DLL? If so, please post the prototype for the function (i.e. the declaration) and the declaration of related types. That should help. Also take a look if the following might help: rvelthuis.de/articles/articles-convert.html#arrayparams Commented Mar 6, 2014 at 16:57
  • You need to work out the lifetime. Where is the memory going to be allocated. It's usually cleanest if the caller does that. Can the caller allocate the outer array, and the inner character arrays? Commented Mar 6, 2014 at 17:10

1 Answer 1

2

According to your code review, the Delphi code expects the function to have the following signature:

function GetToken(Chain: AnsiString): array of AnsiString;

You cannot write such a function in C++. C++ doesn't know what Delphi strings are, and it doesn't know what Delphi dynamic arrays are, either. Both types need to be allocated from Delphi's memory manager, which your C++ DLL won't have access to. Furthermore, C++ doesn't know how to use Delphi's register calling convention.

The DLL interface was designed poorly. DLLs should never use language-specific types unless it was the designer's intention to exclude all other languages. (And in this case, even later versions of the same language are excluded because the definition of AnsiString changed in Delphi 2009 to include more metadata that Delphi 7 won't handle properly.) The safest calling convention to choose is generally stdcall. It's what everything in the Windows API uses.

A better interface would use types that are common to all languages, and it would dictate the use of memory management that's accessible universally. There are several common ways to do that. For example:

  • The strings are returned as simple nul-terminated arrays of characters — PAnsiChar in Delphi; char* in C++. The DLL allocates buffers for the strings, and also allocates a buffer for the array of those strings. When the host application is finished using the array and the strings, it calls another function exported by the DLL wherein the DLL frees the memory it allocated. This is the model used by, for example, FormatMessage; when the host program is finished the with message string, it calls LocalFree.

    type
      PStringArray = ^TStringArray;
      TStringArray = array[0..Pred(MaxInt) div SizeOf(PAnsiChar)] of PAnsiChar;
    function GetToken(Char: PAnsiChar): PStringArray; stdcall;
    procedure FreeStringArray(StringArray: PStringArray); stdcall;
    
    char** __stdcall GetToken(char const* Chain);
    void __stdcall FreeStringArray(char** StringArray);
    
  • Use COM to return a safearray of BStr objects. It's similar to the previous technique, but the memory management is defined by COM instead of by your DLL, so there's less stuff that needs to be defined by either party of the interface.

  • Pass a callback function to the DLL, so instead of returning an array of strings, the DLL just calls the function once for each string it identifies. Then you don't have to define what any array looks like, and the lifetime of each string can be just the lifetime of the callback call — if the host application wants a copy, it can do so. The new function signature would look something like this:

    type
      TTokenCallback = procedure(Token: PAnsiChar); stdcall;
    procedure GetToken(Chain: PAnsiChar; ProcessToken: TTokenCallback); stdcall;
    
    typedef void (__stdcall* TokenCallback)(char const* Token);
    void __stdcall GetToken(char const* Chain, TokenCallback ProcessToken);
    

If you're not the one who designed the DLL interface, then you need to lean on the folks who did and get it changed to be more accessible to non-Delphi code. If you can't do that, then the final alternative is to write a DLL in Delphi that wraps your DLL to massage the parameters into something each side understands.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.