ページ「型付DataSetに列挙型を取り込む」と「C♯のモジュールからC++のDLLを呼び出してみる」の間の差分

提供: とある社畜の頭脳整理
(ページ間の差分)
ナビゲーションに移動 検索に移動
 
 
1行目: 1行目:
型付DataSetに列挙型(enum)を取り込む場合、色々な方法があると思うんだ。一番簡単に想像できるのは、DataSetデザイナーで特定のテーブルの列の型を、ネームスペース含めて列挙型をしてしまうことなんだけど…。僕がやった限り、一回これをやってしまうとデータセットデザイナーが正しく開けなくなっちゃうんだよ。で、色々調べて・試してその結果たどり着いた(僕が)最善と思っている方法を紹介するよ。
+
久しぶりにやったら、すっかり忘れていたので覚書…
  
== 考え方 ==
+
== C++のDLLプロジェクト作成 ==
色々やった結果…実はDataSetに列挙型を取り込むこと自体を諦めたんだよ…(^_^;)でも、コード上で毎回キャストしたくはなかったんだ…。そこで注目したのは、自動生成される型付DataSetはpartialクラスってことなんだ…。つまり、DataSetの定義は「Int32」でしておいて、DataSetのpartialを作って、そのクラスに列挙型で出し入れできるプロパティを作ってしまおうって事なんだよ。
+
まず、C++のDLLを作成するときの注意点…
 +
作成するプロジェクトは、「Win32プロジェクト」を選択するんだ。<br/>
 +
[[ファイル:CShapeToCppDll-005.jpg]]
  
=== partialクラスってなに??? ===
 
