nprogram’s blog

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

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

参考リンク

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

WPFで、自作イベントの作り方 [C#][WPF]

はじめに

WPFで、自作イベントを作成する方法を調べてみました。

参考サイト

以下のサイトを参考にさせていただきました。

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

  • あと、tocsworld 様のCLRイベントとWPF Routedイベントの違いの記事はすごく勉強になります。 tocsworld.wordpress.com

  • あと、英語のページだけどこれもよさげ。 www.c-sharpcorner.com