分享

How to translate “array of” param to C++?

 quasiceo 2012-12-25

I'm working on c++ language bindings for our game engine (made with Delphi XE). How would I translate the array of OleVariant and array of const params to work properly in C++ side?

function  DLM_CallRoutine(const aFullname: PWideChar;
const aParamList: array of OleVariant): OleVariant; stdcall;

function  DLM_CreateObject(const aClassName: PWideChar;
const aParamList: array of const): Integer; stdcall;

Thanks.

share|improve this question
1  
Obligatory reminder: C and C++ are totally different things. – Charles Salvia Jan 8 '11 at 17:17
@Charles Salvia, I mean to say c++. I updated my post. Thanks. – PyroBASIC Jan 8 '11 at 17:26
feedback

3 Answers

Delphi has two completely separate array semantics that both use the same array of code syntax for different purposes.

When array of is used to declare a data type or variable, a Dynamic Array is being used, eg:

type
  TIntegerArray: array of Integer;

var
  Array1: TIntegerArray;
  Array2: array of Integer;

In C++, these correspond to the DynamicArray<T> template class, eg:

typedef DynamicArray<int> TIntegerArray;

TIntegerArray Array1;
DynamicArray<int> Array2;

On the other hand, when array of is used in a function parameter directly (ie, without using a typedef), then an Open Array is being used instead, ie:

procedure DoSomething(Arr: array of Integer);
procedure DoSomethingElse(Arr: array of const);

Values passed to an Open Array parameter are passed by the compiler using two separate parameters - a pointer to the actual array, and the index of the last element in the array. Delphi hides this fact so the coder only sees one parameter, and provides a simple syntax for specifying the parameter values:

DoSomething([12345, 67890]);
DoSomethingElse(['Hello', 12345, True]);

In C++, however, the two parameters used for the array are explicitally declared, and values are typically specified using the OPENARRAY() and ARRAYOFCONST() macros, eg:

// despite their names, the Size parameters are actually indexes.
// This misnaming has already been slated to be fixed in a future
// C++Builder release...
void __fastcall DoSomething(int const *Arr, const int Arr_Size);
void __fastcall DoSomethingElse(TVarRec const *Arr, const int Arr_Size);

