Friday 27 November 2020

Changing the name of a variable in CMake

 Here's a neat trick in CMake: you want to change the name of a variable, but worry that anyone you've distributed the code to already will lose the option they've selected.

Use the old variable as the default value for the new one:

option(OLD_VARIABLE "Some variable" ON)
option(NEW_VARIABLE "Some variable" ${OLD_VARIABLE})

or...

set( OLD_STRING_VARIABLE "Old default" CACHE STRING "Help text" )
set( NEW_STRING_VARIABLE "${OLD_STRING_VARIABLE}" "Help text" )

Thursday 12 November 2020

Passing an array of structs from C++ to C#

To pass an array of structs from C++ to C#, you can pass a pointer to a C-style array. In C++ you may have a struct, e.g.


#pragma pack(push)
#pragma pack(1)
struct InputEvent
{
uint32_t eventId;
float floatValue;
uint32_t intValue;
};
#pragma pack(pop)

The delegate in C++ is:
typedef void(__stdcall* ProcessNewInputFn) (int numEvents, const InputEvent**); Telling C++ what C# function to call: 

 
extern "C" __declspec(dllexport) void SetInputProcessingDelegate(ProcessNewInputFn newInputProcessing)
{
processNewInput = newInputProcessing;
}

Using this from C++

std::vector<InputEvent> inputEvents;
const avs::InputEvent *v=inputEvents.data(); processNewInput(inputEvents.size(), &v); 

In C# the struct is defined as:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct InputEvent
{
public UInt32 eventId;
public float floatValue;
public UInt32 intValue;
}; 



Note the packing! It must match what we had in C++. Now C# must declare the the delegate type it will implement:

[UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate void OnNewInput(int numEvents, in IntPtr newEvents); 


And declare in C# the C++ function that sets the delegate:

[DllImport("dllname")] static extern void SetInputProcessingDelegate(OnNewInput onNewInput );


This is called with

ok = SetInputProcessingDelegate(ProcessingClass.SetInput); 


Where we have a class like this:


class ProcessingClass
{
public static void StaticProcessInput(int numEvents, in IntPtr inputEventsPtr )
{
    int EventSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(avs.InputEvent));
    avs.InputEvent[] inputEvents = new avs.InputEvent[inputState.numEvents];
    IntPtr ptr=  inputEventsPtr;
    for (int i = 0; i < inputState.numEvents; i++)
    {
       inputEvents[i]=Marshal.PtrToStructure<avs.InputEvent>(ptr);
       ptr += EventSize;
}
        }
}


Here, we take the C++ style pointer-to-array, and iterate through the array elements, copying each in turn into the C# style array.

Thursday 14 November 2019

Resolving conflicts between Qt versions

The trueSKY plugin for Unreal uses Qt dll's for UI. Unfortunately so do some other plugins. Because Windows just uses whichever version of a dll was loaded first, this leads to (for example) crashes in trueSKY UI because it tries to access the wrong parts of a dll loaded by Quixel Megascans.

To solve this we recompile Qt using the switch  -qtlibinfix to modify the output filenames. Thus instead of Qt5Core.dll we get Qt5Core_simul.dll etc.

No more conflicts!

UPDATE: To compile Qt, follow the instructions at https://wiki.qt.io/Building_Qt_5_from_Git. For example, for me on Windows, I must git-clone the repo, install perl (!) and call perl init-repository.

Then, I create a subdirectory BUILD_DIR at subdirectory "build/x64". From there, I call:

call ../../configure.bat -qtlibinfix %QT_INFIX% -prefix %BUILD_DIR%\qtbase -skip qtwebengine -developer-build -%reldeb% -force-debug-info -no-warnings-are-errors  -L kernel32 -opengl desktop -opensource -make libs -make tools -nomake examples -nomake tests -platform win32-msvc -confirm-license -no-compile-examples -qt-zlib -plugin-manifests -no-angle -qt-freetype -qt-libjpeg -qt-libpng -D U_STATIC_IMPLEMENTATION %INC% %LIBDIRS% %IC%

QT_INFIX is _simul, while INC LIBDIRS and IC are extra compile options.

Finally, we run nMake to build Qt.

Thursday 2 August 2018

Finding and removing files added to git by accident

If for example, you've added lib files by mistake to a large git repo, and want to remove them, but don't know the exact paths, use this:

git ls-files *.lib>lib.bat

Then in lib.bat you may have e.g.:
Plugins/Media/Intermediate/Build/Win64/DebugMediaEditor/MediaEditor-Win64-Debug.lib

Add git rm --cached to the front of each line, then run the batch file and commit the result.

