corba interfaces - is/as not working properly

classic Classic list List threaded Threaded
22 messages Options
12
Reply | Threaded
Open this post in threaded view
|

corba interfaces - is/as not working properly

David Emerson
Hi all,

I'm testing out CORBA interfaces before changing all my code to use them
(I would prefer not to have managed types for interfaces).

However I am running into a glitch. Testing "if (my_object is
i_some_interface)" is always returning true, even when the object does
not implement the interface. Worse, following the false positive,
(my_object as i_some_interface) seems to be returning a *different*
interface. Proceeding to use that interface runs functions from the
wrongly returned interface, rather than the requested function.


Code below followed by sample output.


program corba_bug;

{$mode objfpc}{$H+}

{$interfaces corba}

uses
   classes,
   sysutils;

type
   i_hello = interface
     procedure hello;
     end;

   i_goodbye = interface
     procedure goodbye;
     end;

   i_nonsense = interface
     procedure nonsense;
     end;

   t_fellow = class (TObject, i_hello, i_goodbye) // no nonsense
     procedure hello;
     procedure goodbye;
     end;

procedure t_fellow.hello;
   begin
     writeln ('hello');
   end;

procedure t_fellow.goodbye;
   begin
     writeln ('goodbye');
   end;

var
   fellow : t_fellow;
   obj : TObject;
   h : i_hello;
   g : i_goodbye;

begin
   fellow := t_fellow.Create;
   obj := fellow;

   writeln ('expect 4x hello:');
   fellow.hello;
   h := fellow;
   h.hello;
   (fellow as i_hello).hello;
   if (obj is i_hello) then begin
     h := obj as i_hello;
     h.hello;
     end;

   writeln;

   writeln ('expect 4x goodbye:');
   fellow.goodbye;
   g := fellow;
   g.goodbye;
   // BROKEN - the two below are giving 'hello' rather than 'goodbye'
   (fellow as i_goodbye).goodbye;
   if (obj is i_goodbye) then begin
     g := obj as i_goodbye;
     g.goodbye;
     end;

   writeln;

   if (fellow is i_nonsense) then begin
     // BROKEN - execution is entering here though it shouldn't
     writeln ('Problem: (fellow is i_nonsense) returned true');
     (fellow as i_nonsense).nonsense; // printing 'hello'
     end;

   fellow.free;
end.


Free Pascal Compiler version 3.0.0 [2015/12/05] for x86_64
Copyright (c) 1993-2015 by Florian Klaempfl and others
Target OS: Linux for x86-64



Sample output:

expect 4x hello:
hello
hello
hello
hello

expect 4x goodbye:
goodbye
goodbye
hello
hello

Problem: (fellow is i_nonsense) returned true
hello


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Graeme Geldenhuys-6
On 2016-09-28 08:38, David Emerson wrote:
> I'm testing out CORBA interfaces before changing all my code to use them
> (I would prefer not to have managed types for interfaces).

I've been using CORBA style interfaces for years, and it was always
worked well for me. I must add, I never use the is/as syntax though (for
COM or CORBA style interfaces). I always use Supports(..) as in:

 var
   h: i_hello;
 begin
   // fellow is the class instance variable.
   if Supports(fellow, i_hello, h) then
      h.hello;


The Supports(..) syntax just seems more logical to me than the is/as
syntax. The latter assumes the object "is" an instance of one of the
interfaces and simply typecasts it to a interface variable. The
Supports(..) syntax queries the object instance and asks if it supports
a specific interface.


Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

noreply
On Wed, September 28, 2016 2:24 am, Graeme Geldenhuys wrote:
> On 2016-09-28 08:38, David Emerson wrote:
>
>> I'm testing out CORBA interfaces before changing all my code to use
>> them (I would prefer not to have managed types for interfaces).
>>
>
> I've been using CORBA style interfaces for years,


Curious, where do corba interfaces come in handy ? When can you use them
that object oriented code won't offer the same features? I'm wondering if
maybe GoLang interfaces are so popular because of similar reasons to the
heavily underused and infamous interfaces available in delphi/fpc for
years.

