VC++で、StackFrameクラスを作成し桜エディタに埋め込み

#環境

環境Windows　Visual Stdudio 2019 VC++

#デバックプリント

ファイル名と行数をプリント出す場合C言語では以下
C＃の場合「StackFrame」クラスを利用すると一つ前の関数の情報を呼び出せる。
これ便利だなぁと思って、VC++でSakuraMyStackFrameクラスを作成し
桜エディタで、以下のようなログを出力するようにしてみた。

出力形式：タイムスタンプ＋ファイル名：関数名（行数）「・・・ログ・・・」

```
[2020/10/31 07:56:04] WinMain.cpp:wWinMain(94) -- -- WinMain -- --
```


+ C/C++言語　現在の関数の情報を出力

```hoge.cpp
printf("%s(%d) hogehoge\n", __func__, __LINE__);
```


+ C＃の場合「StackFrame」クラスを利用

１つ前の場合　StackFrame(1, true)
２つ前の場合　StackFrame(2, true)

```print.cs
public static void out(string buf)
        {
            string path = Directory.GetCurrentDirectory() + "debuglog.txt";
            DateTime dt = DateTime.Now;

            string timebuf = dt.ToString("yyyy/MM/dd HH:mm:ss ");

            //「一つ前の」スタックを参照する
            StackFrame CallStack = new StackFrame(1, true);

            string sourceline = "";
            string fname = "";
            if (CallStack.GetFileName().Length > 0)
            {
                fname = Path.GetFileName(CallStack.GetFileName());
            }
            sourceline = fname + ":" + CallStack.GetMethod().Name + "(" + CallStack.GetFileLineNumber() + ")";

            string linebuf = timebuf + sourceline + buf + "\r\n";

            File.AppendAllText(path, linebuf);
        }

```


#桜エディタにクラスを追加する

桜エディタのソースコード　https://github.com/sakura-editor/sakura
のsakura_coreにmyutilフォルダ追加し以下のクラスを作成する。

SakuraMyStackFrameクラス　・・・関数のスタックを取得する
SakuraMyLogクラス　　　　　・・・ログ出力に関するクラス
SakuraMyStrクラス　　　　　・・・文字操作クラス



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547467/66d4c3db-0c3d-82d3-0cff-e30fc8383d09.png)


コールスタッフ情報を_stackに保存し、取り出しは_frameを指定する。
vector < MYSTACKFRAMES> _stack;


+ SakuraMyStackFrameクラス

```SakuraMyStackFrame.h

#ifndef _INCLUDESakuraMyStackFrame_H_
#define _INCLUDESakuraMyStackFrame_H_

#pragma once

#pragma comment(lib, "dbghelp.lib") //コールスタック利用

#include <cstddef>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <iostream>
#include <list>
#include <vector>

#include <windows.h> //MultiByteToWideChar
#include <tchar.h> // _T

using namespace std;

class MyException {
public:
	CONTEXT Context;

	MyException() {
		RtlCaptureContext(&Context);
	}
};

typedef struct _STACKFRAMES {
	std::string SymbolName;
	ULONG64 SymbolAddress;

	std::string lineFileName;
	ULONG64 lineLineNumber;

	std::string module;

}MYSTACKFRAMES;

class SakuraMyStackFrame
{
private:
	//コールスタックの取り出す番号指定
	int _frame;

	//コールスタックを保持
	vector<MYSTACKFRAMES> _stack;

public:
	SakuraMyStackFrame() {}

	SakuraMyStackFrame(int frame) {
		_frame = frame;

		_stack.clear();

		MyException e;
		printStack(&e.Context);
		//backtrace(false);
	}


	~SakuraMyStackFrame() {
		_stack.clear();
	}

	//
	void Setframe(int f) { _frame = f; };

	//コールスタック
	string backtrace();

	//メソッド
	std::string GetMethodName();	
	ULONG64 GetAddress();
	std::string GetFileFullName();	//ファイル名のフルパス
	std::string GetFileName();		//ファイル名のみ
	ULONG64 GetFileLineNumber();	//行番号
	
	//スタックトレース
	void printStack(CONTEXT* ctx); 

};



#endif // !_INCLUDESakuraMyStackFrame_H_

```

