C#からlong型を含むC言語を呼び出すときは、CLongを用いる

#129
2025.8.7
2025.8.7

C#からC言語を呼び出す際の問題の一つに、環境によってlongの大きさが異なるというものがあります。C言語の仕様ではlongを含む整数型の最小の大きさに関しての指定がありますが、厳密な大きさに関しては実装依存となっています。しかし現在ではintcharなどといったlong以外の整数型は、殆どのプラットフォームで共通した大きさとなっています。

ILP32/LP64/LLP64
sizeof(char) * 88
sizeof(short) * 816
sizeof(int) * 832
sizeof(long long) * 864

しかし、問題はlongの大きさです。一般的な32ビット環境ではlongは32ビットですが、64ビット Unix(-like)では64ビットとなっています。そしてさらにややこしいことに、64ビット WindowsではLLP64と呼ばれるメモリモデルが採用されており、longは32ビットになっています。

ILP32 (macOS/Linux/Windows (32ビット))LP64 (macOS/Linux (64ビット))LLP64(Windows (64ビット))
sizeof(long) * 8326432

これは、インターフェースにlongが含まれているC言語のコードを、C#から呼び出す際に問題となります。C#の基本型には、プラットフォームによって大きさが異なるC言語のlongを表現する型が無いためです。

このような場合こそが、.NET 6で導入されたSystem.Runtime.InteropServices.CLongの出番です。CLongはC言語のlongを表すための型であり、上記の大きさの違いをプラットフォームに応じて自動的に吸収してくれます。考え方としてはIntPtrの大きさがプラットフォームによって変化するのと同じです。

使い方は単純明快で、longに対応する部分の型としてCLongを指定するだけです。このようなC言語のコードを呼び出したいとします。

#if defined(_WIN32) || defined(_WIN64)
#define DllExport __declspec(dllexport)
#else
#define DllExport
#endif

DllExport long addLongs(long a, long b) { return a + b; }

typedef struct {
  long x;
  long y;
} LongPoint;

DllExport LongPoint addLongPoints(LongPoint p1, LongPoint p2) {
  LongPoint result;
  result.x = p1.x + p2.x;
  result.y = p1.y + p2.y;
  return result;
}

これを呼び出すC#コードは、引数やメンバとしてCLongを指定すれば良いだけです。

using System.Runtime.InteropServices;

// ...

[DllImport("native", CallingConvention = CallingConvention.Cdecl)]
public static extern CLong addLongs(CLong a, CLong b);

public struct LongPoint {
  public CLong x;
  public CLong y;
}

[DllImport("native", CallingConvention = CallingConvention.Cdecl)]
public static extern LongPoint addLongPoints(LongPoint p1, LongPoint p2);

static void Main(string[] args) {
  {
    CLong a = new CLong(1);
    CLong b = new CLong(2);
    CLong result = addLongs(a, b);
    Console.WriteLine($"addLongs({a.Value}, {b.Value}) = {result.Value}");
  }

  {
    LongPoint lp1 = new LongPoint { x = new CLong(5), y = new CLong(6) };
    LongPoint lp2 = new LongPoint { x = new CLong(7), y = new CLong(8) };
    LongPoint lpResult = addLongPoints(lp1, lp2);
    Console.WriteLine($"addLongPoints({lp1.x.Value}, {lp1.y.Value}) + ({lp2.x.Value}, {lp2.y.Value}) = ({lpResult.x.Value}, {lpResult.y.Value})");
  }
}

これによって、macOSでもWindowsでもLinuxでも、全く同じコードでlongを含むインターフェースを呼び出すことができます。

addLongs(1, 2) = 3
addLongPoints(5, 6) + (7, 8) = (12, 14)