Thursday 21 June 2018

How to make a custom Wizard for Unreal Editor

I wanted to create a wizard in the trueSKY Unreal plugin that would make it easier for users to add trueSKY to UE scenes. I was following this video where Epic's Michael Noland describes various ways to modify the Editor. So I made a custom Property Editor window with settings to select a sky sequence, create a TrueSkyLight etc.

But it didn't look very friendly. And implementing a wizard-style Apply button just put a button in amongst the other settings - not great. After some searching in the UE codebase, I discovered the SWizard class that Unreal Editor uses for its own wizards. Here's what you do:

1. Create a class derived from SCompoundWidget containing a TSharedPtr<SWizard>. Mine looks like this:


DECLARE_DELEGATE_FourParams( FOnTrueSkySetup, bool, ADirectionalLight* ,bool, UTrueSkySequenceAsset *);
#define S_DECLARE_CHECKBOX(name) \
 bool name; \
 ECheckBoxState Is##name##Checked() const { return name ? ECheckBoxState::Checked:ECheckBoxState::Unchecked;} \
 void On##name##Changed(ECheckBoxState InCheckedState) {name=(InCheckedState==ECheckBoxState::Checked);}

class STrueSkySetupTool : public SCompoundWidget
{
public:
 SLATE_BEGIN_ARGS( STrueSkySetupTool )
  :_CreateTrueSkyLight(false)
  ,_DirectionalLight(nullptr)
  ,_CreateDirectionalLight(nullptr)
  ,_Sequence(nullptr)
 {}
 /** A TrueSkyLight actor performs real-time ambient lighting.*/
 SLATE_ARGUMENT(bool,CreateTrueSkyLight)
 /** TrueSKY can drive a directional light to provide sunlight and moonlight.*/
 SLATE_ARGUMENT(ADirectionalLight*,DirectionalLight)
 /** If there's no directional light in the scene, you can create one with this checkbox.*/
 SLATE_ARGUMENT(bool,CreateDirectionalLight)
 /** The TrueSKY Sequence provides the weather state to render.*/
 SLATE_ARGUMENT(UTrueSkySequenceAsset *,Sequence)
 /** Event called when code is successfully added to the project */
 SLATE_EVENT( FOnTrueSkySetup, OnTrueSkySetup )
 SLATE_END_ARGS()
 /** Constructs this widget with InArgs */
 void Construct( const FArguments& InArgs );
 
 /** Handler for when cancel is clicked */
 void CancelClicked();

 /** Returns true if Finish is allowed */
 bool CanFinish() const;

 /** Handler for when finish is clicked */
 void FinishClicked();

...
 
 S_DECLARE_CHECKBOX(CreateTrueSkyLight)
 S_DECLARE_CHECKBOX(ShowAllSequences)

 void SetupSequenceAssetItems();
 
 void CloseContainingWindow();
private:
 /** The wizard widget */
 TSharedPtr<SWizard> MainWizard;
 FOnTrueSkySetup OnTrueSkySetup;
...
};


The SLATE_ARGUMENT macros allow initialization of named parameters in this style:

TSharedRef<STrueSkySetupTool> TrueSkySetupTool = SNew(STrueSkySetupTool).OnTrueSkySetup(OnTrueSkySetup1).CreateTrueSkyLight(true);

etc. This is super-useful.

2. Create a callback for the wizard to execute:
FOnTrueSkySetup OnTrueSkySetupDelegate;

3. Create a window for the widget. This function is called when the menu option to start the wizard is selected:

