もくじ
やりたいこと
関数ポインタというものに久しぶりに触る機会があった。
昔から「むずかしいもの」というイメージが関数ポインタにはあったので、思い出しがてら練習をしたときのメモ。
作ったもの
構成
exe側で用意した関数を、dll側でなにかの条件判定をして、条件を満たしたときに呼んでやる、みたいな練習をしようと考えた。
で、思いついたのが世界のナベアツだったので、「条件」はそれにした。
下図がざっくりイメージ。
<ファイル構成>
成果物ファイル名 | 説明 |
---|---|
WindowsProject.exe | ダイアログベースのcppアプリ |
DllTest.dll | 上のexeから呼ばれるdll |
<コードのやることイメージ>
- 起動時、exeの持っている関数をdllに登録する。
- Button1を押したら、dllが秒数のカウントを開始。
- カウント中の秒数があの条件を満たしていたらあほになる。満たしてなかったら普通になる。
コード
WindowsProject1.cpp
#include <windows.h>
#include <string>
#include "framework.h"
#include "WindowsProject1.h"
#include "resource.h"
#include "DllTest.h"
HINSTANCE hInst;
HWND hDlgWnd;
// 世界のナベアツ 3の倍数と3がつく数字の時だけアホになります
// https://www.youtube.com/watch?v=wjXoqcrLBbA
// このコード モジュールに含まれる関数の宣言を転送します:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
hInst = hInstance;
DialogBox(hInst, L"MyTestDlgBase_Main", NULL, (DLGPROC)MyDlgProc);
return (int)0;
}
// あほに数える関数
void SanNoBaisuu(int sec)
{
SendMessage(GetDlgItem(hDlgWnd, IDC_LIST1), LB_INSERTSTRING, 0, (LPARAM)(std::to_wstring(sec) + L"ぁ~~ん").c_str());
}
// 真面目に数える関数
void SanNoBaisuuDehaNai(int sec)
{
SendMessage(GetDlgItem(hDlgWnd, IDC_LIST1), LB_INSERTSTRING, 0, (LPARAM)std::to_wstring(sec).c_str());
}
// ダイアログプロシージャ
BOOL CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG:
// 起動時一回通るところ
hDlgWnd = hDlg;
RegisterCountingMethods(SanNoBaisuu, SanNoBaisuuDehaNai); // 関数を登録
break;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDC_BUTTON1:
// ボタンを押したときに
StartCounting(); // 世界のナベアツ カウントスタート
break;
}
return FALSE;
case WM_CLOSE:
StopCounting(); // ナベアツ終了
EndDialog(hDlg, 0);
return TRUE;
}
return FALSE;
}
DllTest.cpp
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <combaseapi.h>
#include <wchar.h>
#include <thread>
#include <string>
#define VC_DLL_EXPORTS
#include "DllTest.h"
std::thread th_b;
int threadStop = 0;
// 関数ポインタ
void (*pfunc1)(int) = nullptr;// あほ
void (*pfunc2)(int) = nullptr;// 普通
// 関数を登録するための関数
void __cdecl RegisterCountingMethods(void (*pf1)(int), void (*pf2)(int))
{
pfunc1 = pf1;
pfunc2 = pf2;
}
// 3の倍数と3がつく数字かどうかを判定する関数
BOOL JudgeSaaaan(int sec)
{
auto secStr = std::to_string(sec);
int pos = secStr.find("3");
return pos != std::string::npos || sec % 3 == 0;
}
// 別スレッド起動し、カウント開始
// 現在の時計の秒数が3の倍数の時はexe側からもらったアホになる関数を呼び、
// そうでない場合はexe側からもらった普通に数える関数を呼ぶ
void __cdecl StartCounting()
{
th_b = std::thread([]
{
while (!threadStop)
{
// 時刻を取得
auto t = time(nullptr);
auto tmv = tm();
localtime_s(&tmv, &t);
// 関数呼び分け
if (JudgeSaaaan(tmv.tm_sec)) pfunc1(tmv.tm_sec);
else pfunc2(tmv.tm_sec);
Sleep(1000);
}
});
}
// カウント停止
void __cdecl StopCounting()
{
threadStop = 1;
th_b.join();
}
関数ポインタの例
関数ポインタは、**「別のヤツに自分の処理をやってもらう」もしくは「自分のタイミングで別のヤツの処理をやってやる」**ということをするために使えるのかな、ということで、それをイメージしたくて上記のような実験アプリを作ったが、基本の、関数ポインタ自体の書き方は下記のとおり。
関数ポインタの書き方
型 (*関数ポインタの名前)(引数)
型 *関数ポインタの名前(引数,,,,);←名前の前後のカッコがない。これはダメ!普通の関数定義になる
※ 関数の名前だけを書くと、関数のアドレスになり、関数ポインタに代入することができる。
例(普通の書き方)
// 関数ポインタ
int (*pfp1)(int, int);
std::wstring(*pfp2)();
// 関数本体
int func1(int a, int b) { return 10; }
std::wstring func2() { return std::wstring(L"abc"); }
// 関数本体のアドレスを関数ポインタに登録
pfp1 = func1;
pfp2 = func2;
// 呼び出し
int ret1 = pfp1(1, 2);
std::wstring ret2 = pfp2();
例(ラムダ式の書き方1)
// 関数ポインタにラムダ式を入れる
int (*pfp1)(int, int) = [](int a, int b) -> int {return 20; };
std::wstring(*pfp2)() = []() ->std::wstring {return std::wstring(L"def"); };
// 呼び出し
int ret1 = pfp1(1, 2);
std::wstring ret2 = pfp2();
例(ラムダ式の書き方2)
// 関数ポインタにラムダ式を入れてautoで受ける
auto pfp1 = [](int a, int b) -> int {return 20; };
auto pfp2 = []() ->std::wstring {return std::wstring(L"def"); };
// 呼び出し
int ret1 = pfp1(1, 2);
std::wstring ret2 = pfp2();
コード置き場所
上のコードは主だったところのみ。全部は下記にある。
https://github.com/tera1707/Cpp/tree/master/CppDllTemplate