nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

C#の勉強サイトまとめ

codezine.jp

[C# / WPF] 最新のC# 6.0でMVVMパターンを実装する qiita.com

C#からC++のDLLを呼び出す (構造体編) [C#]

はじめに

以下の場合も、実現可能です。

  • C++のDLLのAPIに対して、構造体のデータを渡す場合
  • C++のDLLのAPIから、構造体のデータを受け取る場合

環境

  • IDE : Visual Studio Community 2017 (Version 15.7.1)

コード

#include <string>

#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif

const static int INIT_HP = 300;
const static int INIT_MP = 200;
const static int MAX_NAME_LENGTH = 256;
const static int BIG_DATA_LENGTH = 100000;

struct MainCharacter_t
{
    char name[MAX_NAME_LENGTH];
    int hp;
    int mp;
    unsigned char bigData[BIG_DATA_LENGTH];
};

MainCharacter_t MainCharacter = { "Ichiro", INIT_HP, INIT_MP, 0 };


DLLEXPORT void SetCharaData(MainCharacter_t *someData_t)
{
    memcpy_s(&MainCharacter, sizeof(MainCharacter_t), someData_t, sizeof(MainCharacter_t));
}

DLLEXPORT void GetCharaData(MainCharacter_t *someData_t)
{
    memcpy_s(someData_t, sizeof(MainCharacter_t), &MainCharacter, sizeof(MainCharacter_t));
}

// テストデータセット用[f:id:nprogram:20180522230541p:plain]
DLLEXPORT void SetBigData()
{
    unsigned char tempBigData[BIG_DATA_LENGTH] = { 0 };

    for (int i = 0; i < BIG_DATA_LENGTH; i++)
    {
        tempBigData[i] = i % 256;
    }

    memcpy_s(MainCharacter.bigData, BIG_DATA_LENGTH, tempBigData, BIG_DATA_LENGTH);
}

[C#のコード]

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        public const int MAX_NAME_LENGTH = 256;
        public const int BIG_DATA_LENGTH = 100000;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct MainCharacter_t
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_NAME_LENGTH)]
            public string imageFileName;
            public int hp;
            public int mp;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = BIG_DATA_LENGTH)]
            public byte[] bigData;
        }

        // テストデータセット用
        [DllImport("CPlusDLL", EntryPoint = "SetBigData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _SetBigData();

        [DllImport("CPlusDLL", EntryPoint = "SetCharaData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _SetCharaData(IntPtr someCharaData);

        [DllImport("CPlusDLL", EntryPoint = "GetCharaData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _GetCharaData(IntPtr someCharaData);

        static void Main(string[] args)
        {
            // テストデータセット
            _SetBigData();

            ShowCharaData(GetCurrentCharaData());

            MainCharacter_t setData1 = new MainCharacter_t();
            setData1.hp = 10;
            setData1.mp = 0;
            setData1.imageFileName = "Jiro";
            SetCurrentCharaData(setData1);

            // テストデータセット
            _SetBigData();
            ShowCharaData(GetCurrentCharaData());
        }

        static public void SetCurrentCharaData(MainCharacter_t someCharaData)
        {
            // COM タスク メモリ アロケーターから、C#の構造体のサイズ分、メモリ ブロックを割り当てる
            IntPtr someCharaDataPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(someCharaData));

            // マネージ オブジェクトからアンマネージ メモリ ブロックにデータをマーシャリングする
            Marshal.StructureToPtr(someCharaData, someCharaDataPtr, false);

            _SetCharaData(someCharaDataPtr);

            Marshal.FreeCoTaskMem(someCharaDataPtr);
        }

        static public MainCharacter_t GetCurrentCharaData()
        {
            MainCharacter_t mainCharacter = new MainCharacter_t();


            // COM タスク メモリ アロケーターから、C#の構造体のサイズ分、メモリ ブロックを割り当てる
            IntPtr mainCharacterPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(mainCharacter));

            _GetCharaData(mainCharacterPtr);

            // IntPtr変数が示すメモリに格納された情報を、Marshal.PtrToStructure()で、C#の構造体にコピーする
            mainCharacter = (MainCharacter_t)Marshal.PtrToStructure(mainCharacterPtr, mainCharacter.GetType());

            Marshal.FreeCoTaskMem(mainCharacterPtr);

            return mainCharacter;
        }

        static public void ShowCharaData(MainCharacter_t someCharaData)
        {
            Console.WriteLine("MainChara Name : " + someCharaData.imageFileName);
            Console.WriteLine("MainChara HP : " + someCharaData.hp);
            Console.WriteLine("MainChara MP : " + someCharaData.mp);

            Console.WriteLine("[Big Data First Data (5 count)]");
            for ( int i = 0; i < 5; i++)
            {
                Console.WriteLine("[" + i + "] : " + someCharaData.bigData[i]);
            }

            Console.WriteLine("[Big Data Last Data (5 count)]");
            for (int i = BIG_DATA_LENGTH - 5; i < BIG_DATA_LENGTH; i++)
            {
                Console.WriteLine("[" + i + "] : " + someCharaData.bigData[i]);
            }
        }
    }
}

実行結果

f:id:nprogram:20180522230541p:plain

参考リンク

d.hatena.ne.jp

C#からC++のDLLを呼び出す [C#]

はじめに

過去に作成したC++の関数(API)をC#から呼び出したい場合があると思います。

その場合は、C++のプロジェクトをDLLにして、C#からAPIを呼び出すことが可能です。

ただし、C#(マネージ・コード)の変数とC++(アンマネージ・コード)の変数は、

メモリへの配置が基本型以外異なっており、単純にC#からC++に渡すことができません。

例えば、文字列を扱う場合は、C#のstring型とC++のstd::string型は同一ではないため、マーシャリングの処理が必要です。

ここで、マーシャリングとは、異なる2つのシステム間で、データを交換できるようにデータを操作する処理を指します。

環境

  • IDE : Visual Studio Community 2017 (Version 15.7.1)

コード

DLLは、C++のプロジェクトを右クリックしてプロパティ⇒構成プロパティ⇒全般⇒構成の種類をダイナミック ライブラリ (.dll)にすることで作成可能です。

f:id:nprogram:20180516205424p:plain

以下のDLLの関数呼び出しを行います。

関数説明 宣言
1 整数型の引数を2つ渡して戻り値に整数型を返す関数 int Mul(int x, int y);
2 引数に、整数型のポインタを3つ渡す関数 (内、1つは出力用) void MulI(int x, int y, int *result);
3 マルチバイト文字列を設定する関数 void SetNameStr(const char_t *t);
4 マルチバイト文字列を取得する関数 const char_t * GetNameStr();
5 Unicode文字列を設定する関数 void SetNameWStr(const char_t *t);
6 Unicode文字列を取得する関数 const wchar_t * GetNameWStr();

[C++のソースコード]

#include <string>

#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif

static std::string _str = "";
static std::wstring _wstr = L"";


DLLEXPORT int __stdcall Mul(int x, int y)
{
    return x * y;
}

DLLEXPORT void __stdcall MulUsePointer(int *x, int *y, int *result)
{
    *result = (*x) * (*y);
}

DLLEXPORT void __stdcall SetNameStr(const char *t)
{
    _str = std::string(t);
}

DLLEXPORT const char * __stdcall GetNameStr()
{
    return _str.c_str();
}

DLLEXPORT void __stdcall SetNameWStr(const wchar_t *t)
{
    _wstr = std::wstring(t);
}

DLLEXPORT const wchar_t * __stdcall GetNameWStr()
{
    return _wstr.c_str();
}

C#のコードで、DLL関数を呼び出すため、DLLImportを行います。

DllImpoortは、以下のように記載します。CPlusDLLは、DLL名を指します。C#のプロジェクトの実行ファイルと同じ階層にDLLファイルがある場合は、DLLファイル名のみでかまいません。

違うパスにある場合は、Full Pathで指定する必要があります。

C#側でC++のAPIを呼び出そうとしたとき、「エントリーポイントが見つかりません。」と実行時にエラーが出た場合は、DllImport文に誤りがある可能性が高いです。

[DllImport("CPlusDLL", EntryPoint = "SetNameStr", CharSet = CharSet.Ansi)]

パラメータ 説明 既定値
EntryPoint DLL内の関数の名前
CharSet 文字列のマーシャリング方法を示すCharSet列挙 CharSet.Auto
SetLastError Win32エラー情報を維持するか? FALSE
ExactSpelling EntryPointの関数名を厳密に一致させるか? FALSE
PreserveSig 定義通りのメソッドのシグネチャを維持するか?
CallingConvention EntryPointで使用するモードを指定するCallingConvention列挙 StdCall

なお、C# 言語では外部メソッドを呼び出す場合、必ず、メソッドに extern 修飾子を指定しなければなりません。

以下のメソッド宣言で、refoutというパラメーター修飾子を使っています。

out修飾子はreturn以外でメソッド内からメソッド外へデータを受け渡す場合で使用されます。

ref修飾子はメソッド外からメソッド内へデータを渡し、変更を外部へ反映させる必要がある場合に使用します。

static extern void _MulUsePointer(ref int x, ref int y, out int result);

以下のコードstatic extern IntPtr _GetNameStr();では、IntPtr型を戻り値の型として使用しています。 IntPtr型は、汎用的なポインタを表す型で、ほぼ void* と同義です。

[C#のコード]

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        [DllImport("CPlusDLL", EntryPoint = "Mul")]
        static extern int _Mul(int x, int y);

        [DllImport("CPlusDLL", EntryPoint = "MulUsePointer")]
        static extern void _MulUsePointer(ref int x, ref int y, out int result);

        [DllImport("CPlusDLL", EntryPoint = "SetNameStr", CharSet = CharSet.Ansi)]
        static extern void _SetNameStr(string t);

        [DllImport("CPlusDLL", EntryPoint = "GetNameStr", CharSet = CharSet.Ansi)]
        static extern IntPtr _GetNameStr();

        [DllImport("CPlusDLL", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)]
        static extern void _SetNameWStr(string t);

        [DllImport("CPlusDLL", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)]
        static extern IntPtr _GetNameWStr();


        static void Main(string[] args)
        {
            int x = 10;
            int y = 20;

            int resultMul = _Mul(x, y);
            Console.WriteLine("Mul = " + resultMul);

            _MulUsePointer(ref x, ref y, out int resultMulUsePointer);
            Console.WriteLine("MulUsePointer = " + resultMulUsePointer);

            string testString = "東京都新宿 1-1";
            _SetNameStr(testString);
            Console.WriteLine("GetNameStr = " + Marshal.PtrToStringAnsi(_GetNameStr()));

            _SetNameWStr(testString);
            Console.WriteLine("GetNameWStr = " + Marshal.PtrToStringUni(_GetNameWStr()));

        }
    }
}

実行結果

f:id:nprogram:20180516205657p:plain

参考リンク

Google TestをVisual Studio 2017で簡単導入 [C++]

はじめに

Visual Studio 2017を使えば、Google Testを用いたプロジェクトが割と簡単に作成できます。

なお、現状は、C++のプロジェクトでのみ適用可能です。C#は不可 (泣)。

実現例 (プロジェクトテンプレートからGoogle Testプロジェクトを作成可能)

f:id:nprogram:20180515070026p:plain

環境

  • Visual Studio Community 2017 Version 15.7.1
  • Windows 10 バージョン1709

方法

Visual Studio Installerを用いて追加機能をインストールします。

Visual Studio Installerは、メニューのツール⇒ツールと機能の取得(Get Tools And Features)を選択することで、起動可能です。

(1) 言語パックで英語版をインストールしてください。これは、現状、Google Testプロジェクトの作成がVisual Studioが英語文字列表示のみで可能であるためです。

(2) 個別のコンポーネントで、Test Adapter for Google Testをインストールしてください。

<言語パックの選択> f:id:nprogram:20180515064719p:plain

<言語の切り替えはメニューのツール⇒オプション⇒環境⇒国際対応の設定> f:id:nprogram:20180515065440p:plain

参考ページ

あとがき

  • 次に記載する記事では、実際に、Google Testを使用して、C++のプロジェクトの単体テストを実行してみたいと思います。

MFCでBitmapファイルを開く(BITMAPINFOHEADER, BITMAPINFOHEADERを取得) [C++][MFC]

はじめに

MFCでBitmapファイルを開く方法を下記に記載します。

<使用例>

f:id:nprogram:20180501002747p:plain

ソースコードファイルの記載は以下のとおりです。クラスではなく、ユーティリティ関数として作成しました。 [BitmapUtil.cpp]

#include "stdafx.h"
#include "BitmapUtil.h"

bool LoadBitmapFile(CString fileName , BITMAPFILEHEADER &bmpHeader, BITMAPINFOHEADER &bmpInfo, BYTE** bitmapImageBuffer, int &bitmapImageBufferSize) {


    HANDLE file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    DWORD bytesread;

    // read BITMAPFILEHEADER
    if (ReadFile(file, &bmpHeader, sizeof(BITMAPFILEHEADER), &bytesread, NULL) == false)
    {
        CloseHandle(file);
        return false;
    }

    // read BITMAPINFOHEADER
    if (ReadFile(file, &bmpInfo, sizeof(BITMAPINFOHEADER),
        &bytesread, NULL) == false)
    {
        CloseHandle(file);
        return false;
    }

    // First check if the file is actually a bitmap:
    if (bmpHeader.bfType != 'MB')
    {
        CloseHandle(file);
        return false;
    }

    // check if it's uncompressed
    if (bmpInfo.biCompression != BI_RGB)
    {
        CloseHandle(file);
        return false;
    }

    // check if it's 24bit
    if (bmpInfo.biBitCount != 24)
    {
        CloseHandle(file);
        return false;
    }
    int width = bmpInfo.biWidth;
    int height = abs(bmpInfo.biHeight);
    bitmapImageBufferSize = bmpHeader.bfSize - bmpHeader.bfOffBits;

    *bitmapImageBuffer = new BYTE[bitmapImageBufferSize];

    if (ReadFile(file, *bitmapImageBuffer, bitmapImageBufferSize, &bytesread, NULL) == false)
    {
        delete[] *bitmapImageBuffer;
        CloseHandle(file);
        return false;
    }

    CloseHandle(file);
    return true;
}

ヘッダファイルの記載は以下のとおりです。 [BitmapUtil.h]

#pragma once
bool LoadBitmapFile(CString fileName, BITMAPFILEHEADER &bmpHeader, BITMAPINFOHEADER &bmpInfo, BYTE** bitmapImageBuffer, int &size);

利用側のコードは以下のとおりです。 ここでは、ボタン押下時のイベントハンドラ処理に記載しました。 [MFCApplication1Dlg.cpp]

void CMFCApplication1Dlg::OnBnClickedButton1()
{
    // TODO: ここにコントロール通知ハンドラー コードを追加します。
    //BMP画像をファイルから読み込む

    CString         filter("BMP Files (*.bmp)|*.bmp||");
    CFileDialog     selDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filter);

    if (selDlg.DoModal() == IDOK)
    {
        m_fileNameBitmap = selDlg.GetPathName();


        BITMAPFILEHEADER bmpHeader;
        BITMAPINFOHEADER bmpInfo;
        BYTE* bitmapImageBuffer = nullptr;
        int size = 0;

        bool result = LoadBitmapFile(m_fileNameBitmap, bmpHeader, bmpInfo, &bitmapImageBuffer, size);

        std::string strWidth = std::to_string(bmpInfo.biWidth);
        std::string strHeight = std::to_string(bmpInfo.biHeight);

        CString cstrWidth(strWidth.c_str());
        CString cstrHeight(strHeight.c_str());
        m_width = cstrWidth;
        m_height = cstrHeight;

        m_BitmapImageBufferSize = size;


        UpdateData(FALSE);
    }

}

