Use .NET API from D language via C ++ / CLI (old calendar, Japanese calendar processing)

12 minute read

Introduction

I thought about what to do if I want to use .NET API from ** D language **.
I hope it will be helpful for you.

Thing you want to do

In Previous article, I implemented a calendar display program in ** D language **.
I would like to continue to implement the lunar calendar.
The .NET API includes the JapaneseCalendar class and the JapaneseLunisolarCalendar Classes are provided, and you can use them to implement lunar and Japanese calendar calculations from scratch. It seems that you do not have to.

How to implement .NET API call from D language

As far as I can tell, I found the easy way to go via C ++ / CLI wrapping.
This time we will use this method.

  • For reference, the information link I searched for to write the article is posted at the end.
    It is a method to create a DLL with C # and call it from ** D language **.

Preparation 1 (Implementation in C #)

Before implementing it in ** D language **, I implemented the process to get the lunar calendar and Japanese calendar in C #.
If there is no parameter at the time of execution, the lunar calendar or Japanese calendar of today’s date is displayed, and if the date parameter is specified, the lunar calendar or Japanese calendar of that date is displayed.

JCalendar.cs


using System;
using System.Globalization;

public class JCalendar {
	static string[] sEra    = //Era
		{ "", "Meiji", "Taisho", "Showa", "Heisei", "Reiwa" };
	static string[] sRokuyo = //Rokuyo
		{ "Daan", "Red mouth", "First win", "Tomobiki", "Predecessor", "Buddha extinction" };
	static string[] sKanshi = //Heavenly Stems
		{ "", "Instep", "B", "Hinoe", "Ding", "戊", "Self", "Gung", "Spicy", "壬", "癸" };
	static string[] sChishi = //Zodiac
		{ "", "Child", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Not yet", "Monkey", "Rooster", "Dog", "亥" };
	
	public static void Main(string[] args)
	{
		DateTime newDate;
		if ( args.Length >= 3 ){
			newDate = new DateTime(
				int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2]));
		} else {
			newDate = DateTime.Now;
		}
		Console.WriteLine("Year: {0:d}", newDate);
		if ( newDate >= new DateTime(1868, 9, 8) ){
			printJapaneseCalendar(newDate);
		}
		if ( newDate >= new DateTime(1960, 1, 28)
		  && newDate <= new DateTime(2050, 1, 22) ){
			printJapaneseLunisolarCalendar(newDate);
		}
	}
	
	static void printJapaneseCalendar(DateTime newDate)
	{
		JapaneseCalendar jc = new JapaneseCalendar();
		int era   = jc.GetEra(newDate);
		int year  = jc.GetYear(newDate);
		int month = jc.GetMonth(newDate);
		int day   = jc.GetDayOfMonth(newDate);
		DateTime jDate  = new DateTime(year, month, day);
		Console.WriteLine("Japanese Calendar: {0} {1:d}", sEra[era], jDate);
	}
	
	static void printJapaneseLunisolarCalendar(DateTime newDate)
	{
		JapaneseLunisolarCalendar jlc = new JapaneseLunisolarCalendar();
		int era   = jlc.GetEra(newDate);
		int year  = jlc.GetYear(newDate);
		int month = jlc.GetMonth(newDate);
		int day   = jlc.GetDayOfMonth(newDate);
		
		//Get leap month
		string sLeap = "";
		if ( year > 0 ){
			int leapMonth = jlc.GetLeapMonth(year, era);
			if ( month == leapMonth ){
				sLeap = "(Leap month)";
			}
			//Correct the month when including leap months
			if ( (leapMonth > 0) && (month >= leapMonth) ){
				month = month - 1;	//Lunar month correction
			}
		}
		//Zodiac (Heavenly Stems, Zodiac)
		int sy = jlc.GetSexagenaryYear(newDate);
		int tk = jlc.GetCelestialStem(sy);
		int ts = jlc.GetTerrestrialBranch(sy);
		
		//Rokuyo(Daan, Akaguchi, First victory, Tomobiki, First defeat, Buddha extinction)
		// (Month+Day) %6 remainder
		int rokuyo = (month + day) % 6;
		
		Console.WriteLine("Lunar calendar: {0} {1:d4}/{2:d2}/{3:d2} {4}",
			sEra[era], year, month, day, sLeap);
		Console.WriteLine("Zodiac: {0}{1}", sKanshi[tk], sChishi[ts]);
		Console.WriteLine("Rokuyo: {0}", sRokuyo[rokuyo]);
	}
}

