views:

462

answers:

2

I wrote two methods with a void type parameter:

procedure Method1(const MyVar; size: cardinal);
var
  Arr: array of byte;
begin
  SetLength(Arr, size);
  {now copy the data from MyVar to Arr, but how?}  
end;

procedure Method2(var MyVar; size: cardinal);
var
  Arr: array of byte;
begin
  SetLength(Arr, size);
  {return the data from the array, but how?}
end;

In the first one I would like to access the MyVar as an array of byte. In the second one, I would like to copy the data from the local array Arr to MyVar. Therefore I used the CopyMemory() function, but something is wrong with it.

If I use the following in the second method, it is fine as long as Method2 is called with an array as its parameter (Method2(Pointer(MyString)^, Length(MyString)) or Method2(Pointer(MyArray), Length(MyArray))).

CopyMemory(Pointer(MyVar), Pointer(Arr), size);

If I call Method2 with a, for instance, integer parameter (Method2(MyInteger, SizeOf(MyInteger))), it does not work properly. In this case, the CopyMemory() has to be called this way:

CopyMemory(@MyVar, Pointer(Arr), size);

How to return data from Method2 correctly not knowing whether it is a simple type (or record) or an array? The situation will be similar in Method1, but here I would have to use

CopyMemory(Pointer(Arr), Pointer(MyVar), size);

in case of arrays and

CopyMemory(Pointer(Arr), @MyVar, size);

in case of simple types.

What can I do about it when I don't know what the MyVar parameter is?

Thanks in advance.

+2  A: 

Your problem is that you pass either data OR pointer to a data to Method1/2. You should always pass data itself. May be you just forgot that dynamic array is a pointer itself? You should not pass A or Pointer(A) in your methods (A is dynamic array here). Pass A[0] or Pointer(A)^.

procedure Method1(const MyVar; size: cardinal);
var
  Arr: array of byte;
begin
  SetLength(Arr, size);
  CopyMemory(Pointer(Arr), @MyVar, Size);
end;

procedure Method2(var MyVar; size: cardinal);
var
  Arr: array of byte;
begin
  SetLength(Arr, size);
  Arr[0] := 1;
  Arr[1] := 2;
  Arr[2] := 3;
  Arr[3] := 4;
  CopyMemory(@MyVar, Pointer(Arr), Size);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  A: array of Integer;
begin
  I := $01020304;
  Method1(I, 4); // we pass data itself, not the pointer to it.
  Method2(I, 4);
  SetLength(A, 2);
  A[0] := 0;
  A[1] := $01020304;
  Method1(A[0], Length(A) * SizeOf(A[0])); // note, that we pass data, not pointer
  Method2(A[0], Length(A) * SizeOf(A[0])); // A or Pointer(A) is a pointer to array's data
end;

If A is dynamic array:

  1. A[0] is the same as Pointer(A)^ and represents array's data.
  2. @A[0] is the same as Pointer(A) or just A and represents the array itself, which is pointer to it's data (and some tech info on negative offsets).

If A is static array:

  1. A[0] is the same as A and represents the array itself, which is array's data.
  2. @A[0] is the same as @A and represents the pointer to array.
  3. Pointer(A) or Pointer(A)^ are meaningless.

Note, that Arr in Method1/2 is a dynamic array too, that is why we cast it to pointer (CopyMemory asks pointers, not the data). If we want to use Move routine (which asks data), we should write Pointer(A)^ instead.

Alexander
Thank you both for your answers. It seems that I called my methods in a wrong way and that was why I got the AV exceptions and thought the CopyMemory() function had to be called in two different ways. Thanks to your explanations I managed to find my mistake easily.
Mariusz
+5  A: 

There's no such thing a a void type in Delphi. What you're referring to is called an untyped parameter.

An untyped parameter is always the actual thing itself, not a pointer to the thing you're supposed to use. Therefore, the correct way to use CopyMemory with such a parameter is to apply the @ operator to it, like so:

CopyMemory(@MyVar, @Arr[0], size);

Notice I've also changed the way I pass the second parameter. It's better if you don't rely on the fact that a dynamic array is really a pointer to the first element. If you need a pointer to the first element, just say so explicitly, like I did here.

Your confusion comes from a test you made where the parameter was used as though it were a pointer, and the test appeared to work. I doubt the validity of that test, though. When you said Pointer(MyString)^, what you had was the first character of the string. When you then said Pointer(MyVar) inside the function, you were type-casting that character into a pointer, which was an invalid type-cast. If your program appeared to work, then it was only by accident; your code was wrong.

The best advice I can give is to not type-cast stuff unless you really have to. And when you do, please be sure that the thing you're type-casting really does have that type. In your case, you don't need to type-cast anything before you pass it as an untyped parameter. You can call Method1 like this:

Method1(MyString[1], Length(MyString) * Sizeof(Char));

(I multiply by SizeOf(Char) since I don't know whether you have Delphi 2009 or not.)

Also, do avoid untyped parameters. One of the great things a compiler can do to help ensure program correctness is to enforce type safety, but when you take away the types, the compiler can't help you anymore.

Rob Kennedy
+1 Firm _and_ informative!
Argalatyr