参考サイト

なお、以下のサイト参考にさせていただきました。 tipsandtricks.runicsoft.com

ルーティングイベントの学習 [C#][WPF]

ルーティングイベントとは

  • 観点を機能に置いた場合、イベントを生成したオブジェクト上だけでなく、要素ツリー内の複数のリスナー上でハンドラーを呼び出すことができる種類のイベント
  • 観点を実装に置いた場合、ルーティング イベントは、RoutedEvent クラスのインスタンスによってサポートされる CLR イベントであり、Windows Presentation Foundation (WPF) イベント システムによって処理される

サンプル例

Buttonが3つあり、親要素にStatckPanelがあり、さらに、親要素にBorderがあり、さらに親要素にWindowがある画面。

Buttonのどれかを押すと、StackPanelのClickイベントハンドラが呼ばれ、次に、BorderのClickイベントハンドラが呼ばれ、次に、WindowsのClickイベントハンドラが呼ばれる。

f:id:nprogram:20180418235227p:plain

f:id:nprogram:20180418235435p:plain

f:id:nprogram:20180418235447p:plain

f:id:nprogram:20180418235456p:plain

[MainWindow.xaml]

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow"
        SizeToContent="WidthAndHeight" Button.Click="WindowClickHandler">
    <Grid>
        <Border Height="50" Width="Auto" BorderBrush="Gray" BorderThickness="1" Button.Click="BorderClickHandler">
            <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="StackPanelClickHandler">
                <Button Name="YesButton" Width="Auto" >Yes</Button>
                <Button Name="NoButton" Width="Auto" >No</Button>
                <Button Name="CancelButton" Width="Auto" >Cancel</Button>
            </StackPanel>
        </Border>
    </Grid>
</Window>

[MainWindow.xaml.cs]

using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void StackPanelClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[StackPanel] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[StackPanel] Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[StackPanel] Push Cancel Button!");
                    break;
            }
            // e.Handledをtrueにしないかぎり、親要素にボタンクリックイベントに伝搬していく
            e.Handled = false;
        }

        private void BorderClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[Border] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[Border]Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[Border]Push Cancel Button!");
                    break;
            }
            // e.Handledをtrueにしないかぎり、親要素にボタンクリックイベントに伝搬していく
            e.Handled = false;
        }

        private void WindowClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[Window] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[Window]Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[Window]Push Cancel Button!");
                    break;
            }
            e.Handled = true;
        }
    }
}

ハンドラの引数であるRoutedEventArgsに定義されているHandledプロパティを使用すると、イベントルーティングを制御できます。 ハンドラ内でHandledプロパティをtrueにセットした場合、それ以降のイベントルーティングは呼び出されません。 例えば、StackPanelClickHandlerメソッドのe.Handled = falsee.Handled = trueに変更すると、イベントが親要素(Border)に伝搬しなくなります。

RoutedEventについては、この後、記載します。

参考ページ

  • ルーティング イベントの概要

https://msdn.microsoft.com/ja-jp/library/ms742806(v=vs.100).aspx