Your statement about open arrays is not correct. There are three forms of open array parameter:
procedure ByValue(arr: array of Integer);
procedure ConstParam(const arr: array of Integer);
procedure VarParam(var arr: array of Integer);
The final two forms, var and const, pass a reference to the array. However, the first, passing by value, passes a copy. If you want to avoid making a copy, then you should use either a var or const parameter. Choose between these depending on whether or not you wish the caller's array to be modified.
As for classes and strings, these types are reference types. Since they are already references, when you pass one to a procedure, you are passing the reference.
Let's look at the various possibilities for classes:
procedure ByValue(obj: TObject);
procedure ConstParam(const obj: TObject);
procedure VarParam(var obj: TObject);
procedure OutParam(out obj: TObject);
For ByValue and ConstParam, the reference is passed directly. The difference between the two is that in the implementation of ConstParam, the reference cannot be modified. It can in ByValue. The caller cannot see that modification. Of course, you can always call methods on obj that modify the state of the object. In practice there's little point in using const for a reference type.
For VarParam and OutParam, the address of the reference is passed. Thus the procedure receives a pointer to a pointer to the object. All of the pointer handling is hidden from you. But this is what allows you to modify the reference and have the caller see that modification.
Strings are also reference types, and so are handled similarly.