C # compiler (csc.exe) is installed as standard in Window10.
Which version is installed in which folder depends on your environment.
Here is an example of compiling and executing in my environment.
In the lunar calendar, there is a leap month. Use GetLeapMonth to calculate the leap month for the year and correct the month.
In the execution result, the month is corrected and displayed correctly. * Verify with Legendary calendar
The zodiac signs (heavenly stems, zodiac signs) have acquired the zodiac signs of the year. It seems that the zodiac also exists on the moon and the day.

Execution result


d:\Dev>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe JCalendar.cs
Microsoft (R) Visual C# Compiler version 4.8.3752.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

d:\Dev>JCalendar
Year: 2020/09/26
Japanese Calendar:Reiwa 0002/09/26
Lunar calendar:Reiwa 0002/08/10
Zodiac:Yang Metal Rat
Rokuyo:Daan

d:\Dev>JCalendar 2020 5 23
Year: 2020/05/23
Japanese Calendar:Reiwa 0002/05/23
Lunar calendar:Reiwa 0002/04/01 (leap month)
Zodiac:Yang Metal Rat
Rokuyo:Buddha extinction

Preparation 2 (Implementation in C ++ / CLI)

Next, I implemented the process to get the lunar calendar and Japanese calendar with C ++ / CLI.
The information about C ++ / CLI in Japanese is not so much, so I hope it will be helpful.
This is an image of porting the source code of C # from Preparation 1.
If there is no parameter at the time of execution, the lunar calendar or Japanese calendar of today’s date is displayed, and if the date parameter is specified, the lunar calendar or Japanese calendar of that date is displayed.

Unique to C ++ / CLI is the operator (^) .

The handle declarator (^ is “hat”) changes the type specifier to mean that the declared object is automatically deleted when the system determines that the object is inaccessible. I will.

Handle to Object Operator (^) (C ++ / CLI and C ++ / CX)

Also, gcnew is used for class generation of .NET API.

Managed (reference or value) memory is allocated by gcnew and freed by garbage collection.

ref new, gcnew (C ++ / CLI and C ++ / CX)

JCal.cpp


using namespace System;
using namespace System::Globalization;

#include <string>

String^ getEraStr(int era)
{	//Era
	array<String^>^ sEra = gcnew array<String^>
		{ "", "Meiji", "Taisho", "Showa", "Heisei", "Reiwa" };
	return ( sEra[era] );
}

String^ getRokuyoStr(int rokuyo)
{	//Rokuyo
	array<String^>^ sRokuyo =
		{ "Daan", "Red mouth", "First win", "Tomobiki", "Predecessor", "Buddha extinction" };
	return ( sRokuyo[rokuyo] );
}

String^ getKanshiStr(int kanshi)
{	//Heavenly Stems
	array<String^>^ sKanshi =
		{ "", "Instep", "B", "Hinoe", "Ding", "戊", "Self", "Gung", "Spicy", "壬", "癸" };
	return ( sKanshi[kanshi] );
}