i.e. what's the basic point of using an interface compared to objects and
inheritance and procedures? Where do they really shine?

In goLang (don't mean to make this off topic) interfaces are very similar
to VarArgs or Variants but not as evil. I mean to keep this on topic by
asking specifically what corba interfaces should be used for?  Also do
they work across DLL's or DSO's sort of like microsoft COM?
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Michael Van Canneyt


On Wed, 28 Sep 2016, Lars wrote:

> On Wed, September 28, 2016 2:24 am, Graeme Geldenhuys wrote:
>> On 2016-09-28 08:38, David Emerson wrote:
>>
>>> I'm testing out CORBA interfaces before changing all my code to use
>>> them (I would prefer not to have managed types for interfaces).
>>>
>>
>> I've been using CORBA style interfaces for years,
>
>
> Curious, where do corba interfaces come in handy ? When can you use them
> that object oriented code won't offer the same features? I'm wondering if
> maybe GoLang interfaces are so popular because of similar reasons to the
> heavily underused and infamous interfaces available in delphi/fpc for
> years.
>
> i.e. what's the basic point of using an interface compared to objects and
> inheritance and procedures? Where do they really shine?
>
> In goLang (don't mean to make this off topic) interfaces are very similar
> to VarArgs or Variants but not as evil. I mean to keep this on topic by
> asking specifically what corba interfaces should be used for?  Also do
> they work across DLL's or DSO's sort of like microsoft COM?

They do not work with COM. Basically, you should use CORBA interfaces if you
want to use interfaces, but are not interested in the reference counting
mechanism that comes implicitly with the COM interfaces.

The classes unit has an example of CORBA interfaces: the observer pattern is
implemented using CORBA interfaces.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Graeme Geldenhuys-6
In reply to this post by noreply
On 2016-09-28 20:40, Lars wrote:
> i.e. what's the basic point of using an interface compared to objects and
> inheritance and procedures? Where do they really shine?

Not to repeat tons of information already available on the Internet -
Google is your friend.

Objects, Inheritance and Interfaces all have their place, and for
different needs. I like the fact that I can explicitly make available an
API to use Objects, even if the objects are not directly related. I also
like the fact that with interfaces, I can make the implementation private.

I also like to use Interfaces to help with unit testing. Without
Interfaces I can only unit test public methods. I have to create a
"friend class" (aka a class hack) to unit test protected methods. With
Interfaces I don't need any "hacks" and can still unit test
functionality of a class even if that implementation is in the private
section of a class. I like to unit test code, but hate being forced to
make things Public just to unit test them.

Others like the fact that COM Interfaces are reference counted - they
sometimes come in handy, but do tend to complicate matters much more.

Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

David Emerson
In reply to this post by noreply
On 09/28/2016 12:40 PM, Lars wrote:

> Curious, where do corba interfaces come in handy ? When can you use them
> that object oriented code won't offer the same features?

For multiple inheritence, since fpc does not support multiple
inheritence without interfaces. Interfaces are an addition to object
oriented code, not separate from it.

In my particular case, in a project of mine in which I am drawing
lattices, I have a concept of a node. Sometimes I want to draw that node
myself using AGGPAS, and sometimes I want that node to itself be a
Window/Widget, descending from fpGUI classes. If I'm using AGGPAS to
draw the node, then I don't want the node to descend from fpGUI -- I use
my own lighter-weight class hierarchy. So in order to be able to treat
these two things the same, I define "enclosing node" as an interface.
Now I can have classes descending from two (or more) different
hierarchies, which both implement the enclosing node interface. I can
write functions to do things with enclosing nodes, and get the benefits
of polymorphism with multiple inheritence.

All that said, interfaces have their quirks and I wish we had more
native support for multiple inheritence (also templates but that's
another story) but anyway I'll take what I can get :-)

~David.



_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

David Emerson
In reply to this post by Graeme Geldenhuys-6
On 09/28/2016 01:24 AM, Graeme Geldenhuys wrote:

> On 2016-09-28 08:38, David Emerson wrote:
>> I'm testing out CORBA interfaces before changing all my code to use them
>> (I would prefer not to have managed types for interfaces).
>
> I've been using CORBA style interfaces for years, and it was always
> worked well for me. I must add, I never use the is/as syntax though (for
> COM or CORBA style interfaces). I always use Supports(..) as in:
>
>  var
>    h: i_hello;
>  begin
>    // fellow is the class instance variable.
>    if Supports(fellow, i_hello, h) then
>       h.hello;
>
>
> The Supports(..) syntax just seems more logical to me than the is/as
> syntax. The latter assumes the object "is" an instance of one of the
> interfaces and simply typecasts it to a interface variable. The
> Supports(..) syntax queries the object instance and asks if it supports
> a specific interface.


Thanks, Graeme. I tried this but I'm getting a compilation error:
"Interface type i_hello has no valid GUID"

I assume this is the Supports function(s) in sysutils

Is it possible to use Supports / QueryInterface / GetInterface /
something else, without having a GUID?

Alternately, if I give a GUID does an interface not automatically become
COM + managed?

Thanks,
David


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

noreply
In reply to this post by Graeme Geldenhuys-6
On Wed, September 28, 2016 3:44 pm, Graeme Geldenhuys wrote:

> Others like the fact that COM Interfaces are reference counted - they
> sometimes come in handy, but do tend to complicate matters much more.
>


And Corba interfaces.. you have to implement that yourself?

I found this
http://www.lenholgate.com/blog/2001/02/corba---reference-counting.html

As for googling the info, sometimes I just find it easier to talk to a
real programmer with experience than sifting through millions of pages of
misinformation from google :-)
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Graeme Geldenhuys-6
In reply to this post by David Emerson
On 2016-09-28 22:59, David Emerson wrote:
> Is it possible to use Supports / QueryInterface / GetInterface /
> something else, without having a GUID?

I'm not sure. I just had a look at my code. It seems all my [Corba]
Interface have GUID's defined too. I never noticed - probably down to
habit from my COM Interface days with Delphi 4-7.

Ctrl+Shift+G in Lazarus IDE should generate a GUID for you.


> Alternately, if I give a GUID does an interface not automatically become
> COM + managed?

No definitely not! The GUID just helps the compiler or RTL identify an
Interface.


Regards,
  Graeme

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

David Emerson
On 09/28/2016 03:31 PM, Graeme Geldenhuys wrote:
> On 2016-09-28 22:59, David Emerson wrote:
>> Alternately, if I give a GUID does an interface not automatically become
>> COM + managed?
>
> No definitely not! The GUID just helps the compiler or RTL identify an
> Interface.

Oh fabulous! Thanks much. I definitely got a wrong impression on that one.

 > Ctrl+Shift+G in Lazarus IDE should generate a GUID for you.

thanks, very handy :)