```SakuraMyStackFrame.cpp

//ファイル内で次のすべての警告をオフ
#pragma warning(disable : 4996)


#include "StdAfx.h"	//先頭で定義

#include "SakuraMyStackFrame.h"


#include <time.h>
#if !SAKURA
#include <DbgHelp.h>	
#endif
#include <vector>


string SakuraMyStackFrame::backtrace() {
//    backtrace(true);
	string rlt = "";
    vector<MYSTACKFRAMES>::iterator itr = _stack.begin();

    int cnt = 0;
    while (itr != _stack.end())
    {
		string szText;
		sprintf((char*)szText.c_str(), "%02d\tat　関数名:%s ファイル名 %s: 行番号: %lu: アドレス:0x%0x \n",
            cnt, itr->SymbolName.c_str(), itr->lineFileName.c_str(), itr->lineLineNumber, itr->SymbolAddress);

        itr++;
        cnt++;

		rlt = rlt + szText;
    }

	return rlt;
}


std::string SakuraMyStackFrame::GetMethodName() {

    std::string rlt = "";

	if (_stack.size() == 0 || _stack.size() <= _frame) {
        return rlt;
    }

    MYSTACKFRAMES s = _stack[_frame];
    return s.SymbolName;

}

ULONG64 SakuraMyStackFrame::GetAddress() {

    std::string rlt = "";

	if (_stack.size() == 0 || _stack.size() <= _frame) {
        return 0;
    }

    MYSTACKFRAMES s = _stack[_frame];
    return s.SymbolAddress;


}

std::string SakuraMyStackFrame::GetFileFullName() {

    std::string rlt = "";

	if (_stack.size() == 0 || _stack.size() <= _frame) {
        return rlt;
    }

    MYSTACKFRAMES s = _stack[_frame];
    return s.lineFileName;
}


std::string SakuraMyStackFrame::GetFileName() {

    std::string rlt = "";

    if (_stack.size() == 0 || _stack.size() <= _frame) {
        return rlt;
    }

    MYSTACKFRAMES s = _stack[_frame];

    int p1 = s.lineFileName.rfind("\\");

    int len = s.lineFileName.length();

    rlt = s.lineFileName;
    if ( p1 != -1) {
        rlt = rlt.substr(p1 + 1 , len - p1 - 1);
    }

    return rlt;
}

ULONG64 SakuraMyStackFrame::GetFileLineNumber() {

    std::string rlt = "";

	if (_stack.size() == 0 || _stack.size() <= _frame) {
        return 0;
    }

    MYSTACKFRAMES s = _stack[_frame];
    return s.lineLineNumber;
}



void SakuraMyStackFrame::printStack(CONTEXT* ctx) //Prints stack trace based on context record
{
    BOOL    result;
    HANDLE  process;
    HANDLE  thread;
    HMODULE hModule;

    STACKFRAME64        stack;
    ULONG               frame;
    DWORD64             displacement;

    DWORD disp;
    IMAGEHLP_LINE64* line;

    const int MaxNameLen = 256;
    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
    char name[MaxNameLen];
    char module[MaxNameLen];
    PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;

    memset(&stack, 0, sizeof(STACKFRAME64));

    process = GetCurrentProcess();
    thread = GetCurrentThread();
    displacement = 0;
#if !defined(_M_AMD64)
    stack.AddrPC.Offset = (*ctx).Eip;
    stack.AddrPC.Mode = AddrModeFlat;
    stack.AddrStack.Offset = (*ctx).Esp;
    stack.AddrStack.Mode = AddrModeFlat;
    stack.AddrFrame.Offset = (*ctx).Ebp;
    stack.AddrFrame.Mode = AddrModeFlat;
#endif

    SymInitialize(process, NULL, TRUE); //load symbols

    _stack.clear();

    for (frame = 0; ; frame++)
    {
        //リスト追加用
        MYSTACKFRAMES stackf;

        //get next call from stack
        result = StackWalk64
        (
#if defined(_M_AMD64)
            IMAGE_FILE_MACHINE_AMD64
#else
            IMAGE_FILE_MACHINE_I386
#endif
            ,
            process,
            thread,
            &stack,
            ctx,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        if (!result) break;

        //get symbol name for address
        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        pSymbol->MaxNameLen = MAX_SYM_NAME;
        SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, pSymbol);

        line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64));
        line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);

        //try to get line
        if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, line))
        {
            printf("	at %s in %s: line: %lu: address: 0x%0X \n", pSymbol->Name, line->FileName, line->LineNumber, pSymbol->Address);
        

            stackf.SymbolAddress = pSymbol->Address;
            stackf.SymbolName = pSymbol->Name;
            stackf.lineFileName = line->FileName;
            stackf.lineLineNumber = line->LineNumber;
            stackf.module = "";

        }
        else
        {
            //failed to get line
            printf("	at %s, address 0x%0X.", pSymbol->Name, pSymbol->Address);
                hModule = NULL;
            lstrcpyA(module, "");
            GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                (LPCTSTR)(stack.AddrPC.Offset), &hModule);

            //at least print module name
            if (hModule != NULL)GetModuleFileNameA(hModule, module, MaxNameLen);

            printf("in %s\n",module);


            stackf.SymbolAddress = pSymbol->Address;
            stackf.SymbolName = pSymbol->Name;
            stackf.lineFileName = "";
            stackf.lineLineNumber = 0;
            stackf.module = module;


        }

        _stack.push_back(stackf);

        free(line);
        line = NULL;
    }

}


```