String^ getChishiStr(int chishi)
{	//Zodiac
	array<String^>^sChishi =
		{ "", "Child", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Not yet", "Monkey", "Rooster", "Dog", "亥" };
	return ( sChishi[chishi] );
}

void printJapaneseCalendar(DateTime newDate)
{
	JapaneseCalendar^ jc = gcnew JapaneseCalendar();
	int era   = jc->GetEra(newDate);
	int year  = jc->GetYear(newDate);
	int month = jc->GetMonth(newDate);
	int day   = jc->GetDayOfMonth(newDate);
	DateTime jDate  = DateTime::DateTime(year, month, day);
	Console::WriteLine("Japanese Calendar: {0} {1:d}", getEraStr(era), jDate);
}

void printJapaneseLunisolarCalendar(DateTime newDate)
{
	JapaneseLunisolarCalendar^ jlc = gcnew JapaneseLunisolarCalendar();
	int era   = jlc->GetEra(newDate);
	int year  = jlc->GetYear(newDate);
	int month = jlc->GetMonth(newDate);
	int day   = jlc->GetDayOfMonth(newDate);
	
	//Get leap month
	String^ sLeap = "";
	if ( year > 0 ){
		int leapMonth = jlc->GetLeapMonth(year, era);
		if ( month == leapMonth ){
			sLeap = "(Leap month)";
		}
		//Correct the month when including leap months
		if ( (leapMonth > 0) && (month >= leapMonth) ){
			month = month - 1;	//Lunar month correction
		}
	}
	//Zodiac (Heavenly Stems, Zodiac)
	int sy = jlc->GetSexagenaryYear(newDate);
	int tk = jlc->GetCelestialStem(sy);
	int ts = jlc->GetTerrestrialBranch(sy);
	
	//Rokuyo(Daan, Akaguchi, First victory, Tomobiki, First defeat, Buddha extinction)
	// (Month+Day) %6 remainder
	int rokuyo = (month + day) % 6;
	
	Console::WriteLine("Lunar calendar: {0} {1:d4}/{2:d2}/{3:d2} {4}",
		getEraStr(era), year, month, day, sLeap);
	Console::WriteLine("Zodiac: {0}{1}", getKanshiStr(tk), getChishiStr(ts));
	Console::WriteLine("Rokuyo: {0}", getRokuyoStr(rokuyo));
}

int main(int argc, char* argv[])
{
	DateTime newDate = DateTime::Now;
	if ( argc > 3 ){
		newDate = DateTime::DateTime(
			atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
	}
	Console::WriteLine("Year: {0:d}", newDate);
	if ( newDate >= DateTime::DateTime(1868, 9, 8) ){
		printJapaneseCalendar(newDate);
	}
	if ( newDate >= DateTime::DateTime(1960, 1, 28)
	  && newDate <= DateTime::DateTime(2050, 1, 22) ){
		printJapaneseLunisolarCalendar(newDate);
	}
}

I have Visual C ++ Build Tools 2019 (https://visualstudio.microsoft.com/en/downloads/) installed in my environment.

Launch the x64 Native Tools Command Prompt for VS2019 (https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=vs-2019).
Compile with the compile option / clr to use the Common Language Runtime (CLR) (https://docs.microsoft.com/en-us/dotnet/standard/clr) feature.

According to the lunar calendar, Reiwa seems to start on March 27th. * Verify with Legendary calendar

Execution result


d:\Dev>cl /clr JCal.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .For .NET Framework version 4.08.4220.0
Copyright (C) Microsoft Corporation.  All rights reserved.

JCal.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:JCal.exe
JCal.obj

d:\Dev>JCal.exe 2019 4 30
Year: 2019/04/30
Japanese Calendar:Heisei 0031/04/30
Lunar calendar:Heisei 0031/03/26
Zodiac:Yin Earth Pig
Rokuyo:Buddha extinction

d:\Dev>JCal.exe 2019 5 1
Year: 2019/05/01
Japanese Calendar:Reiwa 0001/05/01
Lunar calendar:Reiwa 0001/03/27
Zodiac:Yin Earth Pig
Rokuyo:Daan

D language, C ++ / CLI wrapping source code

The preparation has become long, but the main subject is from here.
This is an implementation example of C ++ / CLI wrapping process for calling .NET API.
It is a simple function that sets the acquisition result of the lunar calendar and the Japanese calendar to struct JCAL.
The ʻextern “C” __declspec (dllexport) `attribute is added so that it can be called from ** D language **.

JCalDll.cpp


using namespace System;
using namespace System::Globalization;

typedef struct {
	int era;
	int year;
	int month;
	int day;
	int leapMonth;
	int zodiac;
	int kanshi;
	int chishi;
	int rokuyo;
} JCAL;

#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)

VC_DLL_EXPORTS void __cdecl getJapaneseCalendar(int year, int month, int day, JCAL &jcal)
{
	DateTime newDate = DateTime::DateTime(year, month, day);
	JapaneseCalendar^ jc = gcnew JapaneseCalendar();
	jcal.era   = jc->GetEra(newDate);
	jcal.year  = jc->GetYear(newDate);
	jcal.month = jc->GetMonth(newDate);
	jcal.day   = jc->GetDayOfMonth(newDate);
}

VC_DLL_EXPORTS void __cdecl getJapaneseLunisolarCalendar(int year, int month, int day, JCAL &jcal)
{
	DateTime newDate = DateTime::DateTime(year, month, day);
	JapaneseLunisolarCalendar^ jlc = gcnew JapaneseLunisolarCalendar();
	jcal.era   = jlc->GetEra(newDate);
	jcal.year  = jlc->GetYear(newDate);
	jcal.month = jlc->GetMonth(newDate);
	jcal.day   = jlc->GetDayOfMonth(newDate);
	jcal.leapMonth = 0;
	//Get leap month
	if ( jcal.year > 0 ){
		int leapMonth = jlc->GetLeapMonth(jcal.year, jcal.era);
		if ( jcal.month == leapMonth ){
			jcal.leapMonth = 1;
		}
		//Correct the month when including leap months
		if ( (leapMonth > 0) && (jcal.month >= leapMonth) ){
			jcal.month = jcal.month - 1;	//Lunar month correction
		}
	}
	//Zodiac (Heavenly Stems, Zodiac)
	jcal.zodiac = jlc->GetSexagenaryYear(newDate);
	jcal.kanshi = jlc->GetCelestialStem(jcal.zodiac);
	jcal.chishi = jlc->GetTerrestrialBranch(jcal.zodiac);
	
	//Rokuyo(Daan, Akaguchi, First victory, Tomobiki, First defeat, Buddha extinction)
	// (Month+Day) %6 remainder
	jcal.rokuyo = (jcal.month + jcal.day) % 6;
}

Next, it is an implementation example of a program that calls C ++ / CLI wrapping in ** D language ** and displays the lunar calendar and Japanese calendar.
The information you want to get is defined in the struct, the memory is secured, and it is passed to C ++ / CLI.
Just by writing pragma (lib," JCalDll ") and ʻextern (Windows) ~, you can call the function on the C ++ / CLI` side, so it was easier to implement than I expected.

oldcal.d


import std.algorithm;
import std.conv;
import std.datetime;
import std.format;
import std.range;
import std.stdio;
import core.sys.windows.windows;

struct JCAL {
	int era;
	int year;
	int month;
	int day;
	int leapMonth;
	int zodiac;
	int kanshi;
	int chishi;
	int rokuyo;
}

pragma(lib, "JCalDll");

extern (Windows) nothrow @nogc {
	void getJapaneseCalendar(int, int, int, ref JCAL);
	void getJapaneseLunisolarCalendar(int, int, int, ref JCAL);
}

string[] sEra =
	[ "", "Meiji", "Taisho", "Showa", "Heisei", "Reiwa" ];
string[] sKanshi =
	[ "", "Instep", "B", "Hinoe", "Ding", "戊", "Self", "Gung", "Spicy", "壬", "癸" ];
string[] sChishi =
	[ "", "Child", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Not yet", "Monkey", "Rooster", "Dog", "亥" ];
string[] sRokuyo =
	[ "Big", "Red", "Win", "friend", "negative", "Buddha" ];
//	[ "Daan", "Red mouth", "First win", "Tomobiki", "Predecessor", "Buddha extinction" ];

void main(string[] args)
{
	Date dt = Clock.currTime().to!Date;
	if ( args.length > 2 ){
		dt = Date(args[1].to!int, args[2].to!int, 1);
	} else {
		dt = Date(dt.year, dt.month, 1);
	}
	writef("\n%4d years%2d month", dt.year, dt.month);
	JCAL jcal;
	string line1 = "     |".cycle.take(dt.dayOfWeek * 6).to!string;
	string line2 = "      |".cycle.take(dt.dayOfWeek * 7).to!string;
	with ( jcal ){
		getJapaneseCalendar(dt.year, dt.month, 1, jcal);
		writef("(%s %2d years) ", sEra[era], year);
		getJapaneseLunisolarCalendar(dt.year, dt.month, 1, jcal);
		writefln("[%s%s]", sKanshi[kanshi], sChishi[chishi]);
		writefln("%-(  %s  |%)", ["Day", "Month", "fire", "water", "wood", "Money", "soil" ]);
		for ( int d = 1; d <= dt.daysInMonth; d++ ){
			getJapaneseLunisolarCalendar(dt.year, dt.month, d, jcal);
			line1 ~= format("%2d %s |", d, sRokuyo[rokuyo]);
			line2 ~= format("%2d/%2d%s|", month, day,(leapMonth == 1) ? "*" : " ");
		}
	}
	int num = (cast(int)line2.length / (7 * 7)) + 1;
	string line0 = "-".cycle.take(num * 7 * 7).to!string;
	roundRobin(line0.chunks(7 * 7), line1.chunks(6 * 7), line2.chunks(7 * 7))
		.each!(s => writefln("%s", s));
}

Supplementary explanation of D language source code

If there are no parameters at runtime, it will be processed in today’s year and month. You can also specify the year and month with the ʻargs` parameter.

string line1 contains one double-byte character for each date. Therefore, the daily display width is 6 characters.
There are a total of 6 characters in the order of 2 characters for the date of the new calendar, 1 character for half-width space, 1 character for Rokuyo display or full-width space, 1 character for half-width space, and 1 character for | .

string line2 is all half-width characters, and the daily display width is 7 characters.
There are 5 characters in the lunar year and month, 1 character in * for half-width spaces or leap months, and 1 character in |, for a total of 7 characters.

string line0 is the horizontal line-.

Use chunks to break line0 line1 line2 every 7 days and roundRobin to alternate.
chunks
roundRobin

compile

Launch the x64 Native Tools Command Prompt for VS2019 (https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=vs-2019).
Add the options / clr and / LD when compiling C ++ / CLI. JCalDll.lib and JCalDll.dll are generated.
JCalDll.lib is required when compiling D language sources. JCalDll.dll is required when running ʻoldcal.exe`.

When compiling with dmd, add the option -m64 to generate 64-bit code.
If you have ldc2 installed, you can use ldc2 oldcal.d.

compile


d:\Dev>cl /clr /LD JCalDll.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .For .NET Framework version 4.08.4220.0
Copyright (C) Microsoft Corporation.  All rights reserved.

JCalDll.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:JCalDll.dll
/dll
/implib:JCalDll.lib
JCalDll.obj
Library JCalDll.lib and object JCalDll.Creating exp

d:\Dev>dmd -m64 oldcal.d

Execution result

The displayed Japanese character code is ʻUTF-8, so please execute chcp 65001 first. If you want to output in Japanese with Shift-JIS`, you need to add character code conversion processing to the source code as introduced before.
Introduction information 1
Introduction information 2

At the command prompt in my environment, I use Cica font.
Therefore, in the execution result, the double-byte space is displayed as a square frame.

Now you can see the lunar calendar of your birth month.
However, in the specification of GetLeapMonth of the JapaneseLunisolarCalendar class, January 1960 AD If you try to get a leap month before 27 (1959 in the lunar calendar), you will get a ʻException`, so you can only view the lunar calendar after February 1960. The upper limit is December 2049 AD.
無題.png

Reference information used this time

JapaneseCalendar class
JapaneseLunisolarCalendar class
Handle to Object Operator (^) (C ++ / CLI and C ++ / CX)
ref new, gcnew (C ++ / CLI and C ++ / CX)

[How to call C # methods from C ++]
(https://qiita.com/tetsurom/items/a0ad9bd24dbe513afdc4)
[Wrap C # with C ++ / CLI and call it from the C ++ app]
(https://qiita.com/tera1707/items/69bdfe99733b84c97217)

Rokuyo / Moon Age / Lunar Calendar
Year / Month / Day Zodiac
How is Rokuyo decided? How to calculate Rokuyo?
Calculation of Rokuyo with c #

Information that is not used this time but may be helpful

Introduction to C ++ / CLI Wrapping
[Try calling a C # DLL from a C ++ module]
(https://knowledge.rinpress.com/index.php/%EF%BC%A3%EF%BC%8B%EF%BC%8B%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%8B%E3%82%89%EF%BC%A3%EF%BC%83%E3%81%AE%EF%BC%A4%EF%BC%AC%EF%BC%AC%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B)
[How to use C # DLL directly from C ++]
(https://qiita.com/Midoliy/items/58d56e202f104ebf867a)

[Calling-NET-from-D]
(https://github.com/taylorh140/Calling-NET-from-D)