~David.


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Martin Schreiber-2
In reply to this post by noreply
On Wednesday 28 September 2016 21:40:39 Lars wrote:
> On Wed, September 28, 2016 2:24 am, Graeme Geldenhuys wrote:
> > On 2016-09-28 08:38, David Emerson wrote:
> >> I'm testing out CORBA interfaces before changing all my code to use
> >> them (I would prefer not to have managed types for interfaces).
> >
> > I've been using CORBA style interfaces for years,
>
> Curious, where do corba interfaces come in handy ?

Assume there is a support-function or -class, for example TStatFiler from
MSEgui, which stores and reads values to/from streams. It must call the
appropriate read and write functions in the client classes. It is possible to
define the functions in a common ancestor of the clients as "virtual
abstract" which enforces a single client-class hierarchy which often is
impossible to achieve.
The solution are interfaces but not the default COM-interfaces with their
reference counting nightmare but the not reference counted CORBA-interfaces -
which is a wrong name BTW because they have nothing to do with CORBA.

Clients of TStatReader/TStatWriter must implement the interface:
"
 istatfile = interface(iobjectlink)[miid_istatfile]
  procedure dostatread(const reader: tstatreader);
  procedure dostatwrite(const writer: tstatwriter);
  procedure statreading;
  procedure statread;
  function getstatvarname: msestring;
  function getstatpriority: integer;
 end;
