A bit of C++ and CMake linking help, please

I’m still on my journey of developing a GP extension using a Web based UI. I’ve shelved the idea of using Ultralight as I think it had some very complex requirement for preinstalled items and copied DDLs to make it workable which makes it go against the single DLL extension ethos.

I am now attempting to use WebView2 with an abstraction wrapper (and so it is Windows specific at the moment but can become cross platform via the aforementioned abstraction layer). I am having a compile issue relating to the linker. I’ll post my code below. Can anyone spot why I am getting “methods already declared” errors when compiling:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.3.2)

# Let's choose a name and version for the extension. Change the name to your
# liking. The version should be a string like "1.0".
set(PROJECT_NAME "BOULDEN.digital-SongSelector") # Change this to your liking
project(${PROJECT_NAME} VERSION 1.0)

# Import the SDK
include(FetchContent)
FetchContent_Declare(
  gp-sdk
  GIT_REPOSITORY https://github.com/gigperformer/gp-sdk.git
  GIT_TAG 6c5432518ef42ea0870fb44597e9d1d3780e2f98 # v44
)
FetchContent_MakeAvailable(gp-sdk)
# Define our library including sources, include directories and dependencies
add_library(${PROJECT_NAME} SHARED)


find_program(NUGET_EXE NAMES nuget)
exec_program(${NUGET_EXE} ARGS install "Microsoft.Web.WebView2" -Version 1.0.1150.38 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages)
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_PACKAGE_REFERENCES "Microsoft.Web.WebView2")

if(${CMAKE_SIZEOF_VOID_P} STREQUAL 8)
    set(architecture x64)
else()
    set(architecture x86)
endif()