void FTrueSkyEditorPlugin::OnAddSequence()
{
 TrueSkySetupWindow = SNew(SWindow)
   .Title( NSLOCTEXT("InitializeTrueSky", "WindowTitle", "Initialize trueSKY") )
   .ClientSize( FVector2D(600, 550) )
   .SizingRule( ESizingRule::FixedSize )
   .SupportsMinimize(false).SupportsMaximize(false);
 OnTrueSkySetupDelegate.BindRaw(this,&FTrueSkyEditorPlugin::OnTrueSkySetup);
 TSharedRef TrueSkySetupTool = SNew(STrueSkySetupTool).OnTrueSkySetup(OnTrueSkySetupDelegate);
 TrueSkySetupWindow->SetContent( TrueSkySetupTool );

If the main frame exists parent the window to it. The main frame should always exist...

 TSharedPtr< SWindow > ParentWindow;
 if( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) )
 {
  IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked<imainframemodule>( "MainFrame" );
  ParentWindow = MainFrame.GetParentWindow();
 }
 
 bool modal=false;
 if (modal)
 {
  FSlateApplication::Get().AddModalWindow(TrueSkySetupWindow.ToSharedRef(), ParentWindow);
 }
 else if (ParentWindow.IsValid())
 {
  FSlateApplication::Get().AddWindowAsNativeChild(TrueSkySetupWindow.ToSharedRef(), ParentWindow.ToSharedRef());
 }
 else
 {
  FSlateApplication::Get().AddWindow(TrueSkySetupWindow.ToSharedRef());
 }
 TrueSkySetupWindow->ShowWindow();
}
4. Implement the setup tool:
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void STrueSkySetupTool::Construct( const FArguments& InArgs )
{
 OnTrueSkySetup = InArgs._OnTrueSkySetup;
 CreateTrueSkyLight=InArgs._CreateTrueSkyLight;
 DirectionalLight=InArgs._DirectionalLight;
 Sequence=InArgs._Sequence;
...

The interface to build the actual UI is really interesting. By overloading the [] and + operators, Epic lets you specify the widget structure like so:



 ChildSlot
 [
  SNew(SBorder)
  .Padding(18)
  .BorderImage( FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush") )
  [
   SNew(SVerticalBox)
   +SVerticalBox::Slot()
   [
    SAssignNew( MainWizard, SWizard)
    .ShowPageList(false)
    .CanFinish(this, &STrueSkySetupTool::CanFinish)
    .FinishButtonText(  LOCTEXT("TrueSkyFinishButtonText", "Initialize") )
    .OnCanceled(this, &STrueSkySetupTool::CancelClicked)
    .OnFinished(this, &STrueSkySetupTool::FinishClicked)
    .InitialPageIndex( 0)
    +SWizard::Page()
    [
     SNew(SVerticalBox)
     +SVerticalBox::Slot()
     .AutoHeight()
     [
      SNew(STextBlock)
      .TextStyle( FEditorStyle::Get(), "NewClassDialog.PageTitle" )
      .Text( LOCTEXT( "WeatherStateTitle", "Choose a Sequence Asset" ) )
     ]
     +SVerticalBox::Slot()
     .AutoHeight()
     .Padding(0)
     [
      SNew(SHorizontalBox)
      +SHorizontalBox::Slot()
      .FillWidth(1.f)
      .VAlign(VAlign_Center)
      [
       SNew(STextBlock)
       .Text(LOCTEXT("TrueSkySetupToolDesc", "Choose which weather sequence to use initially.") )
       .AutoWrapText(true)
       .TextStyle(FEditorStyle::Get(), "NewClassDialog.ParentClassItemTitle")
      ]
     ]
    ]
    +SWizard::Page()
    [
     ...
    ]
   ]
  ]
 ];

}

So by adding new +SWizard::Page() elements we add pages to the wizard.

5. Finally, implement the callback that the delegate calls when you click "Finish":

void FTrueSkyEditorPlugin::OnTrueSkySetup(bool CreateDirectionalLight, ADirectionalLight* DirectionalLight,bool CreateTrueSkyLight,UTrueSkySequenceAsset *Sequence)
{
...
}

The end result looks like this:



Full source for this is at our UE branch, (register at Simul to access).

Tuesday 10 April 2018

Signing installers with certificates

Windows Defender has recently decided to falsely mark all of our installers as containing some virus or other.

It'll be a long long while before they get around to questioning whether their algorithms are in fact, "full of it", as they say, so let's see what happens if we sign our executables using a root certificate.

First, get a certificate, from Comodo. This takes weeks while they check whether an arbitrary non-governmental organization, Dun and Bradstreet, regards your company as genuine. Just check with Companies House? Way too simple!

So you need to get a DUNS number from D&B, then buy a certificate from tucows/Comodo.

After jumping through their hoops (which don't seem to be very secure to me, just cumbersome), you'll get a .crt file.

Then https://support.citrix.com/article/CTX221295 will tell you how to convert your crt to a pfx.

Finally, use the pfx and signtool.exe (in the Windows SDK) to sign your executable.

Friday 17 November 2017

Building Unreal Engine projects from the solution using MSBuild

If you want to use MSBuild to build UE4 projects, but need to build them from within the solution instead of specifying the vcxproj file (which doesn't always work correctly), you need to use the "Target" /t: command line parameter, like so:

"path to MSBuild.exe" /t:Engine\UE4 /p:Configuration="Development Editor" /p:Platform=Win64 UE4.sln

Key things to note:
  • the configuration and platform specifiers are Solution-style, with spaces instead of underscores, and Win64 instead of x64 etc.
  • The solution folder path must be specified in the target parameter, otherwise MSBuild will not recognize the project name.