"
iobjectlink provides basic object communication functionality:
"
 iobjectlink = interface(inullinterface)
  procedure link(const source,dest: iobjectlink; valuepo: pointer = nil;
                        ainterfacetype: pointer = nil; once: boolean = false);
  procedure unlink(const source,dest: iobjectlink; valuepo: pointer = nil);
               //source = 1 -> dest destroyed
  procedure objevent(const sender: iobjectlink; const event: objecteventty);
  function getinstance: tobject;
 end;
"
inullinterface is an empty CORBA-style interface:
"
{$interfaces corba}
 inullinterface = interface
  //no referencecount
 end;
"
If a client registers at TStatFiler it provides its "istatfile" pointer which
will be stored in a list in TStatFiler. TStatfiler will use this list in
order to call the necessary functions and procedures of the clients if
reading/writing has been triggered.

MSEgui extensively uses CORBA-interfaces.

Martin
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Martin Schreiber-2
In reply to this post by David Emerson
On Thursday 29 September 2016 01:14:44 David Emerson wrote:

> On 09/28/2016 03:31 PM, Graeme Geldenhuys wrote:
> > On 2016-09-28 22:59, David Emerson wrote:
> >> Alternately, if I give a GUID does an interface not automatically become
> >> COM + managed?
> >
> > No definitely not! The GUID just helps the compiler or RTL identify an
> > Interface.
>
> Oh fabulous! Thanks much. I definitely got a wrong impression on that one.
>
>  > Ctrl+Shift+G in Lazarus IDE should generate a GUID for you.
>
> thanks, very handy :)
>
Corba-style interfaces can use any string as ID so for local interfaces it is
possible to use shorter ID-strings for better performance.
MSEide constructs such ID's by RightClick-'Insert UID'. Example: "['jA']{49}".
If the interface is listed in header of the queried class it is also possible
to get the interface by compile time type conversion:
"
type
{$interfaces corba}

 iabc = interface
  procedure a();
  procedure b();
  procedure c();
 end;
 
 tabc = class(tobject,iabc)
  protected
   procedure a();
   procedure b();
   procedure c();
 end;

var
 abc: tabc;
 abcintf: iabc;

[...]
 abcintf:= iabc(abc);
"
which has the best performace.

Martin
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Graeme Geldenhuys-6
On 2016-09-29 06:42, Martin Schreiber wrote:
>  abcintf:= iabc(abc);
> "
> which has the best performace.

But you must still remember to check if abcintf has been successfully
assigned.

[personal opinion]
Hence I still prefer the Supports(..) call as it can be used directly in
a boolean expression, the name itself makes it very clear what you are
asking from the code, and it just reads better.

Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Martin Schreiber-2
On Thursday 29 September 2016 10:49:03 Graeme Geldenhuys wrote:
> On 2016-09-29 06:42, Martin Schreiber wrote:
> >  abcintf:= iabc(abc);
> > "
> > which has the best performance.
>
> But you must still remember to check if abcintf has been successfully
> assigned.
>
There will be a compiler error if "abc" does not implement "iabc".

> [personal opinion]
> Hence I still prefer the Supports(..) call as it can be used directly in
> a boolean expression, the name itself makes it very clear what you are
> asking from the code, and it just reads better.
>
Step through the code with F7 and you will see what overhead it means. AFAIK
the compiler does not optimise for compiletime known case.

Martin
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Sven Barth-2

Am 29.09.2016 11:34 schrieb "Martin Schreiber" <[hidden email]>:
>
> On Thursday 29 September 2016 10:49:03 Graeme Geldenhuys wrote:
> > On 2016-09-29 06:42, Martin Schreiber wrote:
> > >  abcintf:= iabc(abc);
> > > "
> > > which has the best performance.
> >
> > But you must still remember to check if abcintf has been successfully
> > assigned.
> >
> There will be a compiler error if "abc" does not implement "iabc".

