VC++の.slnファイルを解析するよい方法が見つからないので書いてみた。
.slnファイルは独自形式だが、同時に単純かつ厳密なフォーマットらしいので割と簡単に書ける。
https://suzulang.com/wp-content/uploads/2025/06/winslnrw.zip
#include <iostream> #include <list> #include <memory> #include <string> #include <vector> #include <fstream> #include <unordered_map> #include <stack> #include <regex> /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // std::stringから先頭の空白を削除する関数 std::string trimLeadingWhitespace(const std::string& str) { size_t start = str.find_first_not_of(" \t\n\r"); return (start == std::string::npos) ? "" : str.substr(start); } // std::stringから、冒頭のアルファベットのみの文字列を取得。 std::string getLeadingAlphabet(const std::string& str) { size_t end = str.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); return (end == std::string::npos) ? str : str.substr(0, end); } bool isSectionMarks(const std::string& str) { // セクションの開始を示す文字列かどうかを判定 // ここでは例として "Project" と "Global" をセクションの開始とする return str == "Project" || str == "ProjectSection" || str == "EndProjectSection" || str == "EndProject" || str == "Global" || str == "GlobalSection" || str == "EndGlobalSection" || str == "EndGlobal" ; }
// slnファイルの各行を表すクラス class LineData { protected: std::string line; public: void setLine(const std::string& text) { line = text; } const std::string& getLine() const { return line; } std::string& getLine() { return line; } };
// ヘッダはセクション扱いとはしないで各行を独自の情報とみなす class HeaderLine :public LineData { protected: std::regex pattern; public: HeaderLine(std::string pat) :pattern(pat) { } virtual std::string getVersion() const{ std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); return text.substr(match.position(1), match.length(1)); } virtual void setVersion(const std::string& newvalue) { std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); text.replace(match.position(1), match.length(1), newvalue); setLine(text); } };
// Visual Studioのバージョン情報を保持するクラス class VisualStudioVersion : public HeaderLine { public: VisualStudioVersion(): HeaderLine(R"(^VisualStudioVersion\s*=\s*([0-9.]+))") { } };
// 最小Visual Studioのバージョン情報を保持するクラス class MinimumVisualStudioVersion : public HeaderLine { public: MinimumVisualStudioVersion(): HeaderLine(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))") { } };
// ソリューションファイルのフォーマットバージョンを保持するクラス class FormatVersion : public HeaderLine { public: FormatVersion(): HeaderLine(R"(Format Version ([0-9.]+))") { } };
enum class HeaderLineTypeT { HL_VisualStudioVersion, HL_MinimumVisualStudioVersion, HL_FormatVersion, HL_ERROR }; HeaderLineTypeT HeaderLineType(const std::string& line) { std::regex format_version_regex(R"(Format Version ([0-9.]+))"); std::regex visual_studio_version_regex(R"(^VisualStudioVersion\s*=\s*([0-9.]+))"); std::regex minimum_visual_studio_version_regex(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))"); std::smatch match; if (std::regex_search(line, match, format_version_regex)) { return HeaderLineTypeT::HL_FormatVersion; } else if (std::regex_search(line, match, visual_studio_version_regex)) { return HeaderLineTypeT::HL_VisualStudioVersion; } else if (std::regex_search(line, match, minimum_visual_studio_version_regex)) { return HeaderLineTypeT::HL_MinimumVisualStudioVersion; } return HeaderLineTypeT::HL_ERROR; }
using SlnFileLineSPtr = std::shared_ptr<LineData>;
class Section : public std::enable_shared_from_this<Section> { protected: std::list< SlnFileLineSPtr > ::iterator head; std::list< SlnFileLineSPtr > ::iterator tail; std::string _sectionType; public: std::list < std::shared_ptr<Section> > subSections; // サブセクションのリスト std::shared_ptr<Section> This() { return shared_from_this(); } std::list < std::shared_ptr<Section> >& getSubSections(){ // グローバルセクションのサブセクションを取得するメソッド return subSections; } virtual void update() {} Section(std::string sectionname, std::list< SlnFileLineSPtr >::iterator shead) { head = shead; _sectionType = sectionname; } std::string getSectionType()const { return _sectionType; } void setSectionTail(std::list< SlnFileLineSPtr >::iterator stail) { tail = stail; } std::list< SlnFileLineSPtr > ::iterator getSectionBegin() const { return head; } std::list< SlnFileLineSPtr > ::iterator getSectionTail() const { return tail; } std::list< SlnFileLineSPtr > ::iterator getSectionEnd() const { auto itr = tail; itr++; return itr; } };
class GlobalSection : public Section { protected: std::string* sectionHeadLine() { return & (*getSectionBegin())->getLine(); } const std::string* sectionHeadLine() const{ return & (*getSectionBegin())->getLine(); } std::regex Pattern; public: GlobalSection(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { Pattern = std::regex(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); } std::string getSectionName()const{ std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { // セクション名をスライスで保存 slice = sectionHeadLine()->substr(match.position(1), match.length(1)); } return slice; } void setSectionName(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(1), match.length(1), newvalue); } } void setSectionTiming(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(2), match.length(2), newvalue); } } std::string getSectionTiming() const { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { slice = sectionHeadLine()->substr(match.position(2), match.length(2)); } return slice; } };
class SolutionConfigurationPlatforms : public GlobalSection { public: SolutionConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ProjectConfigurationPlatforms : public GlobalSection { public: ProjectConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class SolutionProperties: public GlobalSection { public: SolutionProperties(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ExtensibilityGlobals : public GlobalSection { public: ExtensibilityGlobals(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} };
class Header { std::list< SlnFileLineSPtr > lines; std::weak_ptr<VisualStudioVersion> vs_ver; std::weak_ptr<MinimumVisualStudioVersion> minivs_ver; std::weak_ptr<FormatVersion> f_ver; public: std::weak_ptr<VisualStudioVersion> getVisualStudioVersion(){ return vs_ver; } std::weak_ptr<MinimumVisualStudioVersion> getMinimumVisualStudioVersion(){ return minivs_ver; } std::weak_ptr<FormatVersion> getFormatVersion(){ return f_ver; } SlnFileLineSPtr push_back(SlnFileLineSPtr line) { SlnFileLineSPtr ptr; switch (HeaderLineType(line->getLine())) { case HeaderLineTypeT::HL_FormatVersion: { auto ptmp = std::make_shared<FormatVersion>(); ptmp->setLine(line->getLine()); f_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_VisualStudioVersion: { auto ptmp = std::make_shared<VisualStudioVersion>(); ptmp->setLine(line->getLine()); vs_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_MinimumVisualStudioVersion: { auto ptmp = std::make_shared<MinimumVisualStudioVersion>(); ptmp->setLine(line->getLine()); minivs_ver = ptmp; ptr = ptmp; break; } default: ptr = line; } lines.push_back(ptr); return ptr; } std::list< SlnFileLineSPtr > getLines() { return lines; } };
class Project :public Section{ std::string* sectionHeadLine() { return &getSectionBegin()->get()->getLine(); } const std::string* sectionHeadLine() const { return &getSectionBegin()->get()->getLine(); } std::regex projectPattern; std::string getSubstr(const int pos)const { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); return sectionHeadLine()->substr(match.position(pos), match.length(pos)); } void setReplace(const int pos,const std::string& newvalue) { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); sectionHeadLine()->replace(match.position(pos), match.length(pos), newvalue); } public: Project(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { projectPattern = std::regex(R"xxx(Project\("\{([A-F0-9\-]+)\}"\)\s*=\s*"([^"]+)",\s*"([^"]+)",\s*"\{([A-F0-9\-]+)\}")xxx", std::regex::ECMAScript | std::regex::icase); } // プロジェクトの種類をGUID形式で取得するメソッド std::string getProjectTypeGUID() const {return getSubstr(1);} // プロジェクト名を取得するメソッド std::string getProjectName() const {return getSubstr(2);} // プロジェクトのパスを取得するメソッド std::string getProjectPath() const {return getSubstr(3);} // プロジェクトのGUIDを取得するメソッド std::string getProjectGUID() const {return getSubstr(4);} //////////////////////////////////////////////////////////////////////// void setProjectTypeGUID(const std::string& newvalue) {setReplace(1, newvalue);} void setProjectName(const std::string& newvalue) {setReplace(2, newvalue);} void setProjectPath(const std::string& newvalue) {setReplace(3, newvalue);} void setProjectGUID(const std::string& newvalue) {setReplace(4, newvalue);} //////////////////////////////////////////////////////////////////////// };
class Global :public Section { std::unordered_map<std::string , std::weak_ptr<GlobalSection> > subSectionsMap; // サブセクションのマップ public: Global(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) :Section(sectionname,line) { } virtual void update()override { auto& subsections = getSubSections(); for (auto s : subsections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(s); subSectionsMap[gs->getSectionName()] = gs; } } auto getGlobalSections() { return getSubSections(); } std::weak_ptr<SolutionConfigurationPlatforms> getSolutionConfigurationPlatforms(){ auto it = subSectionsMap.find("SolutionConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionConfigurationPlatforms>(); return std::dynamic_pointer_cast<SolutionConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<ProjectConfigurationPlatforms> getProjectConfigurationPlatforms() { auto it = subSectionsMap.find("ProjectConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<ProjectConfigurationPlatforms>(); return std::dynamic_pointer_cast<ProjectConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<SolutionProperties> getSolutionProperties() { auto it = subSectionsMap.find("SolutionProperties"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionProperties>(); return std::dynamic_pointer_cast<SolutionProperties>((*it).second.lock()); } std::weak_ptr<ExtensibilityGlobals> getExtensibilityGlobals() { auto it = subSectionsMap.find("ExtensibilityGlobals"); if (it == subSectionsMap.end()) return std::weak_ptr<ExtensibilityGlobals>(); return std::dynamic_pointer_cast<ExtensibilityGlobals>((*it).second.lock()); } };
// slnファイルを管理するクラス。 // slnファイルフォーマットは原則余計な場所で改行されないので、1行単位で保存 class SlnFileLines { std::list< SlnFileLineSPtr > _lines; std::unordered_map<std::string,std::string> _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; std::string _slnFilename; public: std::shared_ptr<Header> header; // ヘッダー部分 std::vector< std::shared_ptr<Project> > projects; // プロジェクトセクションのポインタ std::shared_ptr<Global> global; // グローバルセクションのポインタ std::list< SlnFileLineSPtr > getLines() const { return _lines; } std::vector< std::shared_ptr<Project> > getProjects() const { // プロジェクトセクションのポインタを返す return projects; } SlnFileLines() { // コンストラクタでセクションマークを初期化 _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; header = std::make_shared<Header>(); // ヘッダー部分を初期化 } std::shared_ptr<Section> createNewSection(std::list< SlnFileLineSPtr >::iterator line_itr,std::string linehead) { std::shared_ptr<Section> newsection; // 各セクションへのショートカット if (linehead == "Project") { std::shared_ptr<Project> newproject= std::make_shared<Project>(linehead,line_itr); projects.push_back(newproject); newsection = newproject; } else if (linehead == "Global") { std::shared_ptr<Global> newglobal = std::make_shared<Global>(linehead,line_itr); global = newglobal; newsection = newglobal; } else if (linehead == "GlobalSection") { std::regex pattern(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); std::smatch match; std::string name; if (std::regex_match((*line_itr)->getLine(), match, pattern)) { name = match.str(1); } if (name == "SolutionConfigurationPlatforms") { newsection = std::make_shared<SolutionConfigurationPlatforms>(linehead,line_itr); } else if (name == "ProjectConfigurationPlatforms") { newsection = std::make_shared<ProjectConfigurationPlatforms>(linehead, line_itr); } else if (name == "SolutionProperties") { newsection = std::make_shared<SolutionProperties>(linehead, line_itr); } else if (name == "ExtensibilityGlobals") { newsection = std::make_shared<ExtensibilityGlobals>(linehead, line_itr); } else { newsection = std::make_shared<GlobalSection>(linehead, line_itr); } } else { newsection = std::make_shared<Section>(linehead, line_itr); } return newsection; } void Load(const std::string& filePath) { _slnFilename = filePath; // ファイルオープン std::ifstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } ///////////////////////////////////////////////////////////////////////////////// // ファイルから行を読み込み、リストに追加 std::string line; while (std::getline(file, line)) { if (!line.empty()) { // 空行は無視 SlnFileLineSPtr lineData = std::make_shared<LineData>(); lineData->setLine(line); _lines.push_back(lineData); } } ///////////////////////////////////////////////////////////////////////////////// // ヘッダー部分の抽出 auto line_itr = _lines.begin(); for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if( isSectionMarks(linehead) == false) { // ヘッダー部分に追加 *line_itr = header->push_back(*line_itr); } else { break; } } ///////////////////////////////////////////////////////////////////////////////// // セクションの開始 std::stack< std::shared_ptr<Section> > currentSection; std::shared_ptr<Section> root = std::make_shared<Section>("root",_lines.end()); // ルートセクションを初期化 currentSection.push(root); // ルートセクションをスタックに追加 for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if (_sectionMarks.find(linehead) != _sectionMarks.end()) { // セクションの開始 std::shared_ptr<Section> newSection = createNewSection(line_itr,linehead); currentSection.top()->subSections.push_back(newSection); currentSection.push(newSection); } else { std::string endSectionMark = _sectionMarks[currentSection.top()->getSectionType()]; if (linehead == endSectionMark) { // セクションの終了 if (!currentSection.empty()) { currentSection.top()->setSectionTail(line_itr); currentSection.top()->update(); currentSection.pop(); } } } } global->update(); } void Save() { // ファイルオープン std::ofstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } for (auto itr = _lines.begin(); itr != _lines.end(); ++itr) { file << (*itr)->getLine() << std::endl; } } };
/////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// void disp(SlnFileLines& sln);
void ChangeProjectTest(SlnFileLines& sln) { std::string BeforeProjName = "ConsoleApp1"; std::string AfterProjName = "MyNewProjectName"; // 編集 auto projects = sln.getProjects(); for (auto p : projects) { std::string name = p->getProjectName(); if (name == BeforeProjName) { p->setProjectName(AfterProjName); std::string pathstr = p->getProjectPath(); std::regex pattern( std::string("^") + BeforeProjName + "\\\\"); std::smatch match; std::regex_search(pathstr, match, pattern); pathstr = std::regex_replace(pathstr, pattern, AfterProjName + "\\"); p->setProjectPath(pathstr); } } }
int main() { SlnFileLines sln; sln.Load(R"(C:\test\ConsoleApp1.sln)"); // 編集前 disp(sln); // 編集 std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; ChangeProjectTest(sln); std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; // 編集後 disp(sln); sln.Save(); }
void disp(SlnFileLines& sln) { std::cout << "FormatVersion: " << sln.header->getFormatVersion().lock()->getVersion() << std::endl; std::cout << "VisualStudioVersion: " << sln.header->getVisualStudioVersion().lock()->getVersion() << std::endl; std::cout << "MinimumVisualStudioVersion: " << sln.header->getMinimumVisualStudioVersion().lock()->getVersion() << std::endl; auto projects = sln.getProjects(); for (auto p : projects) { std::cout << p->getSectionType() << std::endl; std::cout << p->getProjectGUID() << std::endl; std::cout << p->getProjectName() << std::endl; std::cout << p->getProjectPath() << std::endl; std::cout << p->getProjectTypeGUID() << std::endl; std::cout << "-------------------" << std::endl; } auto sections = sln.global->getGlobalSections(); for (auto ss : sections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(ss); ; std::cout << "-------------------" << std::endl; std::cout << "1 " << gs->getSectionType() << std::endl; std::cout << "2 " << gs->getSectionName() << std::endl; std::cout << "3 " << gs->getSectionTiming() << std::endl; std::cout << "4 " << (*gs->getSectionBegin())->getLine() << std::endl; std::cout << "5 " << (*gs->getSectionTail())->getLine() << std::endl; std::cout << "=================" << std::endl; } }
MSBuild APIを使おうとした。まず.NET系のAPIなのでC#でプロジェクトを作る。.NET Framework 4.8.1が必要。
プロジェクトを作ったら右クリック→NuGet パッケージの管理へ行き、
・Microsoft.Build
・Microsoft.Build.Locator
をインストール。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Build.Locator; using Microsoft.Build.Evaluation; namespace Project4 {
internal class Class1 { static void Main(string[] args) { // Microsoft.Build.dllのロードを行うので、最初に実行しておく必要がある。 MSBuildLocator.RegisterDefaults(); // 何を置いても先にMicrosoft.Build.dllをロードしておかなければいけないが、 // Class1の中でnew Project()をすると、Class1のJITが実行されたタイミング(プログラム実行前)に // Microsoft.Build.dllがロードされる。 // すると // 1. JITによるMicrosoft.Build.dllのロード // 2. MSBuildLocator.RegisterDefaults()によるMicrosoft.Build.dllのロード // の順番になり、ランタイムエラーとなる。 // それを避けるため、Projectクラスを使うのはMyUseProjectクラスに移動している。 // // これによりMyUseProjectが使われるこの時点で // new Project()を含むコードがJITされるため、順序の問題が解消される。 MyUseProject.Run(); // 待機 Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
internal class MyUseProject { public static void Run() { // プロジェクトファイルを読み込み var project = new Project("ConsoleApp1-MSBuildAPI.csproj"); // プロジェクトのプロパティを取得 var ret = project.GetPropertyValue("TargetFramework"); // プロパティの値を表示 Console.WriteLine($"TargetFramework: {ret}"); } }
}
AssocQueryStringWを使ってファイルの拡張子または存在するファイルから関連付けられたアプリケーションを特定できる。
#include <iostream> #include <windows.h> #include <shlwapi.h> #include <string> #include <vector> #pragma comment(lib, "shlwapi.lib")
std::wstring CallAssocQueryString(const std::wstring& filepath, ASSOCSTR str) { DWORD len = 0; // まずは、必要なバッファサイズを取得 // lenにはnull終端を含む文字列の長さが返される HRESULT hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), // 拡張子".txt"等、 または存在するファイルパス nullptr, nullptr,// 結果を格納するバッファを指定するが、先にサイズを取得するためnullptrを指定 &len // 結果を格納するのに必要なバッファサイズを取得 ); if (hr != S_OK && hr != S_FALSE) { std::wcerr << L"AssocQueryStringW failed with error: " << hr << std::endl; return L""; } // バッファサイズがわかったので、結果を格納するバッファを確保 std::vector<wchar_t> appPath(len , L'\0'); hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), nullptr, &appPath[0], &len ); return std::wstring(appPath.data()); }
int main() { std::wstring target = L".sln"; // 拡張子から関連付けられたアプリケーションのパスを取得 std::wstring appPath = CallAssocQueryString(target, ASSOCSTR_EXECUTABLE); std::wcout << appPath << std::endl; // 関連付けられたアプリケーションの表示名を取得。 // ただし登録されていない場合も多いので、必ずしも取得できるとは限らない。 std::wstring appName = CallAssocQueryString(target, ASSOCSTR_FRIENDLYAPPNAME); std::wcout << appName << std::endl; // 関連付けられたアプリケーションの実行コマンドを取得 std::wstring appCommand = CallAssocQueryString(target, ASSOCSTR_COMMAND); std::wcout << appCommand << std::endl; return 0; }
windowsではアプリケーションの第一引数にファイルを与えるのが一般的らしいのでASSOCSTR_EXECUTABLEで取得した アプリケーションのパス + ファイルパスでもいいが、より確実にはASSOCSTR_COMMANDで取得した実行コマンドの %1 の部分をファイルパスに置換した方が確実。
#include <iostream> #include <windows.h> #include <shlwapi.h> #include <string> #include <vector> #include <regex> #pragma comment(lib, "shlwapi.lib")
std::wstring CallAssocQueryString(const std::wstring& filepath, ASSOCSTR str) { DWORD len = 0; // まずは、必要なバッファサイズを取得 // lenにはnull終端を含む文字列の長さが返される HRESULT hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), // 拡張子".txt"等、 または存在するファイルパス nullptr, nullptr,// 結果を格納するバッファを指定するが、先にサイズを取得するためnullptrを指定 &len // 結果を格納するのに必要なバッファサイズを取得 ); if (hr != S_OK && hr != S_FALSE) { std::wcerr << L"AssocQueryStringW failed with error: " << hr << std::endl; return L""; } // バッファサイズがわかったので、結果を格納するバッファを確保 std::vector<wchar_t> appPath(len , L'\0'); hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), nullptr, &appPath[0], &len ); return std::wstring(appPath.data()); }
int main() { std::wstring target = LR"(C:\test\test.txt)"; // 関連付けられたアプリケーションの実行コマンドを取得 std::wstring appCommand = CallAssocQueryString(target, ASSOCSTR_COMMAND); std::wcout << appCommand << std::endl; // appCommandは、"C:\Path\To\App.exe" "%1" のような形式 // "%1"をファイルパスに置き換えるとApp.exeでファイルを開ける std::wregex percent1_pattern(LR"((\"?)%1(\"?))"); // %1 または "%1" にマッチ std::wstring quoted_path = L"\"" + target + L"\""; std::wstring command = std::regex_replace(appCommand, percent1_pattern, quoted_path); std::wcout << "Command: " << command << std::endl; STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi; BOOL ret = CreateProcessW( nullptr, &command[0], // 開きたいファイルを含むコマンド nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi ); return 0; }
CreateProcessで外部アプリケーションを起動するとき、アプリケーションによっては現在の環境変数が有効でないと起動できない場合がある。lpEnvironmentが非nullptrの場合、現在の環境変数が反映されないらしく、この場合は自分で取得して、それに今回自分が必要な値を加えて渡してやるという形をとる。
GetEnvironmentStringsを使用すれば、環境変数設定をまるごと取得できる。
#include <iostream> #include <windows.h> //#include <shlwapi.h> #include <string> #include <vector> // 現在の環境変数一覧を取得 void GetCurrentEnvironemts() { // \0区切りの環境変数を一括取得する。 // 例: // SystemRoot=C:\WINDOWS\0SystemDrive=C:\0\0 LPWCH envStrings = GetEnvironmentStringsW(); LPWCH p = envStrings; while(*p != 0) { // 現在のpから\0までの文字列を取得 std::wstring envVar(p); std::wcout << envVar << std::endl; // 次の環境変数へ移動 // p+envVar.size が \0 を表すので、+1すると次の環境変数の先頭 p += envVar.size() + 1; } FreeEnvironmentStringsW(envStrings); } int main() { GetCurrentEnvironemts(); }
NAME=VALUE\0の形で取得できるので、必要であれば加工して渡してもいい。
今回はただCreateProcessの呼び出しに継承したいだけなので、環境変数の終端\0\0から\0を一つとり、その後に自分が設定したい値を加える。
#include <iostream> #include <windows.h> #include <string> #include <vector>
// 現在の環境変数一覧を取得 std::wstring GetCurrentEnvironemts() { // \0区切りの環境変数を一括取得する。 // 例: // SystemRoot=C:\WINDOWS\0SystemDrive=C:\0\0 LPWCH envStrings = GetEnvironmentStringsW(); std::wstring allenv; LPWCH p = envStrings; while(*p != 0) { // 現在のpから\0までの文字列を取得 std::wstring envVar(p); allenv += envVar; allenv.push_back(L'\0'); // 環境変数の区切り // 次の環境変数へ移動 // p+envVar.size が \0 を表すので、+1すると次の環境変数の先頭 p += envVar.size() + 1; } FreeEnvironmentStringsW(envStrings); return allenv; // 末尾は\0なので、このまま与えたいときは\0をもう一つ追加 }
int main() { ///////////////////////////////////////////////// // 現在の環境変数を取得 std::wstring all_environments = GetCurrentEnvironemts(); ///////////////////////////////////////////////// // 自分が追加したい環境変数 std::wstring envVar1 = L"MY_ENV_STR=HelloWorld"; std::wstring envVar2 = L"MY_ENV_INT=12345"; // 追加可能な形に整形 // 環境変数の終端はNULL文字 std::wstring envVars; envVars += envVar1; envVars.push_back(L'\0'); // 環境変数の区切り envVars += envVar2; envVars.push_back(L'\0'); // 環境変数の区切り // ここまでで、 // envVars=="MY_ENV_STR=HelloWorld\0MY_ENV_INT=12345\0" // all_environmentsの末尾にenvVarsを追加 std::wstring new_environments = all_environments + envVars; // 最後に\0を追加して\0\0にする new_environments.push_back(L'\0'); ///////////////////////////////////////////////// // アプリケーション起動 STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi; std::wstring apppath = L"notepad.exe"; BOOL ret = CreateProcessW( nullptr, &apppath[0], // 破壊的パースがされるため非constで渡す nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, (void*)new_environments.c_str(), nullptr, &si, &pi ); }
CreateProcessで環境変数を設定してアプリケーションを起動できる。
テスト用の環境変数を表示するプログラム。
// プログラム1 環境変数を表示 // display_myenvs.exe #include <iostream> #include <windows.h> int main() { char EnvVal[32767]; size_t limit = sizeof(EnvVal) / sizeof(EnvVal[0]); std::cout << "------------------------" << std::endl; // 環境変数を取得 GetEnvironmentVariableA("MY_ENV_STR", EnvVal, limit); std::cout << "MY_ENV_STR: " << EnvVal << std::endl; std::cout << "------------------------" << std::endl; GetEnvironmentVariableA("MY_ENV_INT",EnvVal, limit); std::cout << "MY_ENV_INT: " << EnvVal << std::endl; }
// プログラム2 環境変数設定してアプリケーションを起動 #include <iostream> #include <windows.h> int main() { //////////////////////////////////////////////////// // 独自の環境変数設定 // 環境変数作成 std::wstring envVar1 = L"MY_ENV_STR=HelloWorld"; std::wstring envVar2 = L"MY_ENV_INT=12345"; // 環境変数を設定するときは各変数を\0文字で区切り、終端を\0\0にする // "VAL1=value1\0VAL2=value2\0\0" std::wstring envVars; envVars += envVar1; envVars.push_back(L'\0'); // 環境変数の区切り envVars += envVar2; envVars.push_back(L'\0'); // 環境変数の区切り // 最後は\0\0で終端 envVars.push_back(L'\0'); envVars.push_back(L'\0'); // アプリ起動 STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi; std::wstring apppath = L"display_myenvs.exe"; BOOL ret = CreateProcessW( nullptr, &apppath[0], // 破壊的パースがされるため非constで渡す nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, (void*)envVars.c_str(), nullptr, &si, &pi ); // エラー処理 if (!ret) { std::cerr << "CreateProcessA failed with error: " << GetLastError() << std::endl; return 1; } }
やってできないことはない。実用性があるかどうかは別。これを使うと引数を与える順番を考えなくてよくなるのと、引数の意味が呼び出し側でわかるようになるので若干可読性が上がるかもしれない。
#include <iostream> #include <type_traits>
// 型TがArgs...に含まれているかどうかを判定するメタ関数 template <typename T, typename... Args> constexpr bool is_contains_type = (std::is_same_v<T, Args> || ...);
// 可変引数テンプレートから特定の型Tで与えられた変数を取得する template<typename T, typename First, typename... Rest> decltype(auto) get_value(First&& first, Rest&&... rest) { if constexpr (std::is_same_v<T, std::decay_t<First> > == true) { return std::forward<First>(first); } // sizeof...(rest)で残りの引数の数をチェック else if constexpr (sizeof...(rest) > 0) { // 残りの引数restから T を探す return get_value<T>( std::forward<Rest>(rest)...); } else { static_assert(sizeof...(rest) > 0, "No matching type found in arguments."); } }
template<int ID> // IDを与えてusing時にそれぞれを違う型として認識されるようにする struct ArgString { std::string value; ArgString& operator=(const std::string& str) { value = str; return *this; } }; template<int ID> struct ArgInt { int value; ArgInt& operator=(int i) { value = i; return *this; } }; using Text = ArgString<0>; using Title = ArgString<1>; using Index = ArgInt<0>; using Count = ArgInt<1>; // グローバル引数として、関数に渡す時に指定する引数名を定義 Index index; Count count; Text text; Title title;
// print関数の定義 template <class... Args> void print(Args&&... args) {// argsにはindex,count,text,titleのいずれかが入っている Index index; Count count; Text text; Title title; if constexpr (is_contains_type<Index, std::decay_t<Args>...>) { auto&& arg = get_value<Index>( std::forward<Args>(args)... ); index = arg; // 引数の値をIndexに設定 } if constexpr (is_contains_type<Count, std::decay_t<Args>...>) { auto&& arg = get_value<Count>( std::forward<Args>(args)... ); count = arg; // 引数の値をCountに設定 } if constexpr (is_contains_type<Text, std::decay_t<Args>...>) { auto&& arg = get_value<Text>( std::forward<Args>(args)... ); text = arg; // 引数の値をTextに設定 } if constexpr (is_contains_type<Title, std::decay_t<Args>...>) { auto&& arg = get_value<Title>( std::forward<Args>(args)... ); title = arg; // 引数の値をTitleに設定 } // 各値を使用 std::cout << "index: " << index.value << std::endl; std::cout << "count: " << count.value << std::endl; std::cout << "text: " << text.value << std::endl; std::cout << "title: " << title.value << std::endl; }
int main() { print( index = 5, count = 10, text = "Hello, World!", title = "My Title" ); }
実行結果
optixRaycastingのプロジェクトにどのファイルが必要かを確認する
ソースコードの場所:
C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\SDK\optixRaycasting\
に、以下のファイルが存在
プロジェクトの作成の前に、nvccで以下を生成しておく。
cl.exeも呼び出されるので、x64 Native Tools Command Prompt等で実行する。
本体のC++プロジェクトがMTかMDかでビルドオプションを変える
MT
MD
で、VC++で、C++プロジェクトを作成。
ぱっと見いらないものなどを消して、依存関係やらを整頓する。
・サンプルをビルドしたディレクトリ内にある sutil_7_sdk.lib が必要。
・gladはこの段階では取り除けなかった。
・sutil::loadSceneがモデルの他にシーン情報も設定している
・optixRaycasting_generated_optixRaycastingKernels.cu.obj optixRaycasting_generated_optixRaycastingKernels_dlink.obj をリンカー → 入力 → 追加の依存ファイルに追記しておく
/* * SPDX-FileCopyrightText: Copyright (c) 2019 - 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <cuda_runtime.h> #include <optix.h> #include <optix_function_table_definition.h> #include <optix_stack_size.h> #include <optix_stubs.h> /* // Include ディレクトリ C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\include C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\SDK\cuda C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\SDK C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.8\include C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\SDK\support\imgui\.. */ /* // nvccでビルドして作成する各種関連ファイル: optixRaycasting_generated_optixRaycastingKernels.cu.obj optixRaycasting_generated_optixRaycastingKernels_dlink.obj */ /* プリプロセッサ _USE_MATH_DEFINES NOMINMAX GLAD_GLAPI_EXPORT */ // D:\tmp\optixbuild ここでインクルードしなくてもよい //#include <sampleConfig.h> #include <fstream> // getInputDataの代わりにifstreamを使う #include "cuda/whitted.h" #include <sutil/CUDAOutputBuffer.h> #include <sutil/Matrix.h> #include <sutil/Record.h> #include <sutil/Scene.h> #include <sutil/sutil.h> #include "optixRaycasting.h" #include "optixRaycastingKernels.h" #include <iomanip> #pragma comment(lib, "D:\\tmp\\optixbuild\\lib\\Release\\glad.lib") #pragma comment(lib, "D:\\tmp\\optixbuild\\lib\\Release\\sutil_7_sdk.lib") #pragma comment(lib, "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\lib\\x64\\cudart_static.lib") #pragma comment(lib, "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\lib\\x64\\cudadevrt.lib") struct RaycastingState { int width = 0; int height = 0; OptixDeviceContext context = 0; sutil::Scene scene = {}; OptixPipelineCompileOptions pipeline_compile_options = {}; OptixModule ptx_module = 0; OptixPipeline pipeline_1 = 0; OptixPipeline pipeline_2 = 0; OptixProgramGroup raygen_prog_group = 0; OptixProgramGroup miss_prog_group = 0; OptixProgramGroup hit_prog_group = 0; Params params = {}; Params params_translated = {}; OptixShaderBindingTable sbt = {}; sutil::Texture mask = {}; }; typedef sutil::Record<whitted::HitGroupData> HitGroupRecord; void createModule( RaycastingState& state ) { OptixModuleCompileOptions module_compile_options = {}; #if OPTIX_DEBUG_DEVICE_CODE module_compile_options.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_0; module_compile_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; #else module_compile_options.optLevel = OPTIX_COMPILE_OPTIMIZATION_DEFAULT; module_compile_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; #endif state.pipeline_compile_options.usesMotionBlur = false; state.pipeline_compile_options.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; state.pipeline_compile_options.numPayloadValues = 4; state.pipeline_compile_options.numAttributeValues = 2; state.pipeline_compile_options.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE; state.pipeline_compile_options.pipelineLaunchParamsVariableName = "params"; size_t inputSize = 0; // 使いにくいので書き換え #if 0 const char* input = sutil::getInputData( OPTIX_SAMPLE_NAME, OPTIX_SAMPLE_DIR, "optixRaycasting.cu", inputSize ); #else
std::string filepath = "optixRaycasting_generated_optixRaycasting.cu.optixir"; std::ifstream file(filepath, std::ios::binary | std::ios::ate); if (!file) throw std::runtime_error("Failed to open file: " + filepath); std::streamsize size = file.tellg(); // ファイルサイズ file.seekg(0, std::ios::beg); // 先頭へ移動 std::vector<char> buffer; buffer.resize(size); if (!file.read(buffer.data(), size)) throw std::runtime_error("Failed to read file: " + filepath); const char* input = buffer.data(); inputSize = size;
#endif // optixModuleCreateに.optixirを与える OPTIX_CHECK_LOG( optixModuleCreate( state.context, &module_compile_options, &state.pipeline_compile_options, input, inputSize, LOG, &LOG_SIZE, &state.ptx_module ) ); } void createProgramGroups( RaycastingState& state ) { OptixProgramGroupOptions program_group_options = {}; OptixProgramGroupDesc raygen_prog_group_desc = {}; raygen_prog_group_desc.kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; raygen_prog_group_desc.raygen.module = state.ptx_module; raygen_prog_group_desc.raygen.entryFunctionName = "__raygen__from_buffer"; OPTIX_CHECK_LOG( optixProgramGroupCreate( state.context, &raygen_prog_group_desc, 1, // num program groups &program_group_options, LOG, &LOG_SIZE, &state.raygen_prog_group ) ); OptixProgramGroupDesc miss_prog_group_desc = {}; miss_prog_group_desc.kind = OPTIX_PROGRAM_GROUP_KIND_MISS; miss_prog_group_desc.miss.module = state.ptx_module; miss_prog_group_desc.miss.entryFunctionName = "__miss__buffer_miss"; OPTIX_CHECK_LOG( optixProgramGroupCreate( state.context, &miss_prog_group_desc, 1, // num program groups &program_group_options, LOG, &LOG_SIZE, &state.miss_prog_group ) ); OptixProgramGroupDesc hit_prog_group_desc = {}; hit_prog_group_desc.kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; hit_prog_group_desc.hitgroup.moduleAH = state.ptx_module; hit_prog_group_desc.hitgroup.entryFunctionNameAH = "__anyhit__texture_mask"; hit_prog_group_desc.hitgroup.moduleCH = state.ptx_module; hit_prog_group_desc.hitgroup.entryFunctionNameCH = "__closesthit__buffer_hit"; OPTIX_CHECK_LOG( optixProgramGroupCreate( state.context, &hit_prog_group_desc, 1, // num program groups &program_group_options, LOG, &LOG_SIZE, &state.hit_prog_group ) ); } void createPipelines( RaycastingState& state ) { const uint32_t max_trace_depth = 1; OptixProgramGroup program_groups[3] = {state.raygen_prog_group, state.miss_prog_group, state.hit_prog_group}; OptixPipelineLinkOptions pipeline_link_options = {}; pipeline_link_options.maxTraceDepth = max_trace_depth; OPTIX_CHECK_LOG( optixPipelineCreate( state.context, &state.pipeline_compile_options, &pipeline_link_options, program_groups, sizeof( program_groups ) / sizeof( program_groups[0] ), LOG, &LOG_SIZE, &state.pipeline_1 ) ); OPTIX_CHECK_LOG( optixPipelineCreate( state.context, &state.pipeline_compile_options, &pipeline_link_options, program_groups, sizeof( program_groups ) / sizeof( program_groups[0] ), LOG, &LOG_SIZE, &state.pipeline_2 ) ); OptixStackSizes stack_sizes_1 = {}; OptixStackSizes stack_sizes_2 = {}; for( auto& prog_group : program_groups ) { OPTIX_CHECK( optixUtilAccumulateStackSizes( prog_group, &stack_sizes_1, state.pipeline_1 ) ); OPTIX_CHECK( optixUtilAccumulateStackSizes( prog_group, &stack_sizes_2, state.pipeline_2 ) ); } uint32_t direct_callable_stack_size_from_traversal; uint32_t direct_callable_stack_size_from_state; uint32_t continuation_stack_size; OPTIX_CHECK( optixUtilComputeStackSizes( &stack_sizes_1, max_trace_depth, 0, // maxCCDepth 0, // maxDCDEpth &direct_callable_stack_size_from_traversal, &direct_callable_stack_size_from_state, &continuation_stack_size ) ); OPTIX_CHECK( optixPipelineSetStackSize( state.pipeline_1, direct_callable_stack_size_from_traversal, direct_callable_stack_size_from_state, continuation_stack_size, 2 // maxTraversableDepth ) ); OPTIX_CHECK( optixUtilComputeStackSizes( &stack_sizes_2, max_trace_depth, 0, // maxCCDepth 0, // maxDCDEpth &direct_callable_stack_size_from_traversal, &direct_callable_stack_size_from_state, &continuation_stack_size ) ); OPTIX_CHECK( optixPipelineSetStackSize( state.pipeline_2, direct_callable_stack_size_from_traversal, direct_callable_stack_size_from_state, continuation_stack_size, 2 // maxTraversableDepth ) ); } void createSBT( RaycastingState& state ) { // raygen CUdeviceptr d_raygen_record = 0; const size_t raygen_record_size = sizeof( sutil::EmptyRecord ); CUDA_CHECK( cudaMalloc( reinterpret_cast<void**>( &d_raygen_record ), raygen_record_size ) ); sutil::EmptyRecord rg_record; OPTIX_CHECK( optixSbtRecordPackHeader( state.raygen_prog_group, &rg_record ) ); CUDA_CHECK( cudaMemcpy( reinterpret_cast<void*>( d_raygen_record ), &rg_record, raygen_record_size, cudaMemcpyHostToDevice ) ); // miss CUdeviceptr d_miss_record = 0; const size_t miss_record_size = sizeof( sutil::EmptyRecord ); CUDA_CHECK( cudaMalloc( reinterpret_cast<void**>( &d_miss_record ), miss_record_size ) ); sutil::EmptyRecord ms_record; OPTIX_CHECK( optixSbtRecordPackHeader( state.miss_prog_group, &ms_record ) ); CUDA_CHECK( cudaMemcpy( reinterpret_cast<void*>( d_miss_record ), &ms_record, miss_record_size, cudaMemcpyHostToDevice ) ); // hit group std::vector<HitGroupRecord> hitgroup_records; for( const auto& mesh : state.scene.meshes() ) { for( size_t i = 0; i < mesh->material_idx.size(); ++i ) { HitGroupRecord rec = {}; OPTIX_CHECK( optixSbtRecordPackHeader( state.hit_prog_group, &rec ) ); GeometryData::TriangleMesh triangle_mesh = {}; triangle_mesh.positions = mesh->positions[i]; triangle_mesh.normals = mesh->normals[i]; for( size_t j = 0; j < GeometryData::num_texcoords; ++j ) triangle_mesh.texcoords[j] = mesh->texcoords[j][i]; triangle_mesh.indices = mesh->indices[i]; rec.data.geometry_data.setTriangleMesh( triangle_mesh ); rec.data.material_data = state.scene.materials()[mesh->material_idx[i]]; hitgroup_records.push_back( rec ); } } CUdeviceptr d_hitgroup_record = 0; const size_t hitgroup_record_size = sizeof( HitGroupRecord ); CUDA_CHECK( cudaMalloc( reinterpret_cast<void**>( &d_hitgroup_record ), hitgroup_record_size * hitgroup_records.size() ) ); CUDA_CHECK( cudaMemcpy( reinterpret_cast<void*>( d_hitgroup_record ), hitgroup_records.data(), hitgroup_record_size * hitgroup_records.size(), cudaMemcpyHostToDevice ) ); state.sbt.raygenRecord = d_raygen_record; state.sbt.missRecordBase = d_miss_record; state.sbt.missRecordStrideInBytes = static_cast<uint32_t>( miss_record_size ); state.sbt.missRecordCount = RAY_TYPE_COUNT; state.sbt.hitgroupRecordBase = d_hitgroup_record; state.sbt.hitgroupRecordStrideInBytes = static_cast<uint32_t>( hitgroup_record_size ); state.sbt.hitgroupRecordCount = static_cast<int>( hitgroup_records.size() ); } void bufferRays( RaycastingState& state ) { // Create CUDA buffers for rays and hits sutil::Aabb aabb = state.scene.aabb(); aabb.invalidate(); for( const auto& instance : state.scene.instances() ) aabb.include( instance->world_aabb ); const float3 bbox_span = aabb.extent(); state.height = static_cast<int>( state.width * bbox_span.y / bbox_span.x ); Ray* rays_d = 0; Ray* translated_rays_d = 0; size_t rays_size_in_bytes = sizeof( Ray ) * state.width * state.height; CUDA_CHECK( cudaMalloc( &rays_d, rays_size_in_bytes ) ); CUDA_CHECK( cudaMalloc( &translated_rays_d, rays_size_in_bytes ) ); createRaysOrthoOnDevice( rays_d, state.width, state.height, aabb.m_min, aabb.m_max, 0.05f ); CUDA_CHECK( cudaGetLastError() ); CUDA_CHECK( cudaMemcpy( translated_rays_d, rays_d, rays_size_in_bytes, cudaMemcpyDeviceToDevice ) ); translateRaysOnDevice( translated_rays_d, state.width * state.height, bbox_span * make_float3( 0.2f, 0, 0 ) ); CUDA_CHECK( cudaGetLastError() ); Hit* hits_d = 0; Hit* translated_hits_d = 0; size_t hits_size_in_bytes = sizeof( Hit ) * state.width * state.height; CUDA_CHECK( cudaMalloc( &hits_d, hits_size_in_bytes ) ); CUDA_CHECK( cudaMalloc( &translated_hits_d, hits_size_in_bytes ) ); state.params = {state.scene.traversableHandle(), rays_d, hits_d}; state.params_translated = {state.scene.traversableHandle(), translated_rays_d, translated_hits_d}; } void launch( RaycastingState& state ) { CUstream stream_1 = 0; CUstream stream_2 = 0; CUDA_CHECK( cudaStreamCreate( &stream_1 ) ); CUDA_CHECK( cudaStreamCreate( &stream_2 ) ); Params* d_params = 0; Params* d_params_translated = 0; CUDA_CHECK( cudaMalloc( reinterpret_cast<void**>( &d_params ), sizeof( Params ) ) ); CUDA_CHECK( cudaMemcpyAsync( reinterpret_cast<void*>( d_params ), &state.params, sizeof( Params ), cudaMemcpyHostToDevice, stream_1 ) ); OPTIX_CHECK( optixLaunch( state.pipeline_1, stream_1, reinterpret_cast<CUdeviceptr>( d_params ), sizeof( Params ), &state.sbt, state.width, state.height, 1 ) ); // Translated CUDA_CHECK( cudaMalloc( reinterpret_cast<void**>( &d_params_translated ), sizeof( Params ) ) ); CUDA_CHECK( cudaMemcpyAsync( reinterpret_cast<void*>( d_params_translated ), &state.params_translated, sizeof( Params ), cudaMemcpyHostToDevice, stream_2 ) ); OPTIX_CHECK( optixLaunch( state.pipeline_2, stream_2, reinterpret_cast<CUdeviceptr>( d_params_translated ), sizeof( Params ), &state.sbt, state.width, state.height, 1 ) ); CUDA_SYNC_CHECK(); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( d_params ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( d_params_translated ) ) ); } void shadeHits( RaycastingState& state, const std::string& outfile ) { sutil::CUDAOutputBufferType output_buffer_type = sutil::CUDAOutputBufferType::CUDA_DEVICE; sutil::CUDAOutputBuffer<float3> output_buffer( output_buffer_type, state.width, state.height ); sutil::ImageBuffer buffer; buffer.width = state.width; buffer.height = state.height; buffer.pixel_format = sutil::BufferImageFormat::FLOAT3; // Original shadeHitsOnDevice( output_buffer.map(), state.width * state.height, state.params.hits ); CUDA_CHECK( cudaGetLastError() ); output_buffer.unmap(); std::string ppmfile = outfile + ".ppm"; buffer.data = output_buffer.getHostPointer(); sutil::saveImage( ppmfile.c_str(), buffer, false ); std::cerr << "Wrote image to " << ppmfile << std::endl; // Translated shadeHitsOnDevice( output_buffer.map(), state.width * state.height, state.params_translated.hits ); CUDA_CHECK( cudaGetLastError() ); output_buffer.unmap(); ppmfile = outfile + "_translated.ppm"; buffer.data = output_buffer.getHostPointer(); sutil::saveImage( ppmfile.c_str(), buffer, false ); std::cerr << "Wrote translated image to " << ppmfile << std::endl; } void cleanup( RaycastingState& state ) { OPTIX_CHECK( optixPipelineDestroy( state.pipeline_1 ) ); OPTIX_CHECK( optixPipelineDestroy( state.pipeline_2 ) ); OPTIX_CHECK( optixProgramGroupDestroy( state.raygen_prog_group ) ); OPTIX_CHECK( optixProgramGroupDestroy( state.miss_prog_group ) ); OPTIX_CHECK( optixProgramGroupDestroy( state.hit_prog_group ) ); OPTIX_CHECK( optixModuleDestroy( state.ptx_module ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.params.rays ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.params.hits ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.params_translated.rays ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.params_translated.hits ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.sbt.raygenRecord ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.sbt.missRecordBase ) ) ); CUDA_CHECK( cudaFree( reinterpret_cast<void*>( state.sbt.hitgroupRecordBase ) ) ); CUDA_CHECK( cudaDestroyTextureObject( state.mask.texture ) ); CUDA_CHECK( cudaFreeArray( state.mask.array ) ); } int main( int argc, char** argv ) { std::string infile, outfile; RaycastingState state; state.width = 640; // .gltfはモデルとシーン情報を含む // データの場所: // C:\ProgramData\NVIDIA Corporation\OptiX SDK 9.0.0\SDK\data infile = sutil::sampleDataFilePath("Duck/DuckHole.gltf"); outfile = "output"; try { sutil::loadScene( infile.c_str(), state.scene ); state.scene.createContext(); state.scene.buildMeshAccels(); state.scene.buildInstanceAccel( RAY_TYPE_COUNT ); state.context = state.scene.context(); OPTIX_CHECK( optixInit() ); // Need to initialize function table createModule( state ); createProgramGroups( state ); createPipelines( state ); createSBT( state ); bufferRays( state ); launch( state ); shadeHits( state, outfile ); cleanup( state ); } catch( std::exception& e ) { std::cerr << "Caught exception: " << e.what() << std::endl; return 1; } return 0; }
Windows 11 + Visual C++ 2022 + CUDA 12.8 でOptiX 9.0を試す。
以下からドライバとCUDAを最新にしておく
NVIDIA Driver 今回使用:576.02
https://www.nvidia.com/ja-jp/drivers
CUDA Toolkit 今回使用:12.8
https://developer.nvidia.com/cuda-downloads
以下からOptiX 9.0をダウンロード・インストール
OptiX
https://developer.nvidia.com/designworks/optix/download
以下にインストールされている:
C:/ProgramData/NVIDIA Corporation/OptiX SDK 9.0.0/SDK
ここにCMakeLists.txtも入っているので、CMake-GUI で、Where is the source codeに指定。
Where to build the binaries: にビルド用のディレクトリを入れる。
インストールは行わないので、これでConfigure Generate Open Project する。
VC++でALL_BUILDすると、D:\tmp\optixbuild\bin\Release にサンプルプログラムが入っている。
C++17でFold expressionという機能が追加されたらしい。
なんでも
とすると、
と展開される。
#include <iostream>
template<typename... Args> auto add1(Args... val) { // Fold expression // val1 + val2 + val3 + ... return (val + ...); }
template<int... Args> int add2() { // Fold expression // args1 + args2 + args3 + ... return (Args + ...); }
int main() { auto ret1 = add1(1.0, 2.2, 3.f, 4, 5); std::cout << "Sum: " << ret1 << "\n"; int ret2 = add2<1, 2, 3, 4, 5>(); std::cout << "Sum: " << ret2 << "\n"; }
可変引数テンプレートもそうだが、if constexprやtype_traits、型推論に右辺参照もあるのでかなり簡単に書けるようになっている。
#include <iostream> #include <type_traits>
// 型TがArgs...に含まれているかどうかを判定するメタ関数 template <typename T, typename... Args> constexpr bool is_contains_type = (std::is_same_v<T, Args> || ...);
template<typename T, typename First, typename... Rest> decltype(auto) get_value(First&& first, Rest&&... rest) { if constexpr (std::is_same_v<T, std::decay_t<First> > == true) { return std::forward<First>(first); } // sizeof...(rest)で残りの引数の数をチェック else if constexpr (sizeof...(rest) > 0) { // 残りの引数restから T を探す return get_value<T>( std::forward<Rest>(rest)...); } else { static_assert(sizeof...(rest) > 0, "No matching type found in arguments."); } }
template <class... Args> void print(Args&&... args) { if constexpr (is_contains_type<int, std::decay_t<Args>...>) { auto&& arg_int = get_value<int>( std::forward<Args>(args)... ); std::cout << "int is " << arg_int << "\n"; } if constexpr (is_contains_type<double, std::decay_t<Args>...>) { auto&& arg_double = get_value<double>(std::forward<Args>(args)... ); std::cout << "double is "<< arg_double << "\n"; } if constexpr (is_contains_type<float, std::decay_t<Args>...>) { auto&& arg_float = get_value<float>(std::forward<Args>(args)... ); std::cout << "float is " << arg_float << "\n"; } }
int main() { print(1, 2.5, 5.f); // 引数が右辺値の場合 int K = 10; print(K); // 引数が左辺値の場合 std::cout << "K is " << K << "\n"; }