[C++] 関数ポインタの練習

# もくじ

https://tera1707.com/entry/2022/02/06/144447

# やりたいこと

関数ポインタというものに久しぶりに触る機会があった。
昔から「むずかしいもの」というイメージが関数ポインタにはあったので、思い出しがてら練習をしたときのメモ。

# 作ったもの

## 構成

exe側で用意した関数を、dll側でなにかの条件判定をして、条件を満たしたときに呼んでやる、みたいな練習をしようと考えた。

で、思いついたのが世界のナベアツだったので、「条件」はそれにした。
下図がざっくりイメージ。

**<ファイル構成>**

|成果物ファイル名|説明|
|:--|:--|
|WindowsProject.exe|ダイアログベースのcppアプリ|
|DllTest.dll|上のexeから呼ばれるdll|

**<構成図>**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/89dfdbcf-786f-cc86-af34-f4deefff8215.png)

**<画面イメージ>**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/367eb145-ad1a-bce3-62d7-a58680446e86.png)

**<コードのやることイメージ>**

- 起動時、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();
}
```

# 関数ポインタの例

関数ポインタは、**「別のヤツに自分の処理をやってもらう」**もしくは**「自分のタイミングで別のヤツの処理をやってやる」**ということをするために使えるのかな、ということで、それをイメージしたくて上記のような実験アプリを作ったが、基本の、関数ポインタ自体の書き方は下記のとおり。

## 関数ポインタの書き方

```cpp
型 (*関数ポインタの名前)(引数)
型 *関数ポインタの名前(引数,,,,);←名前の前後のカッコがない。これはダメ！普通の関数定義になる
```

※ **関数の名前だけ**を書くと、関数のアドレスになり、関数ポインタに代入することができる。

## 例(普通の書き方)

```cpp
// 関数ポインタ
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)

```cpp
// 関数ポインタにラムダ式を入れる
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)

```cpp
// 関数ポインタにラムダ式を入れて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