Graeme meant that abc can still be Nil.

Regards,
Sven


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

David Emerson
In reply to this post by Martin Schreiber-2
On 09/28/2016 10:42 PM, Martin Schreiber wrote:
> Corba-style interfaces can use any string as ID so for local interfaces it is
> possible to use shorter ID-strings for better performance.

Is this described anywhere in the documentation?

 > MSEide constructs such ID's by RightClick-'Insert UID'. Example:
 > "['jA']{49}".

Nice feature! One of these days I've got to try MSEide again.


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

David Emerson
In reply to this post by Sven Barth-2
On 09/29/2016 06:06 AM, Sven Barth wrote:

> Am 29.09.2016 11:34 schrieb "Martin Schreiber" <[hidden email]
> <mailto:[hidden email]>>:
>>
>> On Thursday 29 September 2016 10:49:03 Graeme Geldenhuys wrote:
>> > On 2016-09-29 06:42, Martin Schreiber wrote:
>> > >  abcintf:= iabc(abc);
>> > > "
>> > > which has the best performance.
>> >
>> > But you must still remember to check if abcintf has been successfully
>> > assigned.
>> >
>> There will be a compiler error if "abc" does not implement "iabc".
>
> Graeme meant that abc can still be Nil.

Or perhaps, var abc : TObject (or some other descendent passed as a
variable) -- it may or may not support iabc, but we want to check, and
do something with it if it does.

~David


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Martin Schreiber-2
In reply to this post by David Emerson
On Thursday 29 September 2016 23:50:04 David Emerson wrote:
> On 09/28/2016 10:42 PM, Martin Schreiber wrote:
> > Corba-style interfaces can use any string as ID so for local interfaces
> > it is possible to use shorter ID-strings for better performance.
>
> Is this described anywhere in the documentation?
>
http://www.freepascal.org/docs-html/current/ref/refse46.html#x100-1220007.6

Martin
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Graeme Geldenhuys-6
In reply to this post by David Emerson
On 2016-09-29 22:50, David Emerson wrote:
> One of these days I've got to try MSEide again.

MSEide is awesome. I use it for Console, fpGUI and even LCL programming.

Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: corba interfaces - is/as not working properly

Michalis Kamburelis-3
This thread, and my own tests, showed that you need to assign GUIDs to
interfaces to have reliable "is", even for CORBA interfaces.

I'm wondering, would it be possible to improve here things on the
compiler side (at least only in FPC-specific modes or under some
modeswitch), to make things safer?

Propositions:

1. The question "X is IMyInterface", when "IMyInterface" does not have
a GUID, could fail to compile.

  Just like right now "Supports(X, IMyInterface)" does not compile
when "IMyInterface" does not have a GUID. This is better -- the
presence of GUID is checked at compile-time.

  Right now, the "X is IMyInterface" seems like a trap, when the
interfaces can have no GUIDs. Two interfaces without GUIDs are treated
as equal, by the "is" operator. The example code that started this
thread shows how bad are the consequences. I made my own little
example, attaching.

2. I assume "as" operator has the same problem? So "X as IMyInterface"
would benefit from the same safeguard.

3. How about going further, and just making the GUID required at every
interface declaration? Since it's necessary to have reliable "is",
"as", "Supports"...

4. And how about going even more further, and just generate an
internal GUIDs (random, or based on a memory address) when no GUID is
explicitly specified? This way "is", "as", "Supports" always just
work. And it removes the need to do 1., 2., 3. above, of course.

  Is there a drawback to doing this, that I don't see?

Regards,
Michalis

P.S. I have added to
http://michalis.ii.uni.wroc.pl/~michalis/modern_pascal_introduction/modern_pascal_introduction.html
a section documenting it.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

guids_are_needed.lpr (2K) Download Attachment
12