partialクラスって言うのは、クラス定義を分割して定義している(ないしはできる)クラスのことを言うんだよ。たとえば、以下のようなクラスがあったとするよね…
 
  
<syntaxhighlight lang="C#">
+
あと…アプリケーションの設定では「DLL」と「空のプロジェクト」を選択してね。<br/>
namespace CommonCtrlLib
+
[[ファイル:CShapeToCppDll-001.jpg]]<br/>
{
+
 
    public class Class1
+
 
    {
+
空のプロジェクトが作成されたら「cpp」「h」「def」ファイルを追加するんだ。今回は「CppDll.cpp」「CppDll.h」「CppDll.def」を追加したよ。
        private Int32 m_field_A;
+
 
 +
 
 +
そしたら、プロジェクトのプロパティを開いて「構成プロパティ→リンカー→入力→モジュール定義ファイル」に「CppDll.def」を設定するんだ。<br/>
 +
(DebugとReleaseでそれぞれ設定する必要があるんだよ。)<br/>
 +
[[ファイル:CShapeToCppDll-002.jpg]]<br/>
 +
 
 +
== DLLのコード ==
 +
=== ヘッダーファイル(*.h) ===
 +
<syntaxhighlight lang="cpp">
 +
#ifndef DLLAPI
 +
#define DLLAPI extern "C" __declspec(dllimport)
 +
#endif
 +
 
 +
DLLAPI long __stdcall _Sum(const long p_Number1, const long p_Number2);
 +
</syntaxhighlight>
  
        public Class1()
+
=== コードファイル(*.cpp) ===
        {
+
<syntaxhighlight lang="cpp">
            this.m_field_A = -1;
+
#define DLLAPI
        }
 
  
        public Int32 field_A
+
#include "CppDll.h"
        {
 
            get { return this.m_field_A; }
 
            set { this.m_field_A = value; }
 
        }
 
  
        public String field_B
+
DLLAPI long __stdcall _Sum(const long p_Number1, const long p_Number2)
        {
+
{
            get { return this.m_field_A.ToString(); }
+
return p_Number1 + p_Number2;
            set { this.m_field_A = Int32.Parse(value); }
 
        }
 
    }
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
クラス内の定義って言うのはclass{}の中に記述しないといけないから、つまるところ1ファイルの中にすべて書かないといけないって事になっちゃうんだよね。でも、諸事情により別のファイルに書きたいときもあると思うんだ。自動生成されたDataSetのファイルなんかは特にそうで…自動生成されたファイルをカスタマイズしちゃうと、また自動生成したときにカスタマイズが消えちゃったりするんだ。そんなときに、自分のカスタマイズ部分だけ別のファイルに書ければ…ってことになるんだよ。
+
=== モジュール定義ファイル(*.def) ===
 +
<syntaxhighlight lang="text">
 +
LIBRARY CppDll
 +
 
 +
EXPORTS
 +
_Sum
 +
</syntaxhighlight>
 +
 
 +
== C♯のプロジェクト作成 ==
 +
ほとんどそのまま作るんだけど…ソリューションのコンパイル対策をしておくよ。
 +
 
 +
=== ビルドイベントの設定 ===
 +
C++のDLLはソリューションフォルダ直下の「Debug」や「Release」フォルダにDLLが格納されてしまうんだ。そうすると、デバッグするときにDLLが見つからないので、ビルドイベントを使ってコピーしてしまうよ。以下のように設定してね。(「Release」コンパイルするまでは「Release」フォルダがないのでコメントアウトしているよ)<br/>
 +
[[ファイル:CShapeToCppDll-004.jpg]]<br/>
 +
 
 +
=== プロジェクトの依存関係の設定 ===
 +
コピーするにもちゃんとリコンパイルされた資源をコピーしないといけないので、プロジェクトの依存関係を設定することで、ビルドの順番を設定するよ。<Br/>
 +
ソリューションエクスプローラーからC♯のプロジェクトを右クリックして「ビルド依存関係」→「プロジェクト依存関係」を選択してね。<br/>
 +
「依存関係」タブの依存先にC++のプロジェクトが表示されているはずだから、チェックを入れてOKボタンをクリックしてね。
 +
 
 +
== C♯のコード ==
 +
MVVMモデルでサンプルを作ったからビューモデルが入っているけど…DLLを呼ぶには必要ないから無視してね。
 +
=== モデル ===
 +
<source lang="csharp">
 +
using System;
 +
using System.Collections.Generic;
 +
using System.Linq;
 +
using System.Text;
 +
using System.Threading.Tasks;
 +
using System.Windows;
 +
using System.Windows.Controls;
 +
using System.Windows.Data;
 +
using System.Windows.Documents;
 +
using System.Windows.Input;
 +
using System.Windows.Media;
 +
using System.Windows.Media.Imaging;
 +
using System.Windows.Navigation;
 +
using System.Windows.Shapes;
  
そんなときはclassをpartial型で定義するとできちゃうんだよ。
+
//追加
 +
using System.Runtime.InteropServices;
  
ファイル1
+
namespace CSharpToCDLL
<syntaxhighlight lang="C#">
 
namespace TestLibrary
 
 
{
 
{
     public partial class Class1
+
    /// <summary>
 +
    /// MainWindow.xaml の相互作用ロジック
 +
    /// </summary>
 +
     public partial class MainWindow : Window
 
     {
 
     {
         private Int32 m_field_A;
+
        /// <summary>
 +
        /// DLLの関数定義
 +
        /// </summary>
 +
        /// <param name="p_Number1">数値1</param>
 +
        /// <param name="p_Number2">数値2</param>
 +
        /// <returns>合計</returns>
 +
        [DllImport("CppDll.dll")]
 +
         private extern static Int32 _Sum(Int32 p_Number1,Int32 p_Number2);
  
         public Class1()
+
        /// <summary>
 +
        /// 標準のコンストラクタ
 +
        /// </summary>
 +
         public MainWindow()
 
         {
 
         {
             this.m_field_A = -1;
+
             InitializeComponent();
 
         }
 
         }
  
         public Int32 field_A
+
         /// <summary>
 +
        /// ボタンクリックイベントハンドラ
 +
        /// </summary>
 +
        /// <param name="sender">イベント送信元</param>
 +
        /// <param name="e">イベント情報</param>
 +
        private void Button_Click(object sender, RoutedEventArgs e)
 
         {
 
         {
             get { return this.m_field_A; }
+
             //DLLの関数を呼び出す
             set { this.m_field_A = value; }
+
            Int32 l_Result = _Sum(300, 500);
 +
 
 +
             //計算結果の表示
 +
            MessageBox.Show("計算結果:" + l_Result.ToString());
 
         }
 
         }
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
+
</source>
ファイル2
+
=== ビュー ===
<syntaxhighlight lang="C#">
+
<source lang="xml">
namespace TestLibrary
+
<Window x:Class="CSharpToCDLL.MainWindow"
 +
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 +
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 +
        Title="MainWindow"
 +
        Height="77.056"
 +
        Width="525">
 +
    <Grid>
 +
        <Grid.RowDefinitions>
 +
            <RowDefinition Height="5"/>
 +
            <RowDefinition Height="24"/>
 +
            <RowDefinition/>
 +
            <RowDefinition Height="5"/>
 +
        </Grid.RowDefinitions>
 +
        <Grid.ColumnDefinitions>
 +
            <ColumnDefinition Width="5"/>
 +
            <ColumnDefinition/>
 +
            <ColumnDefinition Width="5"/>
 +
            <ColumnDefinition Width="75"/>
 +
            <ColumnDefinition Width="5"/>
 +
        </Grid.ColumnDefinitions>
 +
        <Label
 +
            Grid.Row="1"
 +
            Grid.Column="1"
 +
            Content="{Binding Label_Content}"/>
 +
        <Button
 +
            Grid.Row="1"
 +
            Grid.Column="3"
 +
            Content="実行"
 +
            Click="Button_Click"/>
 +
    </Grid>
 +
</Window>
 +
</source>
 +
=== ビューモデル ===
 +
<source lang="csharp">
 +
using System;
 +
using System.Collections.Generic;
 +
using System.Linq;
 +
using System.Text;
 +
using System.Threading.Tasks;
 +
 
 +
//追加
 +
using System.ComponentModel;
 +
 
 +
namespace CSharpToCDLL
 
{
 
{
     public partial class Class1
+
     class MainWindowViewModel : INotifyPropertyChanged
 
     {
 
     {
         public String field_B
+
        /// <summary>
 +
        /// ラベル表示用変数
 +
        /// </summary>
 +
        private String m_Label_Content;
 +
 
 +
        /// <summary>
 +
        /// ラベル表示文字列
 +
        /// </summary>
 +
         public String Label_Content
 
         {
 
         {
             get { return this.m_field_A.ToString(); }
+
             set
             set { this.m_field_A = Int32.Parse(value); }
+
            {
 +
                this.m_Label_Content = value;
 +
                this.OnPropertyChanged("Label_Content");
 +
            }
 +
             get { return this.m_Label_Content; }
 
         }
 
         }
    }
 
}
 
</syntaxhighlight>
 
 
こうやって定義すると、2つのファイルに分かれているclass定義でも、1つのclassとして扱うことができるんだ。
 
 
== 型付DataSetの作成 ==
 
列挙型を取り込みたいって悩んでいる人には、このステップは用済みかもしれないけど一応書いておくね。VisualStudioを起動してプロジェクトを開いている前提で手順をあげるよ。
 
 
<ol>
 
<li>ソリューションエクスプローラーからDataSetを追加したいプロジェクトを右クリックする。</li>
 
<li>メニューから「追加」→「新しい項目」を選択する。</li>
 
<li>(C♯の)一覧からデータセットを選択し、名前に適当なファイル名を入れ、「追加」ボタンをクリックする。</li>
 
<li>デザイナーが開くので、ツールボックスからDataTableをデザイナーにドラッグアンドドロップしてテーブルを追加する。テーブル名は適当に変更する。</li>
 
<li>デザイナーのテーブルを右クリックして、「追加」→「列」を選択し、適当な列を追加する。</li>
 
<li>必要に応じてキーを設定する</li>
 
</ol>
 
 
ここで、列挙型を保存したい列の型を「System.Int32」に変更するんだよ。
 
デザイナーで列を選択すると、プロパティウインドウに列のプロパティが表示されるんだ。その中の「DataType」プロパティの値を「System.Int32」に変更するんだよ。
 
 
== partialクラスの追加 ==
 
partialクラスを追加する方法もインターネットを調べると色々出てくるんだけど…どうやらMicrosoftはこの方法を推奨しているみたいなんだよ。
 
<ol>
 
<li>データセットデザイナを表示する。</li>
 
<li>データセットデザイナの何も書かれていないところ(余白?)をダブルクリックする。</li>
 
<li>以下のようなコードが表示される(ネームスペースが「TestLibrary」で、データセットクラス名が「DataSet1」の場合)
 
<syntaxhighlight lang="C#">
 
namespace TestLibrary {
 
   
 
   
 
    public partial class DataSet1 {
 
    }
 
}
 
</syntaxhighlight>
 
</li>
 
</ol>
 
これでDataSetのpartialクラスが追加されたんだよ。ソリューションエクスプローラーを見ても「xxxxx.xsd」ファイルの配下にこのpartialクラスのファイルが入っていて、見た目にもすっきりしていると思うんだ。
 
  
== partialクラスにメソッドを追加 ==
+
        /// <summary>
作成されたpartialクラスに列挙型で出し入れできる、プロパティを追加するよ。手順とかじゃなくて、追加したpartialクラスを以下の様に修正するんだよ。
+
        /// プロパティ変更イベントハンドラ
;namespace:TestLibrary
+
        /// </summary>
;DataSet名:DataSet1
+
        public event PropertyChangedEventHandler PropertyChanged;
;DataTable名:DataTable1
 
;DataColumn名:DataColumn1
 
;列挙型名:e_Test
 
  
<source lang="csharp">
+
        /// <summary>
namespace TestLibrary {
+
        /// プロパティ変更通知
    public partial class DataSet1 {
+
        /// </summary>
        partial class DataTable1Row
+
        /// <param name="p_PropertyName">プロパティ名</param>
 +
        public void OnPropertyChanged(String p_PropertyName)
 
         {
 
         {
             public e_Test EnumDataColumn1
+
             if (this.PropertyChanged != null)
 
             {
 
             {
                 get
+
                 this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
                {
 
                    return (e_Test)this.DataColumn1;
 
                }
 
                set
 
                {
 
                    this.DataColumn1 = (Int32)value;
 
                }
 
 
             }
 
             }
 
         }
 
         }
137行目: 208行目:
 
}
 
}
 
</source>
 
</source>
 
これで、列挙型で出し入れできるメソッドを追加することができたよ。もしWPFでバインドしたいときなどは、Int32で取り出すメソッド(この場合「DataColumn1」)でバインドすれば、特に悩むことなく実現できると思うんだ。
 
 
== 参考サイト ==
 
[https://msdn.microsoft.com/ja-jp/library/ms171896.aspx MSDN - 方法 : データセットの機能を拡張する]<br/>
 
[http://gushwell.ldblog.jp/archives/50810947.html Gushwell's C# Dev Notes - DataSetのカラムで列挙型を扱うには]
 
  
 
[[Category:C♯]]
 
[[Category:C♯]]
[[Category:Dataset]]
+
[[Category:C++]]
 +
[[Category:dll]]

2019年7月26日 (金) 07:46時点における版

久しぶりにやったら、すっかり忘れていたので覚書…

C++のDLLプロジェクト作成

まず、C++のDLLを作成するときの注意点… 作成するプロジェクトは、「Win32プロジェクト」を選択するんだ。
CShapeToCppDll-005.jpg


あと…アプリケーションの設定では「DLL」と「空のプロジェクト」を選択してね。
CShapeToCppDll-001.jpg


空のプロジェクトが作成されたら「cpp」「h」「def」ファイルを追加するんだ。今回は「CppDll.cpp」「CppDll.h」「CppDll.def」を追加したよ。


そしたら、プロジェクトのプロパティを開いて「構成プロパティ→リンカー→入力→モジュール定義ファイル」に「CppDll.def」を設定するんだ。
(DebugとReleaseでそれぞれ設定する必要があるんだよ。)
CShapeToCppDll-002.jpg

DLLのコード

ヘッダーファイル(*.h)

#ifndef DLLAPI
#define DLLAPI extern "C" __declspec(dllimport)
#endif

DLLAPI long __stdcall _Sum(const long p_Number1, const long p_Number2);

コードファイル(*.cpp)

#define DLLAPI

#include "CppDll.h"

DLLAPI long __stdcall _Sum(const long p_Number1, const long p_Number2)
{
	return p_Number1 + p_Number2;
}

モジュール定義ファイル(*.def)

LIBRARY	CppDll

EXPORTS
	_Sum

C♯のプロジェクト作成

ほとんどそのまま作るんだけど…ソリューションのコンパイル対策をしておくよ。

ビルドイベントの設定

C++のDLLはソリューションフォルダ直下の「Debug」や「Release」フォルダにDLLが格納されてしまうんだ。そうすると、デバッグするときにDLLが見つからないので、ビルドイベントを使ってコピーしてしまうよ。以下のように設定してね。(「Release」コンパイルするまでは「Release」フォルダがないのでコメントアウトしているよ)
CShapeToCppDll-004.jpg

プロジェクトの依存関係の設定

コピーするにもちゃんとリコンパイルされた資源をコピーしないといけないので、プロジェクトの依存関係を設定することで、ビルドの順番を設定するよ。
ソリューションエクスプローラーからC♯のプロジェクトを右クリックして「ビルド依存関係」→「プロジェクト依存関係」を選択してね。
「依存関係」タブの依存先にC++のプロジェクトが表示されているはずだから、チェックを入れてOKボタンをクリックしてね。

C♯のコード

MVVMモデルでサンプルを作ったからビューモデルが入っているけど…DLLを呼ぶには必要ないから無視してね。

モデル

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

//追加
using System.Runtime.InteropServices;

namespace CSharpToCDLL
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// DLLの関数定義
        /// </summary>
        /// <param name="p_Number1">数値1</param>
        /// <param name="p_Number2">数値2</param>
        /// <returns>合計</returns>
        [DllImport("CppDll.dll")]
        private extern static Int32 _Sum(Int32 p_Number1,Int32 p_Number2);

        /// <summary>
        /// 標準のコンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ボタンクリックイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント情報</param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //DLLの関数を呼び出す
            Int32 l_Result = _Sum(300, 500);

            //計算結果の表示
            MessageBox.Show("計算結果:" + l_Result.ToString());
        }
    }
}

ビュー

<Window x:Class="CSharpToCDLL.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="77.056"
        Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="5"/>
            <RowDefinition Height="24"/>
            <RowDefinition/>
            <RowDefinition Height="5"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition Width="75"/>
            <ColumnDefinition Width="5"/>
        </Grid.ColumnDefinitions>
        <Label
            Grid.Row="1"
            Grid.Column="1"
            Content="{Binding Label_Content}"/>
        <Button
            Grid.Row="1"
            Grid.Column="3"
            Content="実行"
            Click="Button_Click"/>
    </Grid>
</Window>

ビューモデル

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//追加
using System.ComponentModel;

namespace CSharpToCDLL
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// ラベル表示用変数
        /// </summary>
        private String m_Label_Content;

        /// <summary>
        /// ラベル表示文字列
        /// </summary>
        public String Label_Content
        {
            set
            {
                this.m_Label_Content = value;
                this.OnPropertyChanged("Label_Content");
            }
            get { return this.m_Label_Content; }
        }

        /// <summary>
        /// プロパティ変更イベントハンドラ
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティ変更通知
        /// </summary>
        /// <param name="p_PropertyName">プロパティ名</param>
        public void OnPropertyChanged(String p_PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }
    }
}