+ SakuraMyLogクラス


```SakuraMyLog.h
#pragma once

#ifndef _INCLUDESakuraMyLog_H_
#define _INCLUDESakuraMyLog_H_


//#pragma comment(lib, "mscorlib.lib")

#if SAKURA
#include "SakuraMyStr.h"
#include "SakuraMyStackFrame.h"
#else
#include "SakuraMyStr.h"
#include "SakuraMyStackFrame.h"
#endif


#pragma region "コールスタック"

//https://raw.githubusercontent.com/gentilkiwi/mimikatz/110a831ebe7b529c5dd3010f9e7fced0d3e3a46c/inc/DbgHelp.h
#include <windows.h>
//#include <dbghelp.h> //ADDRESS KDHELP

//C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\DbgHelp.h
//に定義済み

#pragma endregion

//#define TO_STR( param ) #param
//#define PRINT( ... ) printf( __VA_ARGS__ )
//#define TO_STR_VA( ... ) #__VA_ARGS__
//#define JOIN( x, y ) x ## y

//#define func(const char* format, ...) funcDebug(__LINE__, __func__, __FILE__, const char* format, ...)
//int funcDebug(int line, char* funcname, char* filename, const char* format, ...) {
//    
//}



using namespace std;

class SakuraMyLog
{
private:
    const char* PATH_FILE_DEBUG = "debug.out";
    int _frame;
public:

    SakuraMyLog() {
        _frame = 3;
    }

    void Setframe(int f) { _frame = f; }
    
    std::string loghead();

    void debugout(const char* format, ...);

    void debugoutW(std::string buf);

    char* GetCurrentPath();
    

};

#endif // !_INCLUDESakuraMyLog_H_

```

保存するファイルパスは適当に変更して下さい
mystr->writeW("G:\\plog\\vs2019\\2020_1022_UtilDLL\\sakuradebug.txt", buf);


```SakuraMyLog.cpp


#include "StdAfx.h" //先頭に追加 C1010対応

#include "SakuraMyLog.h"
#include <stdio.h>
#include <tchar.h> // _T
#include <string>
#include <io.h>
#include <csignal>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.h>



std::string SakuraMyLog::loghead() {
    std::string rlt = "";
    SakuraMyStr* mystr = new SakuraMyStr();
    SakuraMyStackFrame* mystack = new SakuraMyStackFrame(_frame);

    char line[10240];
    char tm[255];

    strcpy(tm, mystr->timestamp(1));

    if (mystack->GetFileName().length() > 0 && mystack->GetMethodName().length() > 0) {
        sprintf(line, "[%s] %s:%s(%d) ",
            tm, mystack->GetFileName().c_str(), mystack->GetMethodName().c_str(), mystack->GetFileLineNumber());
    }
    else if (mystack->GetMethodName().length() > 0) {
        sprintf(line, "[%s] %s(%d) ",
            tm, mystack->GetMethodName().c_str(), mystack->GetFileLineNumber());
    }
    else {
        sprintf(line, "[%s] -(%d) ",
            tm, mystack->GetFileLineNumber());
    }

    rlt = line;

    free(mystr);
    free(mystack);

    return rlt;

}

void SakuraMyLog::debugout(const char* fmt, ...) {

    SakuraMyStr* mystr = new SakuraMyStr();
    //mystack->backtrace();

    char line[10240];

	try {
        strcpy(line, loghead().c_str());
		int size = strlen(line);

		va_list ap;
		// 可変長引数を１個の変数にまとめる
		va_start(ap, fmt);

		// まとめられた変数で処理する
		vsprintf(&line[size], fmt, ap);
		
        va_end(ap);
		// 戻り値省略
        mystr->write("G:\\plog\\vs2019\\2020_1022_UtilDLL\\sakuradebug.txt", (const char*)line);

		free(mystr);

	}
	catch (...) {
		std::cout << "err" << ::GetLastError() << std::endl;
	}
}

void SakuraMyLog::debugoutW(std::string buf) {

    SakuraMyStr* mystr = new SakuraMyStr();


    try {

        //保存するファイルパス
        mystr->writeW("G:\\plog\\vs2019\\2020_1022_UtilDLL\\sakuradebug.txt", buf);

        free(mystr);

    }
    catch (...) {
        std::cout << "err" << ::GetLastError() << std::endl;
    }
}


char* SakuraMyLog::GetCurrentPath() {

    char rlt[1024];

    TCHAR szBuf[MAX_PATH];
    (void)GetCurrentDirectory(_countof(szBuf), szBuf);

    sprintf(rlt, "%s\\%s", szBuf, PATH_FILE_DEBUG);

    return rlt;
}

```



