Life-time strategies questions

Hi, i need to implement a life-time strategies in a c# project. i studied the code from Island source. make a skeleton like this:

public struct GdipObject: ILifetimeStrategy<GdipObject>{
    private static IntPtr value;

public static void* New(void* aTTY, NativeInt aSize)
{

}

public static void Assign(ref GdipObject aDest, ref GdipObject aSource)
{
    aDest = aSource;
}

public static void Copy(ref GdipObject aDest, ref GdipObject aSource)
{
    aDest = aSource;
}

public static void Init(ref GdipObject Dest)
{

}

public static void Release(ref GdipObject Dest)
{

}

}

questions:

  1. what is aTTY point to by in the New method ? what is it use for? purpose ?
    the document said: “that can hold the pointer for the maintained object.” i don’t understand, the pointer return from malloc isn’t the object ? why another pointer?
    2.the size of the record is always suppose to be sizeof(Intptr)+aSize ?

Lifetime strategies can be used for external types. In cases like that, it would be used to create objects. The boehmgc strategy (default GC) is implemented like this:

For this kind of work where the struct you want to define operators for is the same struct, it might be better to use regular operators for lifetime like I do in Olestring:

thank you @ck, i’m just a beginner which need some basic concepts. i don’t understand your code. what is aTTY exactly ? :joy:

aTTY is TypeInfo. But I think lifetime strategies isn’t a good fit for your usecase. You use them when defining the lifetime for “other” types, like classes or structs.

If I read your code right you need an object that manages an external pointer. Regular operators is a better fit (but correct me if I am wrong). something like:

public struct GdiObject {
  private static IntPtr value;
  public GdiObject(IntPtr value) {this.value = value; } // regular ctor
  public this Copy(GdiObject other) { 
    // copy ctor
    this.value = CloneGdiObject(other.value);
  }
  public static operator = (ref OleString dest, ref OleString source) { }}
  public ~() { cleanup }
  } 

Note that this requires the next version as I just found that hte = operator was only supported in Oxygene, not Hydrogene.

i try to wrap around the windows Gdi+ library, many of its classes are subclass from the GdiplusBase, use its own memory management. as the follow c++ code.

class GdiplusBase
{
public:
    void (operator delete)(void* in_pVoid)
    {
       DllExports::GdipFree(in_pVoid);
    }
    void* (operator new)(size_t in_size)
    {
       return DllExports::GdipAlloc(in_size);
    }
    void (operator delete[])(void* in_pVoid)
    {
       DllExports::GdipFree(in_pVoid);
    }
    void* (operator new[])(size_t in_size)
    {
       return DllExports::GdipAlloc(in_size);
    }
};

i can just copy & modify the code from RC strategy in pascal, but when i tried to implement in c# it looks difficult. 2 problems here. 1) as u mentioned, operator = in c# is a problem. 2) how to implement finalizer in c# ?

@ck i made my own RC strategy in c#, compiled without error. but not guarantee to be working.

using rtl;
namespace GDIPlus{

    public class GdipObject:ILifetimeStrategy<GdipObject>{
      private IntPtr value;
      
      public static void* New(void* aTTY, NativeInt aSize)
      {
          IntPtr ptr = API.GdipAlloc( aSize + sizeof( IntPtr ) );
          *((NativeUInt*)ptr) = 1;
          ptr = (IntPtr)(((byte*)ptr) + sizeof( IntPtr ));
          *((NativeUInt*)ptr) = (NativeUInt)aTTY;
          memset( ((byte*)ptr ) + sizeof( IntPtr ), 0, aSize - sizeof( IntPtr ));
          return (void*)ptr;
      }
      
      public static void Copy(ref GdipObject aDest, ref GdipObject aSource)
      {
          if( aSource.value != 0 ){
              InternalCalls.Increment( ref ((NativeInt*)(&aSource))[-1]);
              aDest.value = aSource.value;
          }
      }
      
      
      public static void Assign(ref GdipObject aDest, ref GdipObject aSource)
      {
          if( (&aDest) == (&aSource) ){
              return;
          }
          var srcInst = aSource.value;
          var oldInst = InternalCalls.Exchange( ref aDest.value, srcInst );
          if( oldInst == srcInst ){
              return;
          }
          
          if( srcInst != 0 ){
              InternalCalls.Increment( ref ((NativeInt*)(&srcInst))[-1]);
          }
          if( oldInst != 0 ){
              if( InternalCalls.Decrement( ref ((NativeInt*)(&oldInst))[-1] ) == 0 ){
                  FreeObject( oldInst );
              }
          }
      }
      
      public static void FreeObject(IntPtr obj ){
          if( obj == 0 ){
              return;
          }
          
          try{
            InternalCalls.Cast<GdipObject>((void*)obj).Finalize();
            #pragma disable W58
            API.GdipFree( (IntPtr)(((byte*)obj) - sizeof(IntPtr)) );
            #pragma enable W58
          }
          catch(Exception e){
              
          }
      }
      
      // 
      public ~GdipObject(){
          var old = InternalCalls.Exchange(ref value, 0 );
          if( old == 0 ){
              return;
          }
          var p = InternalCalls.Decrement( ref ((NativeInt*)(&old))[-1] );
          if( p == 0 ){
              FreeObject( p );
          }
      }
      

      public static void Init(ref GdipObject Dest)
      {

      }
      
