文字列の扱い方① [MarshalAs(UnmanagedType.LPWStr)] String

概要

文字列型をどうするかは悩ましいところです。
考えられる選択肢としては、大別して以下の3通りとなることでしょう。

  • 文字列の扱いその① [MarshalAs(UnmanagedType.LPWStr)] String

    メモリリークを受け入れて、String型とする。

    メモリリークメモリリークといっても秀丸のプロセスを閉じれば解放されます。
    とはいえ秀丸から関数を呼び出す度に文字列分のメモリが確保されていくので、
    ・極端に呼び出し回数が多い場合(forループで何百万回も呼び出す)
    ・異常に返す文字列が大きい場合(100MB以上の文字列を返すのを何度も実行する)
    には不適切な実装方法となります。

  • 文字列の扱いその② char *

    unsafe にして、char *にする。
    理論上は厳密にはあやまった方法ですが、.NET Frameworkの仕組み上、
    現実的には通常問題が起きないやり方です。

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

    理論上は理想の実装方法といえますが、本来やりたい「ただの文字列を返す処理」と、記述が乖離してしまうため、
    何が書いてあるのかわかりにくくなってしまう方法です。

この3つの方法について、順に解説していきます。

[MarshalAs(UnmanagedType.LPWStr)] String を使った書き方

この記述はメモリリークはありますが「移植性が最も高い方法」です。
「DllExport.bat による方法」→「hm.NET による方法」への移行、もしくは、「COMによる方法」への移行、
どちらに将来移行するにしても、
[DllExport] と [MarshalAs(UnmanagedType.LPWStr)] を削除するだけで、移行が完了し同じ動作(しかもメモリリークなし)が期待できるのがポイントです。

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

namespace ClassLibrary36 {

    public class Class1
    {
		// この方法の弱点は、この[return: MarshalAs(UnmanagedType.LPWStr)]によって、メモリリークを起こしてしまうこと
        // プロセスを閉じればどうせ解放されるから、特大級の数百MBの文字列や、数百万回ループ呼び出しが無い限り、実用上問題がないという考え方
        [DllExport]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        static String abc([MarshalAs(UnmanagedType.LPWStr)] String a, [MarshalAs(UnmanagedType.LPWStr)] String b)
        {
            return a + b;
        }

    }
}

呼び出し側

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

$add_str = dllfuncstrw( #DLL, "abc", "あいうえお", "素麺");
message($add_str);