+ SakuraMyStrクラス


```SakuraMyStr.h
#pragma once

#ifndef  _INCLUDE_SakuraMyStr_H_
#define  _INCLUDE_SakuraMyStr_H_

#include <cstddef>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <iostream>
#include <list>
#include <fstream>   // ifstream, ofstream

#include <vector>
#include <string>
//
#include <windows.h> //MultiByteToWideChar
#include <tchar.h> // _T


//C++で静的配列の要素数を求めるテンプレート関数
#define COUNT(arry)(sizeof(arry) / sizeof(arry[0]));

using namespace std;

#define MAX_BUF 0x7FFFFFFF

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
//                           定数                              //
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
// SJISのコードページ(CP_ACP では無くこれを使えばおそらく英語版Winでも動くはず。)	2008/5/12 Uchi
#define CP_SJIS		932

class SakuraMyStr
{
private:

public:
	SakuraMyStr() {	}

	~SakuraMyStr() {	}


	//書き込み
	bool write(const char* path, const char* buff);
	bool writeW(std::string, const std::string buff);

	//タイムスタンプ
	char* timestamp(int type);

};

#endif // ! 


```

```SakuraMyStr.cpp

//ファイル内で次のすべての警告をオフ
#pragma warning(disable : 4996)


#include "StdAfx.h"	//先頭で定義

#include "SakuraMyStr.h"
#include <time.h>
#if SAKURA
#include <assert.h>
#else
#include <DbgHelp.h>
#include <cassert>
#endif


/*
モード	働き
"r"	読み出し(のみの)モードでファイルをオープンする。ファイルが存在しないときはオープンできない。
"w"	書き込み(のみの)モードでファイルをオープンする。ファイルがなければ作成し、存在していれば最初にその内容を空にする。
"a"	追加モードでファイルをオープンする。ファイルがなければ作成し、存在していれば、現在の内容はそのままで、ファイル末尾に追加書き込みが行われる。
"r+"	読み書き両用のモードでファイルをオープンする。ファイルが存在しないときはオープンできない。
"w+"	読み書き両用のモードでファイルをオープンする。ファイルがなければ作成し、存在していれば最初にその内容を空にする。
"a+"	読み書き両用のモードでファイルをオープンする。ファイルがなければ作成し、存在していれば、現在の内容はそのままで、ファイル末尾に追加書き込みが行われる。
*/
bool SakuraMyStr::write(const char* path, const char* buff) {
    FILE* fout;         // 出力ストリーム

    fout = fopen(path, "a");  

    fprintf(fout, "%s", buff);

    fclose(fout);
    return true;
}

//SJIS保存
bool SakuraMyStr::writeW(std::string path, const std::string buff) {
    
    
    std::ofstream writing_file;
    writing_file.open(path, std::ios::app);

    
    writing_file << buff;
    
    return true;
}



char* SakuraMyStr::timestamp(int type) {

    time_t timer;        /* 時刻を取り出すための型（実際はunsigned long型） */
    struct tm* local;    /* tm構造体（時刻を扱う */

    /* 年月日と時分秒保存用 */
    int year, month, day, hour, minute, second;

    timer = time(NULL);        /* 現在時刻を取得 */
    local = localtime(&timer);    /* 地方時に変換 */

    /* 年月日と時分秒をtm構造体の各パラメタから変数に代入 */
    year = local->tm_year + 1900;        /* 1900年からの年数が取得されるため */
    month = local->tm_mon + 1;        /* 0を1月としているため */
    day = local->tm_mday;
    hour = local->tm_hour;
    minute = local->tm_min;
    second = local->tm_sec;

    char line[256];
    /* 現在の日時を表示 */
    if (type == 0) {

        sprintf(line, "%04d年%02d月%02d日 %02d時%02d分%02d秒", year, month, day, hour, minute, second);
    }
    else if (type == 1) {
        sprintf(line, "%04d/%02d/%02d %02d:%02d:%02d", year, month, day, hour, minute, second);
    }
    else {
        sprintf(line, "%04d%02d%02d%02d%02d%02d", year, month, day, hour, minute, second);
    }

    return line;

}

```