      public static void Release(ref GdipObject Dest)
      {
          var v = InternalCalls.Exchange(ref Dest.value, 0 );
          if( v == 0 ){
              return;
          }
          
          var p = InternalCalls.Decrement(ref ((NativeInt*)(&Dest))[-1] );
          if( p == 0 ){
              FreeObject( p );
          }
      }
  }

}

another question, i make a test program, how to correctly apply the newly created life-time strategy ?

namespace TestConsoleApplication
{
        public struct GdipObject:ILifetimeStrategy<GdipObject>{
            private IntPtr value;
  
        public static void* New(void* aTTY, NativeInt aSize)
        {
            IntPtr ptr = (IntPtr)malloc( aSize + sizeof( IntPtr ) );
            *((NativeUInt*)ptr) = 1;
            ptr = (IntPtr)(((byte*)ptr) + sizeof( IntPtr ));
            *((NativeUInt*)ptr) = (NativeUInt)aTTY;
            memset( ((byte*)ptr ) + sizeof( IntPtr ), 0, aSize - sizeof( IntPtr ));
            Console.WriteLine(string.Format("New size: {0}", aSize ));
            return (void*)ptr;
        }
  
        public static void Copy(ref GdipObject aDest, ref GdipObject aSource)
        {
            if( aSource.value != 0 ){
                InternalCalls.Increment( ref ((NativeInt*)(&aSource))[-1]);
                aDest.value = aSource.value;
            }
        }
  
  
        public static void Assign(ref GdipObject aDest, ref GdipObject aSource)
        {
            if( (&aDest) == (&aSource) ){
                return;
            }
            var srcInst = aSource.value;
            var oldInst = InternalCalls.Exchange( ref aDest.value, srcInst );
            if( oldInst == srcInst ){
                return;
            }
      
            if( srcInst != 0 ){
                InternalCalls.Increment( ref ((NativeInt*)(&srcInst))[-1]);
            }
            if( oldInst != 0 ){
                if( InternalCalls.Decrement( ref ((NativeInt*)(&oldInst))[-1] ) == 0 ){
                    FreeObject( oldInst );
                }
            }
        }
  
        public static void FreeObject(IntPtr obj ){
            if( obj == 0 ){
                return;
            }
      
            try{
                InternalCalls.Cast<GdipObject>((void*)obj).Finalize();
                #pragma disable W58
                free( (void*)( (IntPtr)(((byte*)obj) - sizeof(IntPtr)) ) );
                #pragma enable W58
            }
            catch(Exception e){
          
            }
        }
  
        // 
        public ~GdipObject(){
            var old = InternalCalls.Exchange(ref value, 0 );
            if( old == 0 ){
                return;
            }
            var p = InternalCalls.Decrement( ref ((NativeInt*)(&old))[-1] );
            if( p == 0 ){
                FreeObject( p );
            }
        }
  

        public static void Init(ref GdipObject Dest)
        {

        }
  
        public static void Release(ref GdipObject Dest)
        {
            var v = InternalCalls.Exchange(ref Dest.value, 0 );
            if( v == 0 ){
                return;
            }
      
            var p = InternalCalls.Decrement(ref ((NativeInt*)(&Dest))[-1] );
            if( p == 0 ){
                FreeObject( p );
            }
            Console.WriteLine(string.Format("Release {0}", p ));
        }
    }




static class Program
{
    public static Int32 Main(string[] args)
    {
        // following line is not compile-able..
        GdipObject<string> s = "Hello";
        Console.WriteLine("The magic happens here.");
        return 0;
    }

}
}

My apologies; I missed your replies somehow.

You really shouldn’t be using the lifetime stuff at all for this but something like this:

public struct GdiObject {
  private static IntPtr value;
  public GdiObject(IntPtr value) {this.value = value; } // regular ctor
  public this Copy(GdiObject other) { 
    // copy ctor
    this.value = CloneGdiObject(other.value);
  }
  public static operator = (ref GdiObject dest, ref GdiObject source) { }}
  public ~() { cleanup }
  } 

with last fridays build.

Then it works right away. Now if you do ever need a lifetime override, something like:
[Assembly: RemObjects.Elements.System.LifetimeStrategyOverrideAttribute(typeOf(rtl.winrt.HSTRING), typeOf(HString_Helper))]

could work; But really thats mostly for overriding it. Above code matches the c++ better.

public struct GdiObject {
  private static IntPtr value;
  public GdiObject(IntPtr value) {this.value = value; } // regular ctor
  public this Copy(GdiObject other) { 
// copy ctor
this.value = CloneGdiObject(other.value);
  }
  public static bool operator = (ref GdiObject dest, ref GdiObject source) { }
  public ~GdiObject() { }
  } 

ck, how can i invoke Copy method ? i tried following code, it shows error: Parameter labels do not match. Parameter 1 is unlabeled, but should should be labeled “withCopy” in call to “MyObject Copy(MyObject src)”

MyObject obj2 = new MyObject(obj);

The compiler calls that for you if you do something like:

  GdiObject x = otherObject; // << calls Copy

But if you want to call it directly:
GdiObject y = new GdiObject Copy(x);

public this Copy(GdiObject other) { 
    Console.WriteLine("Copy called");
}

GdiObject x = otherObject; // << calls Copy

i tested the code above. explicit call works, but implicit call isn’t working at all. it will call opeartor= method instead

Hrmm I think the = might override that behavior yes. But there are cases it still calls the Copy one. I’ll have to check when exactly.