target_sources(
  ${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/src/LibMain.cpp"
                          "${CMAKE_CURRENT_LIST_DIR}/src/LibMain.h"
                          "${CMAKE_CURRENT_LIST_DIR}/src/SongSelector.cpp"
                          "${CMAKE_CURRENT_LIST_DIR}/src/SongSelector.h"
)
target_link_libraries(
    ${PROJECT_NAME} PRIVATE gigperformer::sdk::cpp 
                    PUBLIC ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/${architecture}/WebView2LoaderStatic.lib
)
target_include_directories(
    ${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
                    PUBLIC ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/include
) 


# Language options: this will be a pure C++20 project
set_target_properties(
  ${PROJECT_NAME}
  PROPERTIES CXX_STANDARD 20
             CXX_STANDARD_REQUIRED ON
             CXX_EXTENSIONS NO)

# Install the extension on the development machine
install(
  TARGETS ${PROJECT_NAME}
  LIBRARY DESTINATION "${GIG_PERFORMER_EXTENSIONS_DIRECTORY}"
  RUNTIME DESTINATION "${GIG_PERFORMER_EXTENSIONS_DIRECTORY}")

LibMain.h:

#pragma once

#include "gigperformer/sdk/GPMidiMessages.h"
#include "gigperformer/sdk/GPUtils.h"
#include "gigperformer/sdk/GigPerformerAPI.h"
#include "gigperformer/sdk/types.h"

#include "SongSelector.h"


class LibMain : public gigperformer::sdk::GigPerformerAPI
{
  protected:


    // These are for creating menu items in Gig Performer that can be used to
    // trigger external functions provided by the extension developer
    int GetMenuCount() override;
    std::string GetMenuName(int index) override;
    void InvokeMenu(int itemIndex) override;

  public:
    // These must be here but no need to do anything unless you want extra behavior
    explicit LibMain(LibraryHandle handle) : GigPerformerAPI(handle) {}
    ~LibMain() override {}


    void OnStatusChanged(GPStatusType status) override
    {
        //consoleLog("Gig status changed to " + std::to_string(status));
    }

    void OnRackspaceActivated() override
    {
        //consoleLog("Rackspace activated");
    }

    void OnSetlistChanged(const std::string &newSetlistName) override
    {
       //consoleLog("Setlist switched to: " + newSetlistName);
    }

    void OnModeChanged(int mode) override;

    // Now, simply override the callback methods in which you are interested
    // and, in the Initialization method at the end of this class,
    // call RegisterCallback for each of these methods


    // A midi device was added or removed
    void OnSongChanged(int oldIndex, int newIndex) override;

    void Initialization() override;

    // This MUST be defined in your class
    std::string GetProductDescription() override;


    void showSongSelector();
};

LibMain.cpp

#include "LibMain.h"
#include <cstdint>

#include <fstream>
#include <iostream>

using GPUtils = gigperformer::sdk::GPUtils;

/// Ignore a given value
/// \details this is a dummy function to suppress compiler warnings about unused parameters
template <typename T> void Ignore(T const &) noexcept {};


// define an XML string describing your product
const std::string XMLProductDescription =
    // Replace with your information
    "<Library>"
    "  <Product Name=\"BOULDEN.digital - SongSelector\" Version=\"1.0.0 - WebUI\" BuildDate=\"09/14/2022\"></Product>"
    "  <Description>Just testing a build pipeline</Description>"
    "  <ImagePath></ImagePath>"
    "</Library>";

std::string pathToMe; // This needs to be initialized from the initialization
                      // section of the LibMain class so it can be used in the
                      // standalone functions directly below

// List of menu items
std::vector<std::string> menuNames = {
    "Show Front Panels",
    "Show Back Wiring",
    "Show Setlists",
    "Show Web View"
};

int LibMain::GetMenuCount()
{
    return static_cast<int>(menuNames.size());
}

std::string LibMain::GetMenuName(int index)
{
    std::string text;
    if (index >= 0 && static_cast<std::size_t>(index) < menuNames.size())
    {
        text = menuNames[index];
    }

    return text;
}

void LibMain::InvokeMenu(int index)
{
    if (index >= 0 && static_cast<std::size_t>(index) < menuNames.size())
    {
        switch (index)
        {
        case 0:
            switchToPanelView();
            break;
        case 1:
            switchToWiringView();
            break;
        case 2:
            switchToSetlistView();
            break;
        case 3:
            showSongSelector();
            break;

        default:
            break;
        }
    }
}


void LibMain::OnModeChanged(int mode)
{
    //consoleLog(std::string("Switching to mode: ") + ((mode == GP_SetlistMode) ? "Setlist" : "FrontBack"));
}

void LibMain::OnSongChanged(int oldIndex, int newIndex)
{
    Ignore(oldIndex);
    //consoleLog("Song changed from C++ example");
    std::string name = getSongName(newIndex);
    //consoleLog("New song is called " + name);
}

void LibMain::Initialization()
{
    // Do any initialization that you need

    // .... your code here

    std::ofstream outfile;
    outfile.open("C:/Users/Public/Documents/Gig Performer/Extensions/log.txt", std::ios::app);
    outfile << "Running SongSelector Init" << std::endl;
    outfile.close();

    //consoleLog("Running SongSelector Init");

    // Finally, register all the methods that you are going to actually use,
    // i.e, the ones you declared above as override
    registerCallback("OnSongChanged");
    registerCallback("OnStatusChanged");
    registerCallback("OnModeChanged");
    registerCallback("OnSetlistChanged");
    registerCallback("OnRackspaceActivated");

    //consoleLog("path to library " + getPathToMe());
}

std::string LibMain::GetProductDescription()
{
    // Generally don't touch this - simply define the constant
    // 'XMLProductDescription' at the top of this file with an XML description of
    // your product
    return XMLProductDescription;
}


void LibMain::showSongSelector() {
    //consoleLog("Switching to web view");

}


namespace gigperformer
{
namespace sdk
{

GigPerformerAPI *CreateGPExtension(LibraryHandle handle)
{
    return new LibMain(handle);
}

} // namespace sdk
} // namespace gigperformer

SongSelector.h

#pragma once

#include "../lib/webview.h"

class SongSelector 
{
  private:

  public:
    SongSelector();
    void showSelector();
};

SongSelector.cpp

#include "SongSelector.h"

#include <fstream>
#include <iostream>


SongSelector::SongSelector()
{

    std::ofstream outfile;
    outfile.open("C:/Users/Public/Documents/Gig Performer/Extensions/log.txt", std::ios::app);
    outfile << "Init SongSelector Class" << std::endl;
    outfile.close();

    showSelector();
}

void SongSelector::showSelector()
{
    webview::webview w(false, nullptr);
    w.set_title("Basic Example");
    w.set_size(480, 320, WEBVIEW_HINT_NONE);
    w.set_html("Thanks for using webview!");
    w.run();

}

and webview.h is from this repo which I am using as an abstraction layer to WebView2: https://github.com/webview/webview:

The compiler issues errors claiming the methods in webview.h are being redefined in SongSelector.cpp.obj at the linking stage with this type of error message:

LNK2005 webview_create already defined in LibMain.cpp.obj [in file] C:\Users\DaveBoulden\source\repos\gp-extension-cpp\SongSelector.cpp.obj [at line] 1

I don’t have time to study your code but that error usually means that some source code has been included multiple times - can happen with templates as well, the same type getting instantiated multiple times. Might be that by mistake that webview header is being pulled in multiple times

2 Likes

Thank you, David. Yes, I’m not expecting anyone to debug my code, but more pointers to “it’s usually this kind of thing” 'cos so far Googling isn’t revealing much that’s useful.

What happens if you move the include for webview.h into SongSelector.cpp?

2 Likes

Winner, winner, chicken dinner!!!

Thank you for that, @rank13, I now get a clean compile. My extension is now crashing GP4 when it opens a window, but I’m a step further on.

2 Likes

Crash sorted and we’re away!

Thank you both for your help. I’ll try and get this to the point of a working boilerplate example and then share with the community.

I love this forum!!

2 Likes

Great job @DaveBoulden! First steps are always the hardest :slight_smile:

2 Likes

What was the crash and how did you fix it (may help others later)

I don’t think I have fully sorted the crash, which was actually a hang, I think I have some sort of infinite loop going whenever a set list has any entries in it. I am 100% sure the loop is due to something stupid I have done but will detail it once I sort out the solution.