コンパイラ仕様 / 文法定義 / C# 対応表
| 項目 | 値 |
|---|---|
| コンパイラ | nhspc.exe (コンソール) / NhspCompiler.Core.dll (ライブラリ) |
| 実装言語 | C# (.NET Framework 4.8) |
| ターゲット | .NET Framework 4.8 アセンブリ (IL) |
| 出力形式 | DLL (クラスライブラリ) / EXE (コンソール / Windows GUI) |
| ターゲットアーキ | AnyCPU (既定) / x86 / x64 / AnyCPU 32-bit preferred — -platform オプション |
| IL 生成 | System.Reflection.Emit (AssemblyBuilder / TypeBuilder / ILGenerator) |
| デバッグ情報 | PDB (SequencePoint / DebuggableAttribute) — -debug オプション |
| Win32 リソース | VERSIONINFO (アセンブリメタデータディレクティブから自動生成) / RT_GROUP_ICON+RT_ICON / RT_MANIFEST |
| ソース拡張子 | .nhsp |
Source (.nhsp) → Lexer → Parser → AST → Semantic → IL Emit → Assembly (.dll/.exe)
| 機能 | 構文 | 説明 |
|---|---|---|
| 1行1文 | x = 42 | 改行が文の区切り (セミコロン不要) |
| コロン区切り | a = 1 : b = 2 | HSP と同様、: で1行に複数文を記述可能 |
| 行継続 | a + b + \ | HSP と同様、\ + 改行で次の行に続く |
| dim 省略 | int x = 42 | dim int x = 42 と同等。C# 風の変数宣言 |
| コメント | ; コメント / // コメント | HSP 形式と C 形式の両方に対応 |
| 16進数 | $FF / 0xFF | HSP 形式と C 形式の両方に対応 |
| null リテラル | null / nullptr | C# 形式と C++ 形式の両方に対応 (同じ動作) |
; コロン区切り: 1行に複数文
int a = 1 : int b = 2 : int c = a + b
; 行継続: 長い行を分割
#dllfunc public static int MessageBoxW \
"MessageBoxW", \
IntPtr hWnd, string text, \
string caption, int type
; 式の途中でも分割可能
int result = 1 + 2 + \
3 + 4
// C# は ; で文を区切る
int a = 1; int b = 2; int c = a + b;
// C# は改行を無視するので行継続不要
[DllImport("user32.dll")]
static extern int MessageBoxW(
IntPtr hWnd, string text,
string caption, int type);
int result = 1 + 2 +
3 + 4;
; を使いますが、NHSP でも同じです。: は HSP と同じく文の区切りです。\ による行継続も HSP 互換です。; が必須ですが、NHSP では不要です (改行が区切り)。
| ディレクティブ | 説明 | C# 相当 |
|---|---|---|
#assembly "Name" [, exe] | 出力アセンブリ名・種別 | [assembly: AssemblyTitle("Name")] |
#reference "Lib.dll" | 参照アセンブリ追加 | csc /r:Lib.dll |
#namespace "NS" | 名前空間開始 | namespace NS { |
#endnamespace | 名前空間終了 | } |
#class [修飾子] Name [: Base, IFace] | クラス定義開始 | public class Name : Base, IFace { |
#endclass | クラス定義終了 | } |
#interface Name | インターフェース定義 | public interface Name { |
#endinterface | インターフェース終了 | } |
#func [修飾子] [戻型] Name [, params] | メソッド定義 | public RetType Name(params) { |
#endfunc | メソッド終了 | } |
#init [params] | コンストラクタ | public Name(params) { |
#endinit | コンストラクタ終了 | } |
#field [修飾子] Type Name | フィールド | public Type Name; |
#property Name as Type | プロパティ | public Type Name { get; } |
#main / #endmain | エントリポイント | static void Main() { |
#dllimport "dll名" | DLL 宣言 (以降の #dllfunc に適用) | [DllImport("dll名")] |
#dllfunc [修飾子] [戻型] Name [, params] | P/Invoke 関数宣言 (本体なし) | static extern RetType Name(params); |
#struct [修飾子] Name [, Layout] [, Pack=N] [, Size=N] [, CharSet=X] | 構造体 (値型) 定義 | [StructLayout(...)] public struct Name { |
#endstruct | 構造体終了 | } |
#delegate [修飾子] [戻型] Name [, params] | デリゲート型定義 | public delegate RetType Name(params); |
#enum Name ... #endenum | 列挙型定義 | public enum Name { ... } |
#include "file.nhsp" | ファイルのインクルード | C# には直接相当なし (partial class 等) |
#event [修飾子] DelegateType Name | イベント定義 | public event EventHandler Name; |
#indexer [修飾子] Type [, params] | インデクサ定義 | public Type this[params] { } |
#operator RetType Op, params | 演算子オーバーロード | public static RetType operator Op(params) |
#init static ... #endinit | 静的コンストラクタ | static ClassName() { } |
#destructor ... #enddestructor | デストラクタ (ファイナライザ) | ~ClassName() { } |
#attribute Name, "value" | カスタム属性付与 | [Name("value")] |
| アセンブリメタデータ / Win32 リソース | ||
#version "1.2.3.4" | AssemblyVersion (CLR バージョン) | [assembly: AssemblyVersion("1.2.3.4")] |
#fileversion "1.2.3.4" | AssemblyFileVersion (Win32 VERSIONINFO の FileVersion) | [assembly: AssemblyFileVersion("1.2.3.4")] |
#infoversion "1.2.3-rc1" | AssemblyInformationalVersion (Win32 VERSIONINFO の ProductVersion) | [assembly: AssemblyInformationalVersion("1.2.3-rc1")] |
#title "App Name" | AssemblyTitle (FileDescription) | [assembly: AssemblyTitle("App Name")] |
#description "..." | AssemblyDescription | [assembly: AssemblyDescription("...")] |
#company "ACME" | AssemblyCompany (CompanyName) | [assembly: AssemblyCompany("ACME")] |
#product "MyApp" | AssemblyProduct (ProductName) | [assembly: AssemblyProduct("MyApp")] |
#copyright "(c) 2026" | AssemblyCopyright (LegalCopyright) | [assembly: AssemblyCopyright("(c) 2026")] |
#trademark "..." | AssemblyTrademark (LegalTrademarks) | [assembly: AssemblyTrademark("...")] |
#icon "logo.png" | Win32 アプリケーションアイコン (ico/png/bmp/gif/jpg/jpeg/tif/tiff) | csc /win32icon:logo.ico |
#manifest "app.manifest" | Win32 マニフェスト埋め込み (RT_MANIFEST) | csc /win32manifest:app.manifest |
メタデータディレクティブを 1 つでも書くと nhspc が Win32 VERSIONINFO リソースを自動生成します。
エクスプローラーで EXE/DLL を右クリック → プロパティ → 詳細タブに表示される情報になります。
#icon は .ico をそのまま埋め込むか、PNG/BMP/GIF/JPEG/TIFF を渡した場合は内部で
16/32/48/256 サイズの PNG エントリを持つ Vista+ 形式 .ico に自動変換します (要 GDI+)。
| NHSP | .NET (CLR) | サイズ | リテラル例 |
|---|---|---|---|
int | System.Int32 | 4 byte | 42, -1 |
int64 / long | System.Int64 | 8 byte | |
double | System.Double | 8 byte | 3.14 |
float | System.Single | 4 byte | |
string | System.String | 参照 | "Hello" |
bool | System.Boolean | 1 byte | true, false |
byte | System.Byte | 1 byte | |
sbyte | System.SByte | 1 byte | |
short | System.Int16 | 2 byte | |
ushort | System.UInt16 | 2 byte | |
uint | System.UInt32 | 4 byte | |
ulong | System.UInt64 | 8 byte | |
char | System.Char | 2 byte | |
IntPtr | System.IntPtr | 4/8 byte | ポインタサイズ |
UIntPtr | System.UIntPtr | 4/8 byte | ポインタサイズ |
object / var | System.Object | 参照 |
; dim を使った宣言 (NHSP 従来形式)
dim int x = 42
dim string name = "Hello"
dim double pi = 3.14
; dim 省略形 (C# 風 — 型名から始める)
int x2 = 100
string msg = "World"
double rate = 0.5
bool flag = true
; 型推論 (代入値から自動判定)
dim count = 100 ; int
dim text = "abc" ; string
; 配列
dim int[] arr, 10 ; int 配列 (要素数 10)
string[] names, 5 ; dim 省略も OK
; ジェネリクス型
List<int> nums = new List<int>()
; Nullable
int? maybe = 42 ; null も入れられる
; 16進数リテラル
int hex1 = 0xFF ; C 形式
int hex2 = $FF ; HSP 形式 (同じ値)
; null リテラル (C# / C++ どちらの書き方も OK)
string s1 = null ; C# 形式
string s2 = nullptr ; C++ 形式 (同じ動作)
string safe = s1 ?? "default" ; null なら "default"
// C# の変数宣言
int x = 42;
string name = "Hello";
double pi = 3.14;
int x2 = 100;
string msg = "World";
double rate = 0.5;
bool flag = true;
var count = 100;
var text = "abc";
int[] arr = new int[10];
string[] names = new string[5];
List<int> nums = new List<int>();
int? maybe = 42;
int hex1 = 0xFF;
string s1 = null;
// nullptr は C# にはない (NHSP独自)
string safe = s1 ?? "default";
dim は省略可能。int x = 42 と dim int x = 42 は同じ意味。dim なし、HSP ユーザーは dim 付きが馴染みやすい。どちらの書き方も混在可能。StringBuilder, StreamReader 等) もフルネーム不要で使用可能 (主要な名前空間を自動検索)。
x = 42 は int、s = "text" は string と推論。dim で明示指定も可能。
#class public Person
#field public string Name
#field private int _age
#init string name, int age
Name = name
_age = age
#endinit
#func public string GetInfo
return Name + " (" + str(_age) + ")"
#endfunc
#endclass
public class Person
{
public string Name;
private int _age;
public Person(string name, int age)
{
Name = name;
_age = age;
}
public string GetInfo()
{
return Name + " (" + _age + ")";
}
}
#struct で値型 (struct) を定義できます。P/Invoke でネイティブ構造体を渡す際に必要です。StructLayoutAttribute の全パラメータに対応。
#struct [修飾子] 名前 [, LayoutKind] [, Pack = N] [, Size = N] [, CharSet = X]
#field [修飾子] 型 フィールド名
...
#endstruct
| パラメータ | 値 | 説明 |
|---|---|---|
| LayoutKind | Sequential (既定), Explicit, Auto | フィールドのメモリ配置方式 |
| Pack | 1, 2, 4, 8, 16, 32, 64, 128 | パッキングアラインメント (バイト単位) |
| Size | 整数 | 構造体の明示的な合計サイズ (バイト) |
| CharSet | Ansi, Unicode, Auto, None | 文字列マーシャリングの文字セット |
#struct public SYSTEMTIME, Sequential, Pack = 2
#field public ushort wYear
#field public ushort wMonth
#field public ushort wDayOfWeek
#field public ushort wDay
#field public ushort wHour
#field public ushort wMinute
#field public ushort wSecond
#field public ushort wMilliseconds
#endstruct
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
#struct public Union, Explicit
[FieldOffset 0] #field public int IntValue
[FieldOffset 0] #field public float FloatValue
#endstruct
[StructLayout(LayoutKind.Explicit)]
public struct Union
{
[FieldOffset(0)] public int IntValue;
[FieldOffset(0)] public float FloatValue;
}
#struct public WinStruct, Sequential, CharSet = Unicode
#field public int Id
[MarshalAs ByValTStr, 256] #field public string Name
#endstruct
System.ValueType を継承する値型です。スタック上に配置され、代入時にコピーされます。
P/Invoke で Win32 API にネイティブ構造体を渡す場合に使います。
#delegate でデリゲート型を定義できます。コールバック関数の型や P/Invoke の関数ポインタに使用します。
#delegate [修飾子] [戻り値型] デリゲート名 [, 型 引数名, ...]
; 戻り値 int、引数 2つの演算デリゲート
#delegate public int BinaryOp, int a, int b
; void デリゲート (イベントハンドラ)
#delegate public void EventHandler, object sender, string message
public delegate int BinaryOp(int a, int b);
public delegate void EventHandler(
object sender, string message);
MulticastDelegate を継承し、Invoke, BeginInvoke, EndInvoke メソッドが自動生成されます。
#func [修飾子...] [戻り値型] メソッド名 [, 型 引数名, 型 引数名 ...]
; 本体
#endfunc
| 修飾子 | 説明 |
|---|---|
public | 外部から呼び出し可能 |
private | クラス内部のみ |
static | インスタンス不要 |
virtual | 派生クラスでオーバーライド可能 |
override | 基底クラスのメソッドをオーバーライド |
#func public static int Max, int a, int b
if a > b { return a }
return b
#endfunc
public static int Max(int a, int b)
{
if (a > b) return a;
return b;
}
メソッドの引数に ref (参照渡し) や out (出力引数) を指定できます。呼び出し元の変数を直接変更します。
; ref: 呼び出し元の変数を読み書き
#func public static void AddTen, ref int value
value = value + 10
#endfunc
; out: 呼び出し元に値を返す
#func public static bool TryParse, string s, out int result
result = 42
return true
#endfunc
public static void AddTen(ref int value)
{
value = value + 10;
}
public static bool TryParse(
string s, out int result)
{
result = 42;
return true;
}
ref は IL レベルで Type.MakeByRefType() + Ldarg/Ldind/Stind を使用。
out は ref と同じ ByRef 型に加えて ParameterAttributes.Out を設定。
#field public int Width
#field public int Height
#property Area as int, public
#get
return Width * Height
#endget
#endproperty
public int Width;
public int Height;
public int Area
{
get { return Width * Height; }
}
| NHSP | C# | 説明 |
|---|---|---|
#field public int X | public int X; | 通常のインスタンスフィールド |
#field public static int Count | public static int Count; | 静的フィールド (クラスに1つ) |
#field public const int MAX = 260 | public const int MAX = 260; | コンパイル時定数 (リテラル値が直接埋め込まれる) |
#field public readonly int Id | public readonly int Id; | コンストラクタでのみ代入可能な読み取り専用フィールド |
#class public Config
#field public const int MAX_PATH = 260
#field public const string VERSION = "1.0"
#field public readonly int MaxSize
#init int size
MaxSize = size
#endinit
#endclass
public class Config
{
public const int MAX_PATH = 260;
public const string VERSION = "1.0";
public readonly int MaxSize;
public Config(int size)
{
MaxSize = size;
}
}
const は FieldAttributes.Literal で IL レベルのコンパイル時定数。readonly は FieldAttributes.InitOnly でコンストラクタ内のみ代入可。
; デフォルト (省略可)
#init
X = 0
#endinit
; 引数付き
#init int x, int y
X = x
Y = y
#endinit
public Point()
{
X = 0;
}
public Point(int x, int y)
{
X = x;
Y = y;
}
#interface IShape
#func int Area
#endinterface
#class public Circle : IShape
#field public int Radius
#init int r
Radius = r
#endinit
#func public int Area
return Radius * Radius * 3
#endfunc
#endclass
public interface IShape
{
int Area();
}
public class Circle : IShape
{
public int Radius;
public Circle(int r) { Radius = r; }
public int Area()
{
return Radius * Radius * 3;
}
}
| NHSP | C# | 備考 |
|---|---|---|
if cond { } else { } | if (cond) { } else { } | 条件の括弧不要。= は == |
repeat N ... loop | for(int cnt=0;cnt<N;cnt++){} | cnt が自動カウンタ |
for i = S to E [step N] ... next | for(int i=S;i<=E;i+=N){} | |
while cond ... wend | while(cond){} | |
foreach var in collection ... next | foreach(var x in col){} | IEnumerable 対応 |
switch ... case ... endswitch | switch(x){ case N: } | 自動 break |
break | break; | |
continue | continue; |
; ブレース形式
if x > 10 {
print "big"
} elseif x > 5 {
print "medium"
} else {
print "small"
}
; endif 形式 (HSP 互換)
if x = 1
print "one"
elseif x = 2
print "two"
else
print "other"
endif
if (x > 10)
{
Console.WriteLine("big");
}
else if (x > 5)
{
Console.WriteLine("medium");
}
else
{
Console.WriteLine("small");
}
// HSP互換: = は == として扱われる
// (条件式中のみ)
; 5回繰り返し (cnt は自動カウンタ)
repeat 5
print "cnt = " + str(cnt)
loop
; 出力: cnt = 0, 1, 2, 3, 4
; 無限ループ
repeat
if cnt > 99
break
endif
loop
for (int cnt = 0; cnt < 5; cnt++)
{
Console.WriteLine($"cnt = {cnt}");
}
// 無限ループ
for (int cnt = 0; ; cnt++)
{
if (cnt > 99) break;
}
; 1 から 10 まで
for i = 1 to 10
print str(i)
next
; ステップ指定 (2 刻み)
for i = 0 to 100 step 2
print str(i)
next
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(i);
}
for (int i = 0; i <= 100; i += 2)
{
Console.WriteLine(i);
}
dim int x = 0
while x < 10
x++
wend
print str(x) ; => 10
int x = 0;
while (x < 10)
{
x++;
}
Console.WriteLine(x); // 10
; 基本形
try
dim int result = a / b
return result
catch Exception ex
return -1
finally
print "cleanup"
#endtry
try
{
int result = a / b;
return result;
}
catch (Exception ex)
{
return -1;
}
finally
{
Console.WriteLine("cleanup");
}
return は自動的に IL の Leave 命令を使い、正しく動作します (Phase 17 で修正済み)。
try
dim int result = int("abc")
catch FormatException e
print "書式エラー: " + e.Message
catch OverflowException e2
print "オーバーフロー: " + e2.Message
catch Exception e3
print "その他: " + e3.Message
#endtry
try
{
int result = int.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine("書式エラー: " + e.Message);
}
catch (OverflowException e2)
{
Console.WriteLine("オーバーフロー: " + e2.Message);
}
catch (Exception e3)
{
Console.WriteLine("その他: " + e3.Message);
}
; 新しい例外を投げる
throw new ArgumentException("invalid value")
; catch 内で再送出
try
; ...
catch Exception e
throw ; 同じ例外をそのまま再送出
#endtry
throw new ArgumentException("invalid value");
try { /* ... */ }
catch (Exception e)
{
throw; // rethrow
}
System.Threading の API を利用してマルチスレッドプログラミングが可能です。
; 100ミリ秒待機
sleep 100
Thread.Sleep(100);
#class public Counter
#field private int _count
#field private object _lock
#init
_count = 0
_lock = new object()
#endinit
#func public void Increment
lock _lock
_count += 1
endlock
#endfunc
#func public int GetCount
return _count
#endfunc
#endclass
public class Counter
{
private int _count;
private object _lock;
public Counter()
{
_count = 0;
_lock = new object();
}
public void Increment()
{
lock (_lock)
{
_count += 1;
}
}
public int GetCount() => _count;
}
lock は IL レベルで Monitor.Enter / Monitor.Exit を try/finally で囲んで生成。this や new object() 等)。
; スレッドで並列に処理を実行
#assembly "ThreadDemo", exe
#reference "System.dll"
#class public Worker
#func public static void DoWork
print "Worker start"
sleep 500
print "Worker done"
#endfunc
#endclass
#main
; ThreadStart デリゲートを作成して Thread に渡す
dim ThreadStart ts = new ThreadStart(Worker.DoWork)
dim Thread t = new Thread(ts)
t.Start()
print "Main thread"
t.Join()
print "All done"
#endmain
using System.Threading;
class Worker
{
public static void DoWork()
{
Console.WriteLine("Worker start");
Thread.Sleep(500);
Console.WriteLine("Worker done");
}
}
class Program
{
static void Main()
{
var t = new Thread(Worker.DoWork);
t.Start();
Console.WriteLine("Main thread");
t.Join();
Console.WriteLine("All done");
}
}
#class public ThreadExample
#func public static void Run
; 現在のスレッド情報
dim Thread current = Thread.CurrentThread
print "ThreadId: " + str(current.ManagedThreadId)
print "Name: " + current.Name
print "IsBackground: " + str(current.IsBackground)
; バックグラウンドスレッド作成
dim ThreadStart ts = new ThreadStart(Worker)
dim Thread bg = new Thread(ts)
bg.Name = "MyWorker"
bg.IsBackground = true ; メインスレッド終了時に自動停止
bg.Priority = ThreadPriority.BelowNormal
bg.Start()
bg.Join() ; 完了待ち
#endfunc
#func public static void Worker
dim Thread me = Thread.CurrentThread
print "Worker on: " + me.Name
sleep 100
#endfunc
#endclass
class ThreadExample
{
static void Run()
{
var current = Thread.CurrentThread;
Console.WriteLine(
$"ThreadId: {current.ManagedThreadId}");
Console.WriteLine($"Name: {current.Name}");
Console.WriteLine(
$"IsBackground: {current.IsBackground}");
var bg = new Thread(Worker);
bg.Name = "MyWorker";
bg.IsBackground = true;
bg.Priority = ThreadPriority.BelowNormal;
bg.Start();
bg.Join();
}
static void Worker()
{
var me = Thread.CurrentThread;
Console.WriteLine($"Worker on: {me.Name}");
Thread.Sleep(100);
}
}
#class public ParamThread
#func public static void PrintN, object arg
dim int n = int(arg.ToString())
repeat n
print "Count: " + str(cnt)
sleep 50
loop
#endfunc
#func public static void Run
dim ParameterizedThreadStart pts = new ParameterizedThreadStart(PrintN)
dim Thread t1 = new Thread(pts)
t1.Start(5) ; 引数 5 を渡す
dim Thread t2 = new Thread(pts)
t2.Start(3) ; 引数 3 を渡す
t1.Join()
t2.Join()
print "Both done"
#endfunc
#endclass
class ParamThread
{
static void PrintN(object arg)
{
int n = (int)arg;
for (int i = 0; i < n; i++)
{
Console.WriteLine($"Count: {i}");
Thread.Sleep(50);
}
}
static void Run()
{
var t1 = new Thread(PrintN);
t1.Start(5);
var t2 = new Thread(PrintN);
t2.Start(3);
t1.Join();
t2.Join();
Console.WriteLine("Both done");
}
}
#class public PoolExample
#func public static void Work, object state
dim string name = state.ToString()
print "Start: " + name
sleep 200
print "End: " + name
#endfunc
#func public static void Run
; スレッドプールにジョブを投入
dim WaitCallback cb = new WaitCallback(Work)
ThreadPool.QueueUserWorkItem(cb, "Job1")
ThreadPool.QueueUserWorkItem(cb, "Job2")
ThreadPool.QueueUserWorkItem(cb, "Job3")
; 全ジョブ完了を待つ (簡易)
sleep 1000
print "All jobs done"
#endfunc
#endclass
class PoolExample
{
static void Work(object state)
{
string name = (string)state;
Console.WriteLine($"Start: {name}");
Thread.Sleep(200);
Console.WriteLine($"End: {name}");
}
static void Run()
{
ThreadPool.QueueUserWorkItem(Work, "Job1");
ThreadPool.QueueUserWorkItem(Work, "Job2");
ThreadPool.QueueUserWorkItem(Work, "Job3");
Thread.Sleep(1000);
Console.WriteLine("All jobs done");
}
}
; Task.Run でバックグラウンド処理
#assembly "TaskDemo", exe
#reference "System.dll"
#class public Downloader
#func public static string Fetch, string url
; 重い処理のシミュレーション
sleep 1000
return "Data from " + url
#endfunc
#endclass
#main
; Task.Run で非同期実行
dim Task task1 = Task.Run(new Action(Downloader.Fetch))
print "Waiting..."
task1.Wait()
print "Done"
#endmain
using System.Threading.Tasks;
class Downloader
{
public static string Fetch(string url)
{
Thread.Sleep(1000);
return "Data from " + url;
}
}
class Program
{
static void Main()
{
Task task1 = Task.Run(() =>
Downloader.Fetch("http://example.com"));
Console.WriteLine("Waiting...");
task1.Wait();
Console.WriteLine("Done");
}
}
Task.Wait() や Task.Result で同期的に待機する方法を使います。Task.Run にはデリゲート / メソッド参照を渡します。
; 二重起動防止
#assembly "MutexDemo", exe
#reference "System.dll"
#main
dim bool createdNew
dim Mutex mutex = new Mutex(true, "MyAppMutex")
; Mutex.WaitOne で取得を試みる
if mutex.WaitOne(0)
print "アプリ起動"
sleep 3000
mutex.ReleaseMutex()
else
print "既に起動済みです"
endif
#endmain
using System.Threading;
class Program
{
static void Main()
{
bool createdNew;
var mutex = new Mutex(true,
"MyAppMutex", out createdNew);
if (mutex.WaitOne(0))
{
Console.WriteLine("アプリ起動");
Thread.Sleep(3000);
mutex.ReleaseMutex();
}
else
{
Console.WriteLine("既に起動済みです");
}
}
}
; lock で List への追加を保護
#class public SafeList
#field private List<int> _items
#field private object _sync
#init
_items = new List<int>()
_sync = new object()
#endinit
#func public void Add, int item
lock _sync
_items.Add(item)
endlock
#endfunc
#func public int Count
lock _sync
return _items.Count
endlock
return 0
#endfunc
#endclass
public class SafeList
{
private List<int> _items = new();
private object _sync = new();
public void Add(int item)
{
lock (_sync)
{
_items.Add(item);
}
}
public int Count
{
get
{
lock (_sync)
{
return _items.Count;
}
}
}
}
; 1秒ごとにコールバック
#assembly "TimerDemo", exe
#reference "System.dll"
#class public App
#func public static void OnTick, object state
print "Tick: " + DateTime.Now.ToString("HH:mm:ss")
#endfunc
#endclass
#main
dim TimerCallback cb = new TimerCallback(App.OnTick)
dim Timer timer = new Timer(cb, 0, 0, 1000)
print "Timer started (5秒後に停止)"
sleep 5000
timer.Dispose()
print "Timer stopped"
#endmain
using System.Threading;
class App
{
static void OnTick(object state)
{
Console.WriteLine(
$"Tick: {DateTime.Now:HH:mm:ss}");
}
}
class Program
{
static void Main()
{
var timer = new Timer(
App.OnTick, null, 0, 1000);
Console.WriteLine(
"Timer started (5秒後に停止)");
Thread.Sleep(5000);
timer.Dispose();
Console.WriteLine("Timer stopped");
}
}
System.Threading.Timer はスレッドプールで定期実行。System.Timers.Timer はイベントベースで、#event と組み合わせて使用可能。TimerCallback, ThreadStart 等) を渡します。
#dllimport ディレクティブで Win32 API やネイティブ DLL の関数を直接呼び出せます。DefinePInvokeMethod で IL レベルの P/Invoke を生成。
#class public NativeApi
#dllimport "user32.dll"
#dllfunc public static int MessageBoxA, int hWnd, string text, string caption, int type
#dllimport "kernel32.dll"
#dllfunc public static int GetTickCount
#dllfunc public static int GetCurrentProcessId
#endclass
public class NativeApi
{
[DllImport("user32.dll")]
public static extern int MessageBoxA(
int hWnd, string text,
string caption, int type);
[DllImport("kernel32.dll")]
public static extern int GetTickCount();
[DllImport("kernel32.dll")]
public static extern int GetCurrentProcessId();
}
#dllimport で DLL 名を宣言し、#dllfunc で関数を列挙します。#dllfunc は本体を持たず、#endfunc は不要です。同じ DLL の関数は #dllimport 1回で複数宣言可能。
#dllimport には文字セットや呼び出し規約などのオプションを指定できます。
#dllimport "DLL名", CharSet = Unicode, SetLastError = true, CallingConvention = Cdecl, ExactSpelling = true
| オプション | 値 | 既定値 | 説明 |
|---|---|---|---|
CharSet | Ansi, Unicode, Auto, None | Auto | 文字列パラメータのマーシャリング方式 |
CallingConvention | StdCall, Cdecl, ThisCall, FastCall, Winapi | StdCall | 呼び出し規約 (C ランタイムは Cdecl) |
SetLastError | true, false | false | Win32 の SetLastError を保持する |
ExactSpelling | true, false | false | A/W サフィックスの自動検索を無効化 |
#dllimport "user32.dll", CharSet = Unicode, SetLastError = true
#dllfunc public static int MessageBoxW, IntPtr hWnd, string text, string caption, int type
[DllImport("user32.dll",
CharSet = CharSet.Unicode,
SetLastError = true)]
public static extern int MessageBoxW(
IntPtr hWnd, string text,
string caption, int type);
#dllimport "msvcrt.dll", CallingConvention = Cdecl
#dllfunc public static int puts, string str
#dllimport "kernel32.dll"
#dllfunc public static void GetSystemTime, ref SYSTEMTIME st
[DllImport("kernel32.dll")]
public static extern void GetSystemTime(
ref SYSTEMTIME st);
メソッド名の後に文字列リテラルを書くと、DLL 内の実際のエクスポート名を指定できます。
; 関数名の別名指定
#dllimport "user32.dll", CharSet = Unicode
#dllfunc public static int MsgBox "MessageBoxW", IntPtr hWnd, string text, string caption, int type
; 序数指定 (#123 形式)
#dllimport "mydll.dll"
#dllfunc public static int MyFunc "#123"
[DllImport("user32.dll",
CharSet = CharSet.Unicode,
EntryPoint = "MessageBoxW")]
public static extern int MsgBox(
IntPtr hWnd, string text,
string caption, int type);
[DllImport("mydll.dll",
EntryPoint = "#123")]
public static extern int MyFunc();
"#123") は DLL のエクスポート番号で呼び出すレガシー機能です。#func MessageBoxA "MessageBoxA" int, sptr, sptr, int と同じ考え方です。
実行時の型チェック (is) と安全なキャスト (as) をサポートします。
; 型チェック
if obj is string
print "It's a string"
endif
; 安全なキャスト (失敗時は null)
dim string s = obj as string
dim string result = s ?? "not a string"
if (obj is string)
Console.WriteLine("It's a string");
string s = obj as string;
string result = s ?? "not a string";
is は IL の Isinst + null チェック。as は Isinst のみ (null を返す)。
参照型にのみ使用可能。値型の as は使用不可。
#event でイベントを定義します。内部的にデリゲートフィールド + add/remove メソッドが自動生成されます。
#class public Button
#event public EventHandler Click
#endclass
public class Button
{
public event EventHandler Click;
}
DefineEvent + Delegate.Combine/Delegate.Remove パターンで add/remove メソッドを生成。
イベントの型はデリゲート型 (EventHandler, Action 等) を指定。
#indexer でインデクサ (添字アクセス) を定義します。C# の this[int index] に相当。
#indexer public int, int index
#get
return index * 10
#endget
#set
; value でセットされる値を受け取る
#endset
#endindexer
public int this[int index]
{
get { return index * 10; }
set { /* value */ }
}
DefineProperty("Item", ...) + get_Item/set_Item メソッドを生成。
#operator で演算子のオーバーロードを定義します。C# の operator + 等に相当。
#operator 戻り値型 演算子記号, 型 引数名 [, 型 引数名]
; 本体
#endoperator
#class public Vector
#field public int X
#field public int Y
#operator Vector +, Vector a, Vector b
dim Vector r = new Vector()
r.X = a.X + b.X
r.Y = a.Y + b.Y
return r
#endoperator
#operator bool ==, Vector a, Vector b
return a.X == b.X && a.Y == b.Y
#endoperator
#endclass
public class Vector {
public int X, Y;
public static Vector operator +(
Vector a, Vector b)
{
return new Vector {
X = a.X + b.X,
Y = a.Y + b.Y };
}
public static bool operator ==(
Vector a, Vector b)
{
return a.X == b.X &&
a.Y == b.Y;
}
}
| 演算子 | CLR メソッド名 | パラメータ数 |
|---|---|---|
+ | op_Addition | 2 |
- | op_Subtraction | 2 |
* | op_Multiply | 2 |
/ | op_Division | 2 |
% | op_Modulus | 2 |
== | op_Equality | 2 |
!= | op_Inequality | 2 |
< / > | op_LessThan / op_GreaterThan | 2 |
implicit | op_Implicit | 1 (変換元) |
explicit | op_Explicit | 1 (変換元) |
クラスの内部に別のクラスを定義できます。外部からは Outer+Inner (CLR) または Outer.Inner で参照。
#class public Outer
#class public Inner
#func public static int Value
return 42
#endfunc
#endclass
#endclass
public class Outer
{
public class Inner
{
public static int Value()
=> 42;
}
}
TypeBuilder.DefineNestedType で生成。TypeAttributes.NestedPublic が設定されます。
.NET の既存のジェネリクス型 (List<T>, Dictionary<K,V> 等) を利用できます。
; ジェネリクス型の利用
dim List<int> nums = new List<int>()
dim Dictionary<string,string> map = new Dictionary<string,string>()
dim HashSet<int> set = new HashSet<int>()
| NHSP | .NET 型 |
|---|---|
List<T> | System.Collections.Generic.List<T> |
Dictionary<K,V> | System.Collections.Generic.Dictionary<K,V> |
HashSet<T> | System.Collections.Generic.HashSet<T> |
Queue<T> | System.Collections.Generic.Queue<T> |
Stack<T> | System.Collections.Generic.Stack<T> |
Nullable<T> | System.Nullable<T> |
Action<T> | System.Action<T> |
Func<T,R> | System.Func<T,TResult> |
Task<T> | System.Threading.Tasks.Task<T> |
#class public MyBox<T>) は現在未対応。.NET 標準ライブラリのジェネリクス型の利用のみサポート。クラスが最初に使われるときに1回だけ実行される初期化コード。static フィールドの初期値設定に使います。
#class public Config
#field public static int DefaultValue
#init static
DefaultValue = 42
#endinit
#endclass
public class Config
{
public static int DefaultValue;
static Config()
{
DefaultValue = 42;
}
}
.cctor (TypeInitializer) として生成。MethodAttributes.Static | RTSpecialName | SpecialName。オブジェクトが GC に回収される前に実行されるクリーンアップコード。
#class public Resource
#destructor
; リソース解放処理
#enddestructor
#endclass
public class Resource
{
~Resource()
{
// リソース解放処理
}
}
Finalize() メソッドの override として生成。try { 本体 } finally { base.Finalize(); } パターン。using 文 + IDisposable を推奨。
値型に ? を付けて null を許容する型にできます。
; int? は Nullable<int> と同等
#func public static bool HasValue, int? val
return val.HasValue
#endfunc
; double? も使える
dim double? price = 19.99
public static bool HasValue(int? val)
{
return val.HasValue;
}
double? price = 19.99;
int? は内部的に Nullable<int>(System.Nullable`1[System.Int32])に変換。.HasValue / .Value プロパティ、?? 演算子と組み合わせて使用可能。int[][] (配列の配列) もサポート。
DateTime などの値型 (struct) のプロパティやメソッドも正しく呼び出せます。
; DateTime (値型) のプロパティ
dim DateTime now = DateTime.Now
int y = now.Year
int m = now.Month
int d = now.Day
; メソッド呼び出し
string text = now.ToString("yyyy-MM-dd")
; メソッドチェーン
string time = DateTime.Now.ToString("HH:mm:ss")
print $"Date: {str(y)}-{str(m)}-{str(d)}"
print $"Time: {time}"
DateTime now = DateTime.Now;
int y = now.Year;
int m = now.Month;
int d = now.Day;
string text = now.ToString("yyyy-MM-dd");
string time = DateTime.Now.ToString("HH:mm:ss");
Console.WriteLine($"Date: {y}-{m}-{d}");
Console.WriteLine($"Time: {time}");
Ldloca (アドレス取得) + Call を使用。
メソッドチェーン (DateTime.Now.ToString()) は中間結果を一時変数に格納して処理。
P/Invoke のパラメータや構造体のフィールドに [MarshalAs] 属性を付けて、マネージド型とアンマネージド型の変換方式を指定できます。
; フルネーム: [MarshalAs 型名]
#dllfunc public static int GetModuleFileName, IntPtr hModule, [MarshalAs LPStr] string buffer, int size
; ショートカット: [型名] だけでも MarshalAs として適用される
#dllfunc public static int GetModuleFileName, IntPtr hModule, [LPStr] string buffer, int size
; 固定長文字列: [MarshalAs ByValTStr, サイズ]
[MarshalAs ByValTStr, 260] #field public string Path
; 固定長配列: [MarshalAs ByValArray, サイズ]
[MarshalAs ByValArray, 4] #field public byte Reserved
| 名前 | UnmanagedType | 説明 |
|---|---|---|
Bool | UnmanagedType.Bool | Win32 BOOL (4 byte) |
I1 / U1 | .I1 / .U1 | 符号付/符号なし 1 byte |
I2 / U2 | .I2 / .U2 | 符号付/符号なし 2 byte |
I4 / U4 | .I4 / .U4 | 符号付/符号なし 4 byte |
I8 / U8 | .I8 / .U8 | 符号付/符号なし 8 byte |
R4 / R8 | .R4 / .R8 | float / double |
LPStr | .LPStr | ANSI char* (null終端) |
LPWStr | .LPWStr | Unicode wchar_t* (null終端) |
LPTStr | .LPTStr | TCHAR* (プラットフォーム依存) |
BStr | .BStr | COM BSTR |
ByValTStr | .ByValTStr | 構造体内の固定長文字列 (SizeConst 必須) |
ByValArray | .ByValArray | 構造体内の固定長配列 (SizeConst 必須) |
LPStruct | .LPStruct | 構造体ポインタ |
Struct | .Struct | 構造体 (値渡し) |
Interface | .Interface | COM インターフェースポインタ |
IUnknown | .IUnknown | IUnknown* |
IDispatch | .IDispatch | IDispatch* |
FunctionPtr | .FunctionPtr | 関数ポインタ (デリゲート → native) |
SysInt / SysUInt | .SysInt / .SysUInt | ネイティブ整数 (IntPtr) |
SafeArray | .SafeArray | COM SAFEARRAY |
AsAny | .AsAny | 実行時型推論 |
Error | .Error | HRESULT |
; GetWindowText の宣言
; バッファに LPStr で文字列を受け取る
#struct public RECT, Sequential
#field public int Left
#field public int Top
#field public int Right
#field public int Bottom
#endstruct
#class public WinApi
#dllimport "user32.dll", CharSet = Unicode
#dllfunc public static int GetWindowTextW, IntPtr hWnd, [LPWStr] string buffer, int maxCount
#dllfunc public static int GetWindowRect, IntPtr hWnd, ref RECT rect
#dllimport "kernel32.dll"
#dllfunc public static int GetModuleFileNameA "GetModuleFileNameA", IntPtr hModule, [LPStr] string filename, int size
#endclass
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
}
public class WinApi
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowTextW(
IntPtr hWnd,
[MarshalAs(UnmanagedType.LPWStr)] string buffer,
int maxCount);
[DllImport("user32.dll")]
public static extern int GetWindowRect(
IntPtr hWnd, ref RECT rect);
[DllImport("kernel32.dll", EntryPoint = "GetModuleFileNameA")]
public static extern int GetModuleFileNameA(
IntPtr hModule,
[MarshalAs(UnmanagedType.LPStr)] string filename,
int size);
}
#attribute ディレクティブで .NET の COM 関連カスタム属性を付与できます。COM 相互運用コンポーネントを NHSP で作成可能。
| NHSP | C# 相当 | 説明 |
|---|---|---|
#attribute Guid, "..." | [Guid("...")] | COM GUID の設定 |
#attribute ComVisible, "true" | [ComVisible(true)] | COM から可視にする |
#attribute ClassInterface, "None" | [ClassInterface(ClassInterfaceType.None)] | クラスインターフェースの種別 |
#attribute InterfaceType, "InterfaceIsIDispatch" | [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] | インターフェースの COM 種別 |
#attribute ComSourceInterfaces, "型名" | [ComSourceInterfaces(typeof(型))] | イベントソースインターフェース |
#assembly "MyComLib"
; COM インターフェース
#attribute Guid, "EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F"
#interface ComClass1Interface
#func string Hello
#endinterface
; イベントインターフェース
#attribute Guid, "7BD20046-DF8C-44A6-8F6B-687FAA26FA71"
#attribute InterfaceType, "InterfaceIsIDispatch"
#interface ComClass1Events
#endinterface
; COM クラス本体
#attribute Guid, "0D53A3E8-E51A-49C7-944E-E72A2064F938"
#attribute ClassInterface, "None"
#attribute ComVisible, "true"
#class public ComClass1 : ComClass1Interface
#func public string Hello
return "Hello COM!"
#endfunc
#endclass
using System.Runtime.InteropServices;
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface ComClass1Interface
{
string Hello();
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ComClass1Events
{
}
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComVisible(true)]
public class ComClass1 : ComClass1Interface
{
public string Hello()
{
return "Hello COM!";
}
}
#enum で列挙型を定義します。値の指定がない場合は自動的にインクリメントされます。
#enum [修飾子] 列挙名
メンバ名 [= 値]
メンバ名 [= 値]
...
#endenum
#enum Color
Red
Green
Blue
#endenum
#enum Priority
Low = 1
Medium = 5
High
Critical = 100
#endenum
public enum Color
{
Red, // 0
Green, // 1
Blue // 2
}
public enum Priority
{
Low = 1,
Medium = 5,
High, // 6 (自動)
Critical = 100
}
ModuleBuilder.DefineEnum + DefineLiteral で生成。
値による分岐処理。C# の switch 文に相当します。
switch 式
case 値1
; 処理
case 値2
; 処理
default
; どれにも一致しない場合
endswitch
switch x
case 1
return "one"
case 2
return "two"
default
return "other"
endswitch
switch (x)
{
case 1: return "one";
case 2: return "two";
default: return "other";
}
Ceq + Brfalse の if-else チェーンで生成。
IEnumerable を実装するコレクションを反復処理します。
foreach [型] 変数名 in コレクション式
; 本体
next
foreach string item in list
print item
next
foreach (string item in list)
{
Console.WriteLine(item);
}
GetEnumerator() → MoveNext() / Current のパターンを生成。
IDisposable を実装する列挙子は自動的に Dispose() が呼ばれます。using 文でリソースのスコープを管理します。ブロック終了時に自動的に Dispose() が呼ばれます。
using 変数名 = 初期化式
; 本体
endusing
using reader = new StreamReader("file.txt")
dim string line = reader.ReadLine()
print line
endusing
using (var reader =
new StreamReader("file.txt"))
{
string line = reader.ReadLine();
Console.WriteLine(line);
}
try { 本体 } finally { if (var != null) var.Dispose(); } パターンを生成。
| 優先度 | 種別 | 演算子 |
|---|---|---|
| 1 | 単項 | - ! ~ (ビット反転) |
| 2 | 乗除余 | * / % |
| 3 | 加減 | + - |
| 4 | シフト | << (左シフト) >> (右シフト) |
| 5 | 比較 | < > <= >= |
| 6 | 等値 | == != (= も == と同等) |
| 7 | ビット AND | & |
| 8 | ビット XOR | ^ |
| 9 | ビット OR | | |
| 10 | 論理 AND | && / and (短絡評価) |
| 11 | 論理 OR | || / or (短絡評価) |
| 12 | null 合体 | ?? |
| 13 | 三項 | 条件 ? 真 : 偽 |
| 演算子 | 説明 | IL |
|---|---|---|
= | 代入 | Stloc / Stfld |
+= -= *= /= | 算術複合代入 | Add / Sub / Mul / Div |
&= |= ^= | ビット複合代入 | And / Or / Xor |
++ -- | インクリメント / デクリメント | Ldloc + Ldc_I4_1 + Add/Sub + Stloc |
| 演算子 | 説明 | 例 |
|---|---|---|
?? | null 合体 (左が null なら右を返す) | s ?? "default" |
? : | 三項演算子 (条件分岐式) | a > b ? a : b |
typeof(型) | 型の Type オブジェクトを取得 | typeof(int) → System.Type |
$"...{式}..." | 文字列補間 (変数・関数・式 全対応) | $"x={str(x)}, len={s.Length}" |
; 算術
dim int x = 10 + 3 * 2 ; 16 (*が先)
dim int mod = 10 % 3 ; 1
; ビット演算
dim int flags = $FF & $0F ; 0x0F (AND)
dim int mask = 1 << 4 ; 16 (左シフト)
dim int inv = ~$FF ; ビット反転
; 比較 (= は == と同等)
if x = 16
print "equal"
endif
; 三項演算子
dim int bigger = a > b ? a : b
; null 合体
dim string name = input ?? "anonymous"
; 論理演算 (短絡評価)
if x > 0 && y > 0
print "both positive"
endif
; is / as
if obj is string
dim string s = obj as string
endif
; 文字列補間
print $"x = {x}, name = {name}"
int x = 10 + 3 * 2; // 16
int mod = 10 % 3; // 1
int flags = 0xFF & 0x0F; // AND
int mask = 1 << 4; // 16
int inv = ~0xFF; // NOT
if (x == 16)
Console.WriteLine("equal");
int bigger = a > b ? a : b;
string name = input ?? "anonymous";
if (x > 0 && y > 0)
Console.WriteLine("both positive");
if (obj is string)
{
string s = obj as string;
}
Console.WriteLine($"x = {x}, name = {name}");
| 名前 | 説明 | C# 相当 |
|---|---|---|
print expr | コンソール出力 | Console.WriteLine(expr) |
str(x) | 文字列変換 | x.ToString() |
int(s) | 整数パース | int.Parse(s) |
double(s) | 実数パース | double.Parse(s) |
sleep ms | スレッド停止 | Thread.Sleep(ms) |
new var, Type(args) | オブジェクト生成 (文形式) | var x = new Type(args) |
dim var = new Type() | オブジェクト生成 (dim 形式) | var x = new Type() |
typeof(型) | Type オブジェクト取得 | typeof(int) |
$"...{式}..." | 文字列補間 (変数・関数呼び出し・プロパティ・式 全対応) | $"...{expr}..." |
x++ / x-- | インクリメント / デクリメント | x++; / x--; |
; 型変換
dim string s = str(42) ; "42"
dim int n = int("123") ; 123
dim double d = double("3.14") ; 3.14
; コンソール出力
print "Hello" ; Hello
print 42 ; 42
print "x = " + str(x) ; x = 10
; 文字列補間 (変数・関数・プロパティ・式に対応)
dim string name = "World"
int x = 42
print $"Hello {name}!" ; Hello World!
print $"x = {str(x)}" ; x = 42
print $"len = {name.Length}" ; len = 5
print $"{str(x)} * 2 = {str(x*2)}" ; 42 * 2 = 84
; オブジェクト生成 (2つの書き方)
new sb, StringBuilder() ; 文形式
dim StringBuilder sb2 = new StringBuilder() ; dim形式
; typeof
dim Type t = typeof(int)
print t.FullName ; System.Int32
string s = 42.ToString();
int n = int.Parse("123");
double d = double.Parse("3.14");
Console.WriteLine("Hello");
Console.WriteLine(42);
Console.WriteLine("x = " + x);
string name = "World";
Console.WriteLine($"Hello {name}!");
var sb = new StringBuilder();
var sb2 = new StringBuilder();
Type t = typeof(int);
Console.WriteLine(t.FullName);
#include ディレクティブでソースファイルを分割できます。指定されたファイルの内容がその位置に挿入されます。
#include "ファイル名"
#assembly "MyApp", exe
#include "classes.nhsp"
#include "utils.nhsp"
#main
new calc, Calculator
print calc.Add(1, 2)
#endmain
#class public Calculator
#func public int Add, int a, int b
return a + b
#endfunc
#endclass
#include はコンパイル前のプリプロセス段階で処理されます。指定パスはソースファイルからの相対パスです。
ネストした #include は現在サポートされていません。
全ての .NET Framework 4.8 API にアクセス可能。静的メソッドは ClassName.Method()、インスタンスメソッドは obj.Method() で呼び出し。
dim string s = "Hello World"
dim int len = s.Length ; 11
dim string upper = s.ToUpper() ; "HELLO WORLD"
dim bool has = s.Contains("World") ; true
dim string sub = s.Substring(0, 5) ; "Hello"
dim string rep = s.Replace("World", "NHSP")
dim string[] parts = s.Split(" ")
string s = "Hello World";
int len = s.Length;
string upper = s.ToUpper();
bool has = s.Contains("World");
string sub = s.Substring(0, 5);
string rep = s.Replace("World", "NHSP");
string[] parts = s.Split(' ');
dim int bigger = Math.Max(10, 20) ; 20
dim int abs = Math.Abs(-5) ; 5
dim double sqrt = Math.Sqrt(2.0) ; 1.414...
dim double pi = Math.PI ; 3.14159...
int bigger = Math.Max(10, 20);
int abs = Math.Abs(-5);
double sqrt = Math.Sqrt(2.0);
double pi = Math.PI;
; 出力 (print は Console.WriteLine のショートカット)
print "Hello"
Console.Write("no newline")
; 入力
dim string input = Console.ReadLine()
dim int key = Console.Read()
Console.WriteLine("Hello");
Console.Write("no newline");
string input = Console.ReadLine();
int key = Console.Read();
dim DateTime now = DateTime.Now
print now.ToString("yyyy-MM-dd HH:mm:ss")
dim int year = now.Year
dim int month = now.Month
var now = DateTime.Now;
Console.WriteLine(
now.ToString("yyyy-MM-dd HH:mm:ss"));
int year = now.Year;
int month = now.Month;
#reference "System.dll"
; ファイル読み書き
File.WriteAllText("test.txt", "Hello")
dim string content = File.ReadAllText("test.txt")
dim bool exists = File.Exists("test.txt")
; パス操作
dim string dir = Path.GetDirectoryName("C:\\temp\\file.txt")
dim string ext = Path.GetExtension("image.png")
File.WriteAllText("test.txt", "Hello");
string content = File.ReadAllText("test.txt");
bool exists = File.Exists("test.txt");
string dir = Path.GetDirectoryName(
@"C:\temp\file.txt");
string ext = Path.GetExtension("image.png");
; リスト
dim List<int> nums = new List<int>()
nums.Add(1)
nums.Add(2)
nums.Add(3)
print str(nums.Count) ; 3
; ディクショナリ
dim Dictionary<string,int> map = new Dictionary<string,int>()
map.Add("apple", 100)
map.Add("banana", 200)
var nums = new List<int>();
nums.Add(1);
nums.Add(2);
nums.Add(3);
Console.WriteLine(nums.Count);
var map = new Dictionary<string, int>();
map.Add("apple", 100);
map.Add("banana", 200);
; 環境情報
print Environment.MachineName
print Environment.OSVersion.ToString()
print Environment.CurrentDirectory
; プロセス起動
Process.Start("notepad.exe")
Console.WriteLine(Environment.MachineName);
Console.WriteLine(Environment.OSVersion);
Console.WriteLine(Environment.CurrentDirectory);
Process.Start("notepad.exe");
nhspc.exe <input.nhsp> [options]
<input.nhsp> : ソースファイル
-o <file> : 出力ファイルパス (省略時: アセンブリ名.exe / .dll)
-debug : PDB デバッグ情報を生成
-platform <plat> : ターゲットプラットフォーム
anycpu (既定) — AnyCPU
x86 — 32bit 専用
x64 — 64bit 専用
anycpu32 — AnyCPU + 32bit 優先
-target <kind> : 出力種別を強制
exe — 強制的に EXE
dll — 強制的に DLL
(省略時は #main の有無で自動判定)
-subsystem <sub> : EXE のサブシステム
console (既定) — コンソール窓を表示
windows — GUI アプリ用 (コンソール窓なし)
-r <assembly> : 参照アセンブリを追加 (複数指定可)
-reference <assembly> : -r の別名
-win32icon <file> : アプリケーションアイコンを埋め込む
ico/png/bmp/gif/jpg/jpeg/tif/tiff に対応
(ico 以外は内部で .ico に自動変換)
REM AnyCPU 既定 + メタデータはソース側 (#version 等) で指定
nhspc.exe app.nhsp
REM x64 専用 + デバッグ情報
nhspc.exe app.nhsp -platform x64 -debug
REM WinForms GUI アプリ (コンソール窓なし)
nhspc.exe gui.nhsp -subsystem windows -win32icon logo.png
REM ライブラリ参照を CLI から追加
nhspc.exe app.nhsp -r System.Xml.dll -r System.Drawing.dll
以下の設定はソース側ディレクティブ (#icon / #reference / #manifest 等) と CLI オプション
(-win32icon / -r) のどちらでも指定できます。両方指定された場合、ソース側が優先 (CLI は不足分のみ追加) される
ものと、CLI が上書きするものがあるので注意してください。
- #icon が指定されている場合: -win32icon は無視
- #reference と -r: 両方の集合がマージされる
- -target: #main 自動判定や #assembly "...", exe を上書きする
nhspc -debug を付けてビルドすると、生成 EXE/DLL と並んで .pdb
ファイルが出力されます。これは Windows PDB 形式 (フル PDB) で、
System.Reflection.Emit がデフォルトで生成する形式です。
Visual Studio や dnSpy はこの Windows PDB をそのまま読めますが、 VS Code (.NET ランタイム coreclr デバッガ) や Rider のクロスプラット フォームデバッガは Portable PDB 形式しか受け付けません。 そのため nhspc には変換ツールが同梱されています。
Pdb2PortablePdb <assembly.exe|dll> [-o <output.pdb>]
<assembly> : 対象アセンブリ (隣接する同名 .pdb を読み込む)
-o <output> : 出力先 .pdb (省略時は元の .pdb を上書き)
内部的には Microsoft.DiaSymReader.Tools の PdbConverter を使って
Windows PDB → Portable PDB の変換を行います。アセンブリ本体は変更されません
(MVID と PDB の対応関係はそのまま維持されます)。
VS Code 拡張から NHSP Debug を起動すると、コンパイル直後に自動的に
Pdb2PortablePdb.exe が呼ばれ、生成された Windows PDB がその場で Portable
PDB に変換されてから、coreclr デバッガが起動されます。手動で変換する必要は
ありません。ブレークポイント、ステップ実行、変数ウォッチがそのまま使えます。
| シナリオ | 必要な PDB 形式 | nhspc から見た手順 |
|---|---|---|
| Visual Studio (Windows) でデバッグ | Windows PDB | nhspc -debug のみで OK (変換不要) |
| dnSpy / dnSpyEx でデバッグ | Windows PDB | nhspc -debug のみで OK |
| VS Code (C# / coreclr) でデバッグ | Portable PDB | vscode-nhsp 拡張が自動変換 (手動なら Pdb2PortablePdb) |
| Rider (.NET) でデバッグ | Portable PDB 推奨 | Pdb2PortablePdb で変換 |
| 本番リリース (デバッグなし) | 不要 | -debug を付けない |
System.Reflection.Emit の DefineDocument / ISymbolWriter 系 API は Windows PDB
専用です。Portable PDB を直接生成するには System.Reflection.Metadata.MetadataBuilder
を使った別系統の IL/メタデータビルダーが必要で、AssemblyBuilder ベースの現行
コードからは差し替えコストが大きいため、現状は「フル PDB を出して必要なら
変換」という二段構えにしています。
nhspc のリポジトリには Visual Studio 2022 用の TextMate ベース構文ハイライト拡張
nhsp-language.vsix が同梱されています。インストールすると .nhsp ファイルが
NHSP 用のシンタックスハイライトで開けるようになり、デバッグも追加設定なしで使えます。
もっとも簡単な手順は install.bat を実行することです。VSIX が未ビルドなら自動でビルドします。
ABC 全フェーズを 1 コマンドでセットアップできます:
install.bat [/q] [/home [path]] [/openfolder <project-dir>] [/noinstall]
| フラグ | 説明 |
|---|---|
/q | サイレント (VSIXInstaller の GUI 確認ダイアログを出さない / 既存テンプレも問答無用で上書き) |
/home [path] | Phase B 用に NHSPC_HOME ユーザー環境変数を setx で永続設定。パス省略時は nhspc\bin\Release や dist 等を自動検出 |
/openfolder <dir> | Phase B テンプレ (launch.vs.json / tasks.vs.json) を <dir>\.vs\ にコピー |
/noinstall | VSIXInstaller 呼び出しをスキップ。VSIX は既に入っていて /home や /openfolder だけ実行したい時に便利 |
REM Phase A + C のみ (VSIX をインストール、確認ダイアログあり)
install.bat
REM Phase A + C のみ (サイレント)
install.bat /q
REM Phase A + B + C 全部入り (NHSPC_HOME を自動検出し、project に templates 配置)
install.bat /home /openfolder C:\projects\myapp
REM サイレント + 明示パス指定
install.bat /q /home C:\tools\nhspc /openfolder C:\projects\myapp
REM 既に VSIX 入れた後で別プロジェクトに Open Folder テンプレだけ追加
install.bat /noinstall /openfolder C:\projects\anotherapp
REM アンインストール
nhspc\vs2022-nhsp\uninstall.bat
REM サイレントアンインストール
nhspc\vs2022-nhsp\uninstall.bat /q
install.bat は vswhere.exe で VS 2022 を検出し、VSIXInstaller.exe を呼び出します。
インストール時は VS 2022 を 事前に終了させておく 必要があります (起動中だとインストールがキューイングされて
次回 VS 起動時まで反映されません)。
手動でビルドだけしたい場合や、ZIP の中身を見たい場合は build.bat を直接呼んでください:
nhspc\vs2022-nhsp\build.bat REM nhsp-language.vsix を生成のみ
VSIXInstaller がはじいた場合は、nhsp-language.vsix の中身 (zip として開く) を次の場所に手動で展開して
VS を再起動するフォールバックもあります (17.0_xxxx のサフィックスはインスタンスごとに異なります):
%LocalAppData%\Microsoft\VisualStudio\17.0_xxxx\Extensions\IronHSP\NhspLanguage\
nhspc が出力する Windows PDB は VS 2022 のデバッガが第一級でサポートする形式なので、 拡張機能を入れなくてもデバッグ自体は動きます。手順:
nhspc app.nhsp -o app.exe -debug で EXE と PDB を生成app.exe を選択app.exe を右クリック → Set as Startup Projectapp.nhsp を VS 内で開いて行頭マージンをクリックしてブレークポイントを置くDefineDocument に
.nhsp ファイルの絶対パスを書き込んでいるため、VS デバッガはソース行情報から
直接 .nhsp を開けます。VS 2022 が .nhsp という拡張子を「言語」として知らなくても、
デバッガはソース行のマップに拡張子を見ないので問題なく動きます。Locals ウィンドウに
出る変数名・型は nhspc が PDB の LocalScope に書き込んだものがそのまま表示されます。
プロジェクトファイルなしで .nhsp を直接編集 → F5 でビルド → デバッグしたい場合は、
VS 2022 の Open Folder モードに tasks.vs.json と
launch.vs.json を置く方法があります (VS Code の launch.json / tasks.json と同じノリ)。
nhspc/vs2022-nhsp/openfolder-templates/ にテンプレートを同梱しています。次の手順でセットアップしてください:
NHSPC_HOME に設定 (例: setx NHSPC_HOME C:\tools\nhspc)C:\projects\myapp) に .vs サブフォルダを作る.vs\ にコピー: tasks.vs.json / launch.vs.jsonapp.exe) → 入力するとビルドタスクが走り、デバッガが起動テンプレが提供するタスク:
| taskLabel | 説明 |
|---|---|
NHSP: Build (Debug) | nhspc -debug で AnyCPU ビルド (BP / Locals 用 PDB 付き) |
NHSP: Build (Release) | -debug なしの AnyCPU ビルド |
NHSP: Build x64 (Debug) | -platform x64 -debug で 64bit 専用ビルド |
タスクは Solution Explorer の .nhsp ファイルを右クリック → 任意のタスクを実行で個別に呼ぶこともできます。 ビルドだけしたい (デバッガを立ち上げたくない) ときに便利です。
tasks.vs.json の
args 配列を直接編集してください。${file} / ${fileDirname} /
${fileBasenameNoExtension} / ${workspaceRoot} / ${env.X} などの
プレースホルダが使えます。
| 機能 | VSIX なし | VSIX あり |
|---|---|---|
| シンタックスハイライト | 白黒 | キーワード/型/文字列/コメントが色分け |
| ファイル拡張子の関連付け | 「不明な拡張子」扱い | NHSP として開く |
| デバッグ (BP / Locals / Step) | 動く | 動く (変わらず) |
| IntelliSense / 補完 | なし | なし (LSP の補完機能は将来追加予定) |
| エラー波線 (リアルタイム) | なし | VS Code 経由で nhspls.exe を使えば可 (下記 LSP セクション参照) |
nhspls.exe は nhspc のレキサー / パーサーをそのまま流用した
Language Server Protocol 実装です。エディタが文書の変更を通知してくると、
nhspls がリアルタイムで構文解析を回して publishDiagnostics で赤波線を返します。
コンパイル (IL Emit) は走らないので軽量で、保存しなくてもタイプ中にエラー位置が出ます。
| LSP メソッド | 動作 |
|---|---|
initialize / initialized | Capabilities ハンドシェイク (textDocumentSync = Full) |
textDocument/didOpen / didChange / didClose | 文書ストアに lex+parse 結果をキャッシュ → 各機能で共有 |
textDocument/publishDiagnostics | パースで集めた DiagnosticBag を LSP 形式で送出 (リアルタイム赤波線) |
textDocument/documentSymbol | アウトライン: クラス → メソッド/フィールド/プロパティ/イベント の階層、インターフェース、enum、delegate |
textDocument/completion | # 後はディレクティブ一覧、それ以外はキーワード + 型エイリアス + ユーザー定義クラス/インターフェース |
textDocument/hover | カーソル下のトークン種別を判定: 型エイリアスは CLR 名、キーワードは説明、識別子はクラス/メソッド/フィールドのシグネチャ |
textDocument/definition | カーソル下の識別子をユニット内のクラス/メソッド/フィールド/プロパティ/インターフェース/enum/delegate と照合してジャンプ先を返す |
shutdown / exit | クリーン終了 |
未実装 (将来予定):
obj. のあとの候補表示) — 型推論が必要( 入力時のパラメータヒント)
vscode-nhsp 拡張は起動時に nhspls.exe を子プロセスとして spawn し、開いている .nhsp
すべてに対して didOpen/didChange を流します。設定:
| 設定キー | 既定値 | 説明 |
|---|---|---|
nhsp.languageServer.enabled | true | LSP を有効にするか。false にすると compile-on-save の診断のみになる |
nhsp.languageServerPath | (空) | nhspls.exe のパス。空なら拡張同梱版 → ワークスペース内のビルド出力 の順に自動検出 |
nhsp-language.vsix には MEF コンポーネント
NhspVsLanguageClient.dll が同梱されており、VS 2022 で .nhsp ファイルを開くと
自動的に nhspls.exe が子プロセスとして spawn されます。VS 2022 の
Microsoft.VisualStudio.LanguageServer.Client API が LSP プロトコルを駆動するので、以下が動きます:
| 機能 | VS 2022 での見え方 |
|---|---|
| 診断 (publishDiagnostics) | エラー リスト + コード ウィンドウの赤波線 |
| documentSymbol | ナビゲーションバー (上部のドロップダウン) と「コード マップ」 |
| completion | Ctrl+Space で候補表示、# でディレクティブ補完 |
| hover | 識別子にマウスホバーで型/シグネチャ ツールチップ |
| definition | F12 / Go to Definition で #class / #func へジャンプ |
ビルド方法: nhspc\vs2022-nhsp\build.bat を実行すると NhspVsLanguageClient と
NhspLanguageServer をリリースビルドし、必要な DLL/EXE を全部 zip して nhsp-language.vsix を作ります。
出来上がった VSIX をダブルクリックでインストール → VS 2022 を再起動 → .nhsp ファイルを開けば動き出します。
nhspls.exe は単体で起動して JSON-RPC で対話できます。デバッグ用にスクリプトから叩く例:
; Python から (Content-Length ヘッダ + JSON ボディで送る)
import subprocess, json
def msg(m):
body = json.dumps(m).encode()
return f"Content-Length: {len(body)}\r\n\r\n".encode() + body
p = subprocess.Popen([r"...\nhspls.exe"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(msg({"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"processId":0,"rootUri":None,"capabilities":{}}}))
p.stdin.flush()
; ... read framed response from p.stdout
| 制限 | 回避策 |
|---|---|
| メソッド名に HSP 予約語 (repeat, double 等) は使えない | 別の名前を使用 (例: Double → Twice) |
| 複数クラスの new で一部 AccessViolation | 同一クラス内からの参照、または1対1のクロス参照のみ |
| ジェネリクス型の利用で一部 StackOverflow | 複雑なネストを避ける |
| catch when (例外フィルタ) の IL 生成未実装 | 構文は認識されるが、when 条件は実行時に無視される。catch 内で if 分岐で代替 |
| 機能 | C# 例 | 難易度 | 回避策 |
|---|---|---|---|
| ジェネリクス型定義 | class Box<T> { T Value; } | 難 | 既存の .NET ジェネリクス型 (List<int> 等) の利用は対応済み。独自ジェネリクスクラスの定義のみ未対応。C# ヘルパー DLL で定義して参照する方法で代替可能 |
| yield return (イテレータ) | yield return x; | 難 | ステートマシンクラスの自動生成が必要。List<T> を構築して返すことで代替 |
| 多次元配列 | int[,] matrix | 中 | ジャグ配列 int[][] は対応済み。真の多次元配列は Array.CreateInstance で代替 |
| 機能 | C# 例 | 難易度 | 回避策 |
|---|---|---|---|
| async / await | async Task<int> M() { await ...; } | 超難 | ステートマシン + AsyncTaskMethodBuilder の完全生成が必要。Thread / Task.Run で代替 |
| ラムダ式 | (x) => x * 2 | 難 | クロージャクラスの自動生成 + キャプチャ変数の管理が必要。#delegate 型定義は対応済み。通常のメソッド参照で代替 |
| LINQ クエリ構文 | from x in list where x > 5 select x | 難 | メソッドチェーン (.Where().Select()) への構文変換が必要。直接メソッドチェーンを記述して代替 |
| パターンマッチング | x is int i and > 0 | 難 | C# 7+ の高度なパターン。is / as + if 分岐で代替 |
| 拡張メソッド | static void M(this string s) | 中 | メソッド解決の仕組み変更が必要。通常の static メソッドとして定義・呼び出しで代替 |
| レコード型 | record Person(string Name) | 中 | Equals/GetHashCode/ToString の自動生成が必要。通常のクラスで手動実装して代替 |
| タプル | (int, string) pair = (1, "a") | 中 | ValueTuple<T1,T2> + 分解代入が必要。クラスやフィールドで代替 |
| dynamic 型 | dynamic obj = ...; | 超難 | DLR (CallSite / Binder) が必要。var (Object) + リフレクションで代替 |
| unsafe / ポインタ | int* p = &x; | 中 | P/Invoke + IntPtr で代替 |
| カテゴリ | 機能 |
|---|---|
| 型定義 | class, interface, struct, enum, delegate, sealed, abstract, nested class |
| メンバー | field (const/readonly/static), method (virtual/override/abstract), constructor, static constructor, destructor, property, event, indexer, operator overload |
| パラメータ | ref, out, params, default value, [MarshalAs], [In]/[Out] |
| 制御構文 | if/elseif/else, repeat/loop, while/wend, for/next, foreach/next, switch/case, break, continue |
| 例外処理 | try/catch (複数)/finally, throw, catch with type filter, Leave 自動対応 |
| 演算子 | 算術 (+,-,*,/,%), 比較 (==,!=,<,>,<=,>=), 論理 (&&,||,!), ビット (&,|,^,~,<<,>>), 三項 (?:), null合体 (??), is, as, typeof, ++/-- |
| 型 | int, int64, double, float, string, bool, byte, sbyte, short, ushort, uint, ulong, char, IntPtr, UIntPtr, int? (Nullable), int[] (配列), int[][] (ジャグ配列), List<T> (ジェネリクス利用) |
| 文 | dim, new, print, sleep, lock, using, return, throw |
| その他 | $"補間文字列 (変数・関数・式対応)", 16進数 ($FF/0xFF), #namespace, #include, #dllimport/#dllfunc (EntryPoint/CharSet/CallingConvention/SetLastError), COM属性, PDB デバッグ情報 (Windows PDB + Pdb2PortablePdb で Portable PDB へ変換可), コロン区切り (:), 行継続 (\), dim 省略形, null/nullptr, 値型メンバーアクセス (DateTime 等) |