C#でレガシーな事をする方向けのまとめ

  • 55
    Like
  • 0
    Comment
More than 1 year has passed since last update.

構造体

バウンダリアライメントを調整するには

StructLayoutを利用する。
この指定は直後のstruct一つのみに適用できる。


    using System.Runtime.InteropServices;

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Header
    {
        ushort len;
        byte firstAddress;
        byte secondAddress;
        byte sourceAddress;
    }

構造体のサイズについて

  • 構造体はvirtual関数を持つことができない。(実装にvtable的なものがない、はず)

基本型のみ

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct A
    {
        public int a;
        public byte b;

        public static readonly int len;
        static A()
        {
            len = Marshal.SizeOf(typeof(A)); =>  5
        }
    }

構造体中に構造体有り

構造体中に構造体(基本型のみ)有り

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct B
    {
        public byte a;
        public A b;

        public static readonly int len;
        static B()
        {
            len = Marshal.SizeOf(typeof(B)); => 6 (A.len + sizeof(b))
        }
    }

構造体中に構造体(参照型有り)有り

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct A
    {
        public int a;
        public byte[] b;

        public static readonly int len;
        static A()
        {
            len = Marshal.SizeOf(typeof(A)); => 8
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct B
    {
        public byte a;
        public A b;

        public static readonly int len;
        static B()
        {
            len = Marshal.SizeOf(typeof(B)); => 9 (A.len + sizeof(b))
        }
    }

構造体中に構造体(UnamagedType配列)有り

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct A
    {
        public int a;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] b;

        public static readonly int len;
        static A()
        {
            len = Marshal.SizeOf(typeof(A)); => 6
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct B
    {
        public byte a;
        public A b;

        public static readonly int len;
        static B()
        {
            len = Marshal.SizeOf(typeof(B)); => 7 (A.len + sizeof(a)) 
        }
    }

配列を含む構造体のマーシャリング

構造体中に配列を含む構造体をマーシャリングするためには以下のようにする。

OK例


 struct AAA
 {
   [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
   public byte[] test;
 }

NG例

 struct AAA
 {
   public byte[] test;
 }

使い方の注意

次の例はNG

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct A
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public int a; => MarshalAsを指定して非配列はNG. Marshal系のメソッドを利用するとException発生.

        public static readonly int len;
        static A()
        {
            len = Marshal.SizeOf(typeof(A));
        }
    }

OK例

    public struct A
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] a;

        public static readonly int len;
        static A()
        {
            len = Marshal.SizeOf(typeof(A));
        }
    }

    void foo()
    {
      A a = new A();
      a.a = new byte[2]; => newの必要あり。サイズがSizeConstと一致の必要有
    }

構造体<=>byte[] 変換

unmanagedの利用はお勧めしない。つまり次のコードはお勧めしない。

※ 変換対象の型はMarshalAs属性指定済みであること。

      public static void byteToStruct<T>(byte[] buffer, int pos, int typeLen, out T result)
        {
            int endPos = pos + typeLen;
            if (endPos > buffer.Length)
            {
                throw new Exception("サイズ不正[endPos," + endPos.ToString() + "][buflen," + buffer.Length.ToString() + "]");
            }

            T obj;
            IntPtr ptr = Marshal.AllocHGlobal(typeLen);
            try
            {
                Marshal.Copy(buffer, pos, ptr, typeLen);
                obj = (T)Marshal.PtrToStructure(ptr, typeof(T));
                result = obj;
            }
            finally
            {
                if (IntPtr.Zero != ptr)
                {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }


         public static void structToByte<T>(T obj, int typeLen, byte[] buffer, int pos)
        {
            int endPos = pos + typeLen;
            if (endPos > buffer.Length)
            {
                throw new Exception("サイズ不正[endPos," + endPos.ToString() + "][buflen," + buffer.Length.ToString() + "]");
            }
            IntPtr ptr = Marshal.AllocHGlobal(typeLen);
            try
            {
                Marshal.StructureToPtr(obj, ptr, false);
                Marshal.Copy(ptr, buffer, pos, typeLen);
            }
            finally
            {
                if (IntPtr.Zero != ptr)
                {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }


構造体は値型

CSharpの構造体は生成時にnewを必要とするが値型。

  • nullを許容しない。
  • 次のオブジェクトA a,A bは別々のオブジェクト
 struct A
 {
   int a;
   int b;
 }

 void foo()
 {
   A a = new A() { a=10,b=20 };
   A b = a; // オブジェクトの参照ではなく値のコピーが行われる。
   b.a = 30; => a.a==10 b.a==30
 }   

値型(struct)でstaticコンストラクタ禁止

値型のstaticコンストラクタは、値型のコンストラクタが呼び出されない限り呼び出されない。

そのためstaticコンストラクタ中でstaticメンバの初期化を行っていると、参照のタイミングによっては初期化されていない可能性があるため、禁止とのこと。(MSDNの記載による)

MSDN 値型のスタティック フィールドのインラインを初期化します
http://msdn.microsoft.com/ja-jp/library/vstudio/ms182346(v=vs.100).aspx

※翻訳が意味不明

MSDN Initialize value type static fields
http://msdn.microsoft.com/en-us/library/ms182346.aspx
※意味がまだ分かる。

委譲

汎用委譲 Func

Func<T...,RetVal>

Tが引数の型、RetValは戻り値の型


 void init()
 {
     Func<int,int,int> addDelegate = delegate(int a,int b) {
        return a + b;
     }

     int sum = foo(1,2, addDelegate);
 }

 int foo(int a, int b, Func<int,int,int> operation) {
   return operation(a,b);
 }

delegateを呼び出す場合はnullチェックが必要

delegate void fucn();

 func myFunc = null;

 void foo() {
  if( myFunc != null ) {
   myFunc();
  }
 }

Delegateオブジェクトに対する匿名メソッドの指定方法

Delegateは抽象クラスのため、パラメータとしてDelegateを要求するメソッドへ直接匿名メソッドを利用することはできない。

利用するためには、Delegate型を明示する必要がある。

 void otherThreadMain()
 {
   items...

   listView.Invoke((MethodInvoker)delegate()
   {
     listView.Clear();
     listView.AddRange(items);
   },null);
 }

その他

DLLとCSharpでの関数の呼び出し規約を調整するには

CallingConventionを利用する。

http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.callingconvention.aspx

 using System.Runtime.InteropServices;

 [System.Runtime.InteropServices.DllImport("MyDll.dll" ,CallingConvention=CallingConvention.ThisCall, EntryPoint = "? get@CMyFunc@@QAEHHH@Z")]
 extern Int32 get(Int32 a, Int32 b);

CSharpからVC(アンマネージド)で作成されたDLLを利用するには

VCでクラスDLLを作成する。

 class CUDPSocket;
 class DLLDECLAR CMyTest
 {
 private:
    CUDPSocket* mSocket;

 public:
    CMyTest(void);
    virtual ~ CMyTest(void);

 public:
    INT get(INT a, INT b);
 };

 // new 用
 DLLDECLAR CMyTest* newMyTest();
 // delete用
 DLLDECLAR void releaseMyTest(CMyTest* obj);

C#等のCLR環境でWrapperDLLを作成する。(直接アプリ側でクラスを定義してもよい)

 namespace XXX
 {
     public class MyTest : IDisposable 
     {
         // EntryPointはDependency Walkerなどで調べる...
         [System.Runtime.InteropServices.DllImport("MyTestDll.dll",  EntryPoint = "?newMyTest@@YAPAVCMyTest@@XZ")]
         static extern IntPtr newMyTest();
         [System.Runtime.InteropServices.DllImport("MyTest.dll",  EntryPoint = "?releaseMyTest@@YAXPAVCMyTest@@@Z")]
        static extern void deleteMyTest(IntPtr obj);
         // メソッドは呼び出し規約を明示する必要がある。
         [System.Runtime.InteropServices.DllImport("MyTestDll.dll"  ,CallingConvention=CallingConvention.ThisCall, EntryPoint = "? get@CMyTest@@QAEHHH@Z")]
         static extern Int32 get(IntPtr obj, Int32 a, Int32 b);
         [System.Runtime.InteropServices.DllImport("MyTest.dll",  EntryPoint="?addDll@@YAHHH@Z")]
         static extern Int32 addDll(Int32 a, Int32 b);

         IntPtr mBody;

         public MyTest()
         {
             this.mBody = newMyTest();
         }

         public void Dispose()
         {
             deleteMyTest(this.mBody);
         }

         public Int32 Get(Int32 a, Int32 b)
         {

             return get(this.mBody, a, b);
         }

         public static Int32 Add(Int32 a, Int32 b)
         {
             return addDll(a, b);
         }
     }
 }

後はC#で利用するだけ。

staticコンストラクタの注意

  • staticコンストラクタは、初回のインスタンス生成か、初回のstaticフィールドのアクセス時に呼び出される。
  • 厳密な呼び出しタイミングは保障されない。

GUIのスレッドルール

CSharpでは、GUI(Form等)を生成したスレッド以外から、その部品に
アクセス(変更のみ?)していはいけないというルールがある。
(このルールを破って部品にアクセスした場合、例外がスローされる)

UIスレッド以外から部品を更新するためには、Control.Invokeメソッドを利用する。


 ListBox listBox1;

 public void onReceiveAlive(Object sender, InetEvent evt)
 {
   // 他スレッドから呼び出された場合.
   if (listBox1.InvokeRequired)
   {
     listBox1.Invoke(new Action<Object, InetEvent>(onReceiveAlive), 
                     new Object[]     { sender, evt });
     return;
   }

   // 通常の処理.
   listBox1.Items.Insert(0, "test");
 }