DoSomething(OPENARRAY(int, (( 12345, 67890 )) );
DoSomethingElse(ARRAYOFCONST(( "Hello", 12345, true )) );
share|improve this answer
Lebeau, thank you. I understand now. Hmmm yea I forgot about ARRAYOFCONST. I think I had to use it once. Let me ask is this also in other C++ compilers such as VC too? Or would I have to declare it in the header of my own binding? I'm trying to make the bindings work with the most commonly used C++ compilers. – PyroBASIC Jan 9 '11 at 13:30
Then your engine should not be using Pascal-specific features in its public interfaces, like open arrays. Use plain vanilla array pointers instead. If you need to support multiple data types in your array, then use the standard Win32 VARIANT data type, not OleVariant or TVarRec (which is used by the compiler internally for array of const values), as they are specific to Delphi and C++Builder and will not work in other C++ compilers. – Remy Lebeau Jan 10 '11 at 23:24
feedback

When creating code with interfaces to other languages it is wise to avoid Delphi specific types and conventions. Where you know your code will be interfacing with Delphi code, you may choose to provide Delphi-friendly interfaces, and "wrappers" to map Delphi-friendly types and mechanisms onto more portable ones for those other languages.

So....

Array of OLEVariant

Since you are passing an array of variants, clearly your C++ code is variant aware/capable, in which case I would pass these values in a variant array themselves (passed as a Variant itself), preserving the dynamic nature of the array, but eliminating any concerns over Delphi RTL specific "magic" types (dynamic arrays).

So you may have a Fn(array of OLEVariant) for your Delphi code, which internally re-packages the array into a Variant Array of Variant before passing the call on to the actual API code (psuedo-code):

  Fn(array of OLEVariant)
  begin
    arr := VarArrayCreate(...);
    try
      // ... init arr from array of OLEVariant parameter

      // call actual API fn:
      APIFn(arr);

    finally
      // Dispose of variant array
    end;
  end;

Array of Const

The values end up being passed in an array of TVarRec (not directly connected to variants, tho with a similar intention). Again this is a "magic" Delphi type - you will need to research the TVarRec type and map it to some equivalent C++ type.

In this case I would determine exactly what you need to pass in the params list and adopt a mechanism that is more portable between the two languages. Perhaps a simple string containing a name/value pair delimited string of parameter names and values?

In this case, providing a Delphi friendly wrapper around the API call would involve a slightly different approach from that which you are currently using (array of const), given that array of const does not provide for named entries in the array. Or you might choose simply to provide a delimited set of type/values, encoded in a string, in which case you could continue to use array of const for the Delphi side, with a wrapper function that processes the TVarRec array to format a string approriately for the API function.

share|improve this answer
wonderful, thanks. Our engine is coded in Delphi so the C side will be calling into a Delphi routine which is expected an OLEVariant param. Hmm.. I didn't consider encoding the types in a string, nice. – PyroBASIC Jan 8 '11 at 22:54
The engine allows compiling script to machine code and you can then call routines/methods on the script side. I can specify the calling convention on the script side so I was thinking of just calling the routine directly. It works in Delphi but I've not tested in C. Wanted to get an idea if it will work before installing VC. I can call DLM_GetAddress to return a pointer to the routine. This is assigned to a procedural variable with the same signature as the script routine. I can then directly make the call. Off hand I don't see any problems doing the same in C?. Any thoughts about this? – PyroBASIC Jan 8 '11 at 23:04
Like Deltics said, do not use Delphi-specific data types, like OleVariant. They will not work in C, even though you can call the routines. OleVariant is a compiler-managed data type, so you would have to replicate all of Delphi's compiler+RTL backend logic in your C code manually in order to use it. It is not worth it. When dealing with cross-language code, just use plain vanilla POD data types only. – Remy Lebeau Jan 10 '11 at 23:28
feedback

If I remember correctly, dynamic arrays in Delphi store the size of the array in the first few bytes. You would have to declare your interface function as taking a pointer, plus a size:

function  DLM_CallRoutine(const aFullname: PWideChar; 
          aParamList: POleVariant; numParams :integer): OleVariant; stdcall;

Callers would have to pass the address of the first actual element and the number of elements, @A[0] and Length(A) in Delphi.

Beware: There are memmory management/garbage collection issues wherever OleVariant and other OLE types are involved. You have to make sure that reference counts are incremented and decremented appropiately.

Delphi Dynarrays

This is correctly documented somewhere in the Delphi help, and can probably be learned from looking at the source code of System.pas and SysUtils.pas. What follows is from memory (sorry in advance for being of such little help).

A Delphi dynarray is a record:

type
  TDynArray = record
    refcount :integer;
    size:    :integer;
    content  :array[size*elementSize] of byte;
  end;

@A[0] is equivalent is the same as @content. To get to the address that includes the refcount you'd have to do some casting.

Having C call Delphi

You could have the clients written in C manipulate the structures for Delphi dynarrays, But why impose Delphi implementation semantics on them? If C is calling into your code, then by all means use the C way of doing it, which is:

  1. A pointer to an array of structures.
  2. A number-of-records parameter.
  3. The obligation of your API to copy what was passed as parameters so the caller can free what it likes right after the call. The C code owns what it passes in parameters.

The API exposed to C can be easily implemented with calls to the existing API.

I hope that helps.

share|improve this answer
ahh.. yea numParams ok. Would I need to manually inc/dec the ref count on the c++ side? – PyroBASIC Jan 8 '11 at 17:37
1  
You need to make sure that the refcount doesn't go to zero on the Delphi side while the C side still holds a reference. As @Deltics suggests, if you're using OLE, then you'll probably be better off resourcing to OLE arrays. If you're just using Variants for their convenience, and you don't need OLE, then stay away from OLE. – Apalala Jan 8 '11 at 20:37

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多