最終更新日 2024-09-25

文字列の扱い方③ SafeHandleを継承した文字列ラッパークラス

概要

SafeHandleを継承し、メモリの解放に対して安全性を担保しつつ、
文字列としての特性と、そこを指すIntPtrとしての特性を持たせるクラスを作るという方法です。
もっとも完全に近い手法ですが、何をやっているのかわかりにくい記述となってしまいます。
(本来はただ文字列を返したいだけなのに...)

ラッパークラス StaticWStrPtrHandle

StaticWStrPtrHandle.cs
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal sealed class StaticWStrPtrHandle : SafeHandle
{
    private StaticWStrPtrHandle() : base(IntPtr.Zero, true) { }
    public StaticWStrPtrHandle(String managedString) : base(IntPtr.Zero, true)
    {
        handle = Marshal.StringToHGlobalUni(managedString);
    }

    public static implicit operator String(StaticWStrPtrHandle managedString)
    {
        return managedString.ToString();
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    protected override bool ReleaseHandle()
    {
        try
        {
            Marshal.FreeHGlobal(handle);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Trace.WriteLine("文字列解放エラー:\n" + ex.Message);
            return false;
        }

        return true;
    }

    public override bool IsInvalid
    {
        get { return (IntPtr.Zero == handle); }
    }

    public override string ToString()
    {
        if (this.IsInvalid)
        {
            return String.Empty;
        }
        else
        {
            return Marshal.PtrToStringUni(handle);
        }
    }
}

StaticWStrPtrHandle を利用する

文字列を引数とする場合も、文字列を返す場合も「IntPtr型」でやりとりすることになるため、
表面的な記述の上ではわかりにくくなってしまいます。

ClassLibrary36.cs
using System;
using System.Runtime.InteropServices;

namespace ClassLibrary36
{
    public class Class1
    {
        // 文字列を引数とする関数
        [DllExport]
        public static IntPtr my_func1(IntPtr wStringPointer)
        {
            String str = Marshal.PtrToStringUni(wStringPointer);
            return (IntPtr)str.Length;
        }

        // static な静的フィールドのクラス変数にしておくことが重要。
        // 関数と1対1にしておく。
        static StaticWStrPtrHandle hReturnWStringPointer = new StaticWStrPtrHandle("");

        [DllExport]
        // 文字列を返す関数
        public static IntPtr my_func2() 
        {
            // 先にDispose()をする。1つ前の文字列を解放するため。
            hReturnWStringPointer.Dispose();

            // 文字列を StaticWStrPtrHandle 型として確保する。
            hReturnWStringPointer = new StaticWStrPtrHandle("abc");

            // 文字列が格納されているメモリアドレスを返す。
            return hReturnWStringPointer.DangerousGetHandle();
        }
    }
}

呼び出し側

ClassLibrary36.mac
#DLL = loaddll( currentmacrodirectory + @"\ClassLibrary36.dll");

#str_length = dllfuncw( #DLL, "my_func1", "あいうえお");
message(str(#str_length));

$str_result = dllfuncstrw( #DLL, "my_func2");
message($str_result);