HSP風の書きやすさで .NET アセンブリを作ろう
NHSP (Net HSP) は、HSP に似た文法で .NET のクラスライブラリ (DLL) や実行ファイル (EXE) を作れるプログラミング言語です。
#func / #class のディレクティブ形式-platform でターゲットアーキを切替可能#icon (PNG/JPG/BMP/GIF も OK) や #version 等で EXE のメタ情報を埋め込み-debug オプションで PDB を生成、Visual Studio や dnSpy でブレークポイント設定可能#assembly "HelloLib"
#class public Greeter
#func public string Hello, string name
return "Hello, " + name + "!"
#endfunc
#endclass
public class Greeter
{
public string Hello(string name)
{
return "Hello, " + name + "!";
}
}
nhspc.exe と NhspCompiler.Core.dll を同じフォルダに置くだけです。インストール不要。
テキストエディタで hello.nhsp を作成:
; hello.nhsp - 最初のプログラム
#assembly "HelloLib"
#class public Hello
#func public static string SayHello
return "Hello from NHSP!"
#endfunc
#endclass
nhspc.exe hello.nhsp -o HelloLib.dll
-debug オプションを付けると、デバッグ用の PDB ファイルも生成されます:nhspc.exe hello.nhsp -o HelloLib.dll -debugnhspc -debug が出力するのは
Windows PDB 形式で、Visual Studio や dnSpy ではそのまま使えますが、
VS Code (.NET) のデバッガは Portable PDB しか読めません。
そのため nhspc には Pdb2PortablePdb.exe という変換ツールが付属しており、
VS Code 拡張 (vscode-nhsp) を使えばコンパイル時に自動で変換されます。
詳細は リファレンスの PDB セクション を参照。app.exe を VS で開いて F5 するだけでブレークポイント / Locals / コールスタックが
動きます。シンタックスハイライト用に同梱の nhsp-language.vsix も合わせて
インストールするのが推奨です。詳細は
リファレンスの Visual Studio 2022 セクション を参照。
; HSP3 スクリプト
loadnet "HelloLib.dll", 2
newnet p, "HelloLib", "Hello", 1
netres r
mcall p, "SayHello"
mes nettoval(r, 2) ; => "Hello from NHSP!"
; これはコメント(HSP スタイル)
// これもコメント(C スタイル)
; dim を使った宣言
dim int x = 42
dim string name = "Taro"
; dim 省略形 (C# と同じ書き方)
int y = 100
string msg = "Hello"
; 型推論(最初の代入で型が決まる)
count = 100 ; int 型
text = "abc" ; string 型
; 配列
dim int arr, 10
arr(0) = 100
arr(1) = 200
; null (空っぽ) を入れる
string s = null ; C# 形式
string s2 = nullptr ; C++ 形式 (同じ動作)
; null チェック
string name = s ?? "名無し" ; s が null なら "名無し"
; HSP と同じ!コロンで1行に複数文が書ける
int a = 1 : int b = 2 : int c = a + b
; 長い行は \ で分割できる(HSP 互換)
#dllfunc public static int MessageBoxW, \
IntPtr hWnd, string text, \
string caption, int type
: (文区切り) と \ (行継続) がそのまま使えます。C# には同じ機能がありません。
| NHSP | 説明 | 例 |
|---|---|---|
int | 32bit 整数 | 42 |
double | 倍精度小数 | 3.14 |
string | 文字列 | "Hello" |
bool | 真偽値 | true / false |
int64 | 64bit 整数 | 9999999999 |
; 算術: + - * / %
result = 10 + 3 * 2 ; => 16 (掛け算が先)
; 比較: == != < > <= >=
; HSP互換: = も == と同じ
if x = 5 { ... } ; x == 5 と同じ
; 論理: && || !
if x > 0 && x < 10 { ... }
; 複合代入: += -= *= /=
x += 5
; 数値 → 文字列
s = str(42) ; => "42"
; 文字列 → 数値
n = int("123") ; => 123
; 文字列連結 (+ で自動変換)
msg = "値は" + str(x)
#class public Calculator
#func public int Add, int a, int b
return a + b
#endfunc
#func public int Multiply, int a, int b
return a * b
#endfunc
#endclass
#class public Counter
#field public int Count
#func public Increment
Count += 1
#endfunc
#func public int GetCount
return Count
#endfunc
#endclass
#class public Person
#field public string Name
#field public int Age
; コンストラクタ: オブジェクト作成時に呼ばれる
#init string name, int age
Name = name
Age = age
#endinit
#func public string Greet
return "I'm " + Name + ", age " + str(Age)
#endfunc
#endclass
#class public Rectangle
#field public int Width
#field public int Height
#init int w, int h
Width = w
Height = h
#endinit
#property Area as int, public
#get
return Width * Height
#endget
#endproperty
#endclass
インスタンスを作らなくても呼べるメソッドです。
#class public MathUtil
#func public static int Max, int a, int b
if a > b {
return a
}
return b
#endfunc
#endclass
if score >= 80 {
print "優"
} elseif score >= 60 {
print "良"
} else {
print "不可"
}
; cnt がカウンター(0 から開始)
repeat 5
print str(cnt) ; 0, 1, 2, 3, 4
loop
for i = 1 to 10
print str(i) ; 1, 2, ..., 10
next
; step 指定
for i = 0 to 100 step 10
print str(i) ; 0, 10, 20, ..., 100
next
dim int i
i = 10
while i > 0
print str(i)
i -= 1
wend
repeat 100
if cnt = 50 { break } ; ループを抜ける
if cnt % 2 = 0 { continue } ; 偶数はスキップ
print str(cnt)
loop
; 配列の宣言(10個分)
dim int scores, 10
; 値の代入
scores(0) = 85
scores(1) = 92
scores(2) = 78
; ループで合計
dim int total
total = 0
for i = 0 to 2
total += scores(i)
next
print "合計: " + str(total)
#class public Animal
#field public string Name
#init string name
Name = name
#endinit
#func public virtual string Speak
return Name + " says ..."
#endfunc
#endclass
; Dog は Animal を継承
#class public Dog : Animal
#init string name
Name = name
#endinit
#func public override string Speak
return Name + " says Woof!"
#endfunc
#endclass
; 「何ができるか」を定義
#interface IDrawable
#func Draw
#endinterface
; インターフェースを実装
#class public Circle : IDrawable
#func public Draw
print "Drawing a circle"
#endfunc
#endclass
; String のメソッド
dim string s
s = "Hello World"
print str(s.Length) ; => 11
print s.ToUpper() ; => "HELLO WORLD"
print s.Contains("World") ; => True
; Math クラス
dim int max
max = Math.Max(10, 20) ; => 20
print str(Math.Abs(-42)) ; => 42
dim int result
result = 0
try
result = 10 / 0 ; ゼロ除算!
catch ex
result = -1 ; エラー時の処理
endtry
print str(result) ; => -1
; app.nhsp
#assembly "MyApp"
#class public Greeter
#func public static string Hello, string name
return "Hello, " + name + "!"
#endfunc
#endclass
#main
print Greeter.Hello("World")
#endmain
nhspc.exe app.nhsp -o MyApp.exe
作った EXE のエクスプローラー上のアイコンや、右クリック → プロパティの「詳細」タブに出るバージョン情報・会社名・著作権なども ソース内でディレクティブとして指定できます。
#assembly "MyApp"
; --- バージョン情報 (右クリックプロパティに出る) ---
#version "1.0.0.0"
#fileversion "1.0.0.0"
#title "私のアプリ"
#description "ボタンを押すと挨拶するアプリ"
#company "ACME 株式会社"
#product "MyApp"
#copyright "(c) 2026 ACME"
; --- アイコン (.ico はもちろん、.png/.bmp/.gif/.jpg もそのまま渡せる) ---
#icon "logo.png"
#main
print "Hello"
#endmain
#icon に .ico を渡せばそのまま埋め込み、PNG / BMP / GIF / JPEG / TIFF を渡すと
nhspc が内部で 16 / 32 / 48 / 256 ピクセルの 4 サイズに変換した .ico を自動生成して埋め込みます。
わざわざ .ico を作る必要はありません。
WinForms などの GUI アプリで EXE をダブルクリックすると、デフォルトでは後ろに黒いコンソール窓が出てしまいます。
これを抑制するには -subsystem windows オプションを使います。
nhspc.exe gui_app.nhsp -o GuiApp.exe -subsystem windows
既定の AnyCPU は実行マシンに合わせて自動的に 32bit / 64bit が選ばれます。
特定のアーキテクチャに固定したい (例: 32bit DLL を P/Invoke する) 場合は -platform を使います。
nhspc.exe app.nhsp -platform x86 REM 32bit 専用
nhspc.exe app.nhsp -platform x64 REM 64bit 専用
nhspc.exe app.nhsp -platform anycpu32 REM AnyCPU + 32bit 優先
Windows の DLL にある関数を直接呼び出すことができます。HSP の #uselib / #func に相当します。
#class public WinApi
; user32.dll の関数を宣言
#dllimport "user32.dll"
#dllfunc public static int MessageBoxA, int hWnd, string text, string caption, int type
#dllfunc public static int MessageBoxW, int hWnd, string text, string caption, int type
; kernel32.dll の関数を宣言
#dllimport "kernel32.dll"
#dllfunc public static int GetTickCount
#dllfunc public static int GetCurrentProcessId
#endclass
; DLL の関数を宣言
#dllimport "user32.dll"
#dllfunc public static int MessageBoxA, int hWnd, string text, string caption, int type
; DLL の関数を宣言
#uselib "user32.dll"
#func MessageBoxA "MessageBoxA" int, sptr, sptr, int
#dllimport で DLL 名を宣言し、#dllfunc で関数を列挙します。
#dllfunc は宣言だけなので #endfunc は不要。
同じ DLL の関数は #dllimport 1回で複数宣言できます。
#dllimport にオプションを指定できます。
; Unicode 版の API を呼ぶ
#dllimport "user32.dll", CharSet = Unicode
#dllfunc public static int MessageBoxW, IntPtr hWnd, string text, string caption, int type
; C ランタイム関数は Cdecl で呼ぶ
#dllimport "msvcrt.dll", CallingConvention = Cdecl
#dllfunc public static int puts, string str
引数に [MarshalAs] を付けて文字列の変換方式を指定することもできます。
; 引数の文字列を ANSI 文字として渡す
#dllfunc public static int GetModuleFileName, IntPtr hModule, [LPStr] string buffer, int size
COM 相互運用コンポーネントも作れます。#attribute で GUID やインターフェースの種類を指定します。
; COM インターフェース
#attribute Guid, "EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F"
#interface IMyComponent
#func string GetMessage
#endinterface
; COM クラス
#attribute Guid, "0D53A3E8-E51A-49C7-944E-E72A2064F938"
#attribute ClassInterface, "None"
#attribute ComVisible, "true"
#class public MyComponent : IMyComponent
#func public string GetMessage
return "Hello from NHSP COM!"
#endfunc
#endclass
regasm.exe で COM 登録すれば、VBScript や VBA など COM 対応言語から利用できます。
NHSP でコンパイルした DLL は、IronHSP の loadnet で読み込めます。
; HSP3 スクリプト
loadnet "MyLib.dll", 2
newnet p, "MyLib", "Calculator", 0
netres r
mcall p, "Add", 10, 20
mes "結果: " + nettoval(r, 4) ; => 結果: 30
構造体は「値型」と呼ばれるデータの入れ物です。Win32 API にデータを渡すときによく使います。HSP の構造体変数に近い考え方です。
; 2D座標を表す構造体
#struct public Point2D, Sequential
#field public int X
#field public int Y
#endstruct
C# では struct キーワードで書くものが、NHSP では #struct ... #endstruct になります。
Windows API で使う構造体はパッキング (アラインメント) の指定が必要な場合があります。
; SYSTEMTIME 構造体 (Pack = 2 でパッキング指定)
#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
; 使い方: P/Invoke と組み合わせる
#class public TimeUtil
#dllimport "kernel32.dll"
#dllfunc public static void GetSystemTime, ref SYSTEMTIME st
#endclass
ushort は符号なし16ビット整数。他にも uint, ulong, sbyte, char などの型が使えます。
IntPtr はポインタサイズの整数で、ハンドル (HWND, HANDLE) を渡すときに使います。
同じメモリ位置に違う型でアクセスしたい場合は Explicit レイアウトと [FieldOffset] を使います。
#struct public NumberUnion, Explicit
[FieldOffset 0] #field public int IntValue
[FieldOffset 0] #field public float FloatValue
#endstruct
固定長の文字列フィールドには [MarshalAs] で長さを指定します。
#struct public FileInfo, Sequential, CharSet = Unicode
#field public int Id
[MarshalAs ByValTStr, 260] #field public string Path
#endstruct
通常メソッドは return で1つの値しか返せませんが、ref や out を使うと引数経由で複数の値を返せます。
ref は「呼び出し元の変数を直接書き換える」意味です。
#class public MathUtil
; ref で渡した変数を直接変更
#func public static void AddTen, ref int value
value = value + 10
#endfunc
; 2つの変数を入れ替え
#func public static void Swap, ref int a, ref int b
dim int temp
temp = a
a = b
b = temp
#endfunc
#endclass
out は「メソッド内で必ず値を設定して返す」意味です。TryParse パターンでよく使います。
#class public Parser
; 文字列を数値に変換、成功/失敗を return で返し、結果は out で返す
#func public static bool TryParse, string s, out int result
result = 42
return true
#endfunc
#endclass
; HSP では変数を直接参照渡しできないので
; 配列の要素を使ったり、命令で返す
; NHSP の ref/out なら直接書ける
public static void AddTen(ref int value)
{
value = value + 10;
}
public static bool TryParse(
string s, out int result)
{
result = 42;
return true;
}
const をつけたフィールドはコンパイル時に値が確定し、変更できません。
#class public Config
#field public const int MAX_PATH = 260
#field public const string VERSION = "1.0"
#endclass
readonly はコンストラクタで1回だけ設定でき、その後は変更できません。
#class public Settings
#field public readonly int MaxSize
#init int size
MaxSize = size
#endinit
#endclass
static フィールドはクラスに1つだけ存在し、全インスタンスで共有されます。
#class public Counter
#field public static int Count
#endclass
デリゲートは「この形の関数」を表す型です。コールバックやイベントで使います。
; 「int を2つ受け取って int を返す関数」を表すデリゲート型
#delegate public int BinaryOp, int a, int b
; void のデリゲート
#delegate public void EventHandler, object sender, string message
delegate と同じです。HSP にはない概念ですが、
.NET のイベント処理やコールバック関数で必要になることがあります。
列挙型は、決まった選択肢の中から値を選ぶときに使います。HSP の定数定義に近い使い方ができます。
; 色を表す列挙型
#enum Color
Red
Green
Blue
#endenum
; 値を指定することもできる
#enum Priority
Low = 1
Medium = 5
High
Critical = 100
#endenum
値で分岐するとき、if / elseif の連続よりも見やすく書けます。
switch x
case 1
print "one"
case 2
print "two"
default
print "other"
endswitch
1行で「もし〜なら A、そうでなければ B」を書けます。
; 大きい方を返す
dim int result = a > b ? a : b
; null チェック
dim string name = s ?? "名無し"
return a > b ? a : b
return s ?? "default"
return a > b ? a : b;
return s ?? "default";
ハードウェア制御やフラグ操作でよく使います。HSP の &, | と同じ考え方です。
dim int flags = $FF
dim int masked = flags & $0F ; AND: 下位4ビットだけ残す → $0F
dim int combined = $0F | $F0 ; OR: 合成 → $FF
dim int toggled = flags ^ $0F ; XOR: 反転 → $F0
dim int inverted = ~flags ; NOT: 全ビット反転
dim int shifted = 1 << 4 ; 左シフト: 1 → 16
dim int right = 256 >> 3 ; 右シフト: 256 → 32
$"..." の中で {式} を書くと、その部分に値が埋め込まれます。変数名だけでなく、関数呼び出しやプロパティアクセスも使えます。
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
$"..." 内の {} には変数、関数、プロパティ、計算式が書けます。C# の補間文字列とほぼ同じです。
数値は自動的に str() で文字列に変換されます。
型の情報を取得できます。
dim t = typeof(int)
print t.Name ; → "Int32"
x++ は x += 1、x-- は x -= 1 のショートカットです。
dim int i = 0
i++ ; i = 1
i++ ; i = 2
i-- ; i = 1
is で型チェック、as で安全なキャストができます。
; is: 型が合っているか調べる
if obj is string
print "文字列です"
endif
; as: キャストを試みる (失敗したら null)
dim string s = obj as string
dim string result = s ?? "文字列ではない"
.NET のイベント機能を使えます。ボタンのクリックなど、通知の仕組みに使います。
#class public Button
; EventHandler 型のイベントを宣言
#event public EventHandler Click
#endclass
引数にデフォルト値を設定できます。呼び出し時に省略可能になります。
#func public static string Greet, string name = "World", int count = 1
return name
#endfunc
クラスが初めて使われるときに1回だけ実行されます。static フィールドの初期値設定に便利です。
#class public Config
#field public static int MaxRetry
; クラスが初めて使われるときに実行
#init static
MaxRetry = 3
#endinit
#endclass
オブジェクトがメモリから解放されるときに実行されます。ファイルハンドルなどの後片付けに使います。
#class public FileHandler
#destructor
; ファイルを閉じるなどの後処理
#enddestructor
#endclass
using 文を使いましょう。
値型に ? をつけると null を入れられる型になります。
; int? は「整数か null」を表す型
dim int? value
value = 42
if value.HasValue
print value.Value
endif