# 桜エディタのプロパティ
※必要であれば定義する。自分の場合、他で利用してプリプロセッサの定義で切り分けている。
プロパティ＞構成プロパティ＞プリプロセッサ＞プリプロセッサの定義に　SAKURA　を追記

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547467/4c4eb6fb-fa08-eb90-c5a0-d216fde81013.png)


桜エディタのプロパティの追加インクルードディレクトリに、新規作成のパスを指定
例：
G:\plog\vs2019\2020_1022_UtilDLL\UtilDLL_master\CplusDLL\Sakura

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547467/870fa501-b934-176f-a5bc-f181da4046e4.png)




# 桜エディタに埋め込む

追加したクラスを利用する
WinMain.cpp　の　DEBUG_TRACEや、MYTRACEのメッセージを出力するファイルの呼び出し先がDebug1.cpp

```sakura-master\sakura_core\_main\WinMain.cpp
	//開発情報
	DEBUG_TRACE(L"-- -- WinMain -- --\n");
```

StdAfx.hを修正する。最終行あたりに以下３行追加

```sakura-master\sakura_core\StdAfx.h

（省略）

#include "SakuraMyStr.h"
#include "SakuraMyLog.h"
#include "SakuraMyStackFrame.h"

```

Debug1.cppに以下を追記　

//追加
mylog.Setframe(3); //３つ前の関数を参照している。
std::string log = mylog.loghead(); //Debugログのタイムスタンプとファイル名＋行数を作成
log += wcstombs_new(szText);　//SJISをUNICODEに変換して連結している。
mylog.debugoutW(log);　//出力




```\sakura-master\sakura_core\debug\Debug1.cpp
/*!	@file
	@brief デバッグ用関数

	@author Norio Nakatani

	@date 2001/06/23 N.Nakatani DebugOut()に微妙～な修正
	@date 2002/01/17 aroka 型の修正
	@date 2013/03/03 Uchi MessageBox用関数を分離
*/
/*
	Copyright (C) 1998-2001, Norio Nakatani
	Copyright (C) 2002, aroka

	This source code is designed for sakura editor.
	Please contact the copyright holder to use this code for other purpose.
*/

#include "StdAfx.h"
#include "debug/Debug1.h"

//追加
#include "SakuraMyLog.h"

#include <stdio.h>
#include <stdarg.h>

#if defined(_DEBUG) || defined(USE_RELPRINT)

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
//                   メッセージ出力：実装                      //
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

/*! @brief 書式付きデバッガ出力

	@param[in] lpFmt printfの書式付き文字列

	引数で与えられた情報をDebugStringとして出力する．
*/
void DebugOutW( LPCWSTR lpFmt, ...)
{
	static WCHAR szText[16000];
	SakuraMyLog mylog;	//追加

	va_list argList;
	va_start(argList, lpFmt);

	//整形
	int ret = _vsnwprintf_s( szText, _TRUNCATE, lpFmt, argList );

	//出力
	if( errno != EINVAL ){
		::OutputDebugStringW( szText );

		//追加
		mylog.Setframe(3);
		std::string log = mylog.loghead();
		log += wcstombs_new(szText);
		mylog.debugoutW(log);

	}

	//切り捨て対策
	if( -1 == ret && errno != ERANGE ){
		::OutputDebugStringW( L"(切り捨てました...)\n" );

		::DebugBreak();

		int count = _vscwprintf( lpFmt, argList );
		auto pLargeBuf = std::make_unique<WCHAR[]>( count + 1 );
		if( vswprintf( &pLargeBuf[0], count + 1, lpFmt, argList ) > 0 )
			::OutputDebugStringW( &pLargeBuf[0] );
	}

	va_end(argList);

	//ウェイト
	::Sleep(1);	// Norio Nakatani, 2001/06/23 大量にトレースするときのために
}

#endif	// _DEBUG || USE_RELPRINT


```

こうすると、以下のようなログを出力する.
[2020/10/31 07:56:04] WinMain.cpp:wWinMain(94) -- -- WinMain -- --


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547467/74ea70b0-069b-52f0-a8f2-4c8f07e2eb79.png)

もし、意図しないログの場合、mylog.Setframe(3)　の値を変更してみて下さい。

以上


参考URL：https://www.366service.com/jp/qa/24585ff2ce46fd450143a4b58ef0af8b