Commandlets not marked with _API

It is useful sometimes to run some of the existing commandlets from code. For example, now I’m trying to add extra logic to the localization pipeline. The problem is that I cannot use commandlets in my own modules, because they aren’t marked with UNREALED_API! Attempts to call

NewObject()

leads to unresolved external symbol. So, I either have to copy the whole thing into my module or I have to modify the UE4 engine code which is not encouraged in our team.

Is there a way I can execute commandlets in my modules and access their results without marking these commandlets with UNREALED_API in UE4 source code?

Can you post some example code? I don’t quite understand what you want to archive.

I think you can create new ones and add them to the delegate that you need

No, unfortunately. I copied the commandlet into my own codebase, renamed (and rewrote a bit) conflicting names, and called it where I wanted to.

Well, here’s what I want to do:

void TextGatherer::Gather()
{
#ifndef UE_4_15
  UGatherLocTextFromSourceCommandlet* Commandlet = NewObject<UGatherLocTextFromSourceCommandlet>(GetTransientPackage());
  Commandlet->AddToRoot();

  TSharedRef<FManifestInfo> CommandletManifestInfo = MakeShareable(new FManifestInfo());
  Commandlet->Initialize(CommandletManifestInfo, nullptr);

  if (Commandlet->Main("") == 0)
  {
    UELog_S(UGatherLocTextFromSourceCommandlet, Debug, log << "successful gathering");

    UTextLibrary* Library = UTextLibrary::StaticClass()->GetDefaultObject<UTextLibrary>();
    for (auto Entry : Commandlet->ParseCtxt.MyManifestInfo.MyEntries)//= CommandletManifestInfo->GetManifest()->GetEntriesBySourceTextIterator(); ManifestEntryIt; ++ManifestEntryIt)
    {
      Library->GetText(*Entry.Namespace, *Entry.Key, *Entry.Source);
    }
  }
#endif // UE_4_15
}

So I want to use a standard commandlet to collect source code texts and then do my own thing with. If you are curious about what exactly: our game is originally in Russian, but you can’t put Russian texts in source code (causes problems on Mac) and sometimes it’s necessary. We are forced to put English texts in code and so there’s a problem when you are extracting texts for localization, there are now English texts in Russian locale and you would need to double translate it.

The solution was to keep source texts in an object (which is also saved into an .ini file) but refer to it with the same macros LOCTEXT, NSLOCTEXT. And to exclude the need to put everything in that object manually why not use a handy commandlet that knows how to gather these texts from the code?

Yeah, I’ve seen this solution somewhere when I was trying to resolve this, but as you correctly mentioned it won’t give me much.

As for the second suggestion, hmm… I think I’ve tried it but with an Unreal Cast and had problems, but I didn’t think about a static cast. I’ll try that :slight_smile:

I tried this however it fails to load the class, I also tried using ANY_PACKAGE instead of nullptr but this also fails, this is my exact code:

UClass* Class = LoadClass< UCommandlet >(ANY_PACKAGE, TEXT("UImportAssetsCommandlet"));
	auto Cmdlet = NewObject< UCommandlet >(GetTransientPackage(), Class);
	Cmdlet->Main(TEXT("[-help]"));

I have tried without the U prefix to UImportAssetsCommandlet, without the Commandlet postfix and with nullptr but none of these are working.

Thanks very much for your help, this worked exactly as I needed!

Well is there another way to run multiple commandlets from one instance of the editor?

All I’m really aiming to do is run two commandlets, one after the other, without needing to open and close an editor process for each. The method above does that, however I’m now receiving errors from the import assets commandlet :confused: the headline: Unhandled Exception: EXCEPTION_ACCESS_VIOLATION writing address 0x00001894

I guess it’s trying to save assets to a location it doesn’t have permission for?

If there is another way it would be great… seems very inefficient to have to run every commandlet on a separate process.

I’m looking for the same thing, did you find a solution?

An example could be running one of the existing commandlets such as fixupredirects or importassets, from our own commandlets, however this is seemingly not possible as the existing commandlets haven’t been marked with _API

If you only need access to a base class interface of virtual functions (like UCommandlet in this case), then it is possible to be sneaky and create an instance of a non-exported class using the reflection system. For a class USomeCommandlet in module SomeModule, this should work:

UClass* Class = LoadClass< UCommandlet >(nullptr, TEXT("/Script/SomeModule.SomeCommandlet"));
auto Cmdlet = NewObject< UCommandlet >(GetTransientPackage(), Class);
Cmdlet->Main(TEXT("[params]"));

Keep in mind though that commandlets are intended to be run from the command line, so you probably don’t gain anything with this approach over just invoking a command line call.

Thinking about it, if you want to be really evil, you could use a static/C-style cast to cast Cmdlet above to the derived commandlet type, in which case you should be able to access its public data members and inline functions even if it’s not exported.

Sorry my mistake, you need to specify full path like this:

UClass* Class = LoadClass< UCommandlet >(nullptr, TEXT("/Script/UnrealEd.ImportAssetsCommandlet"));

Also I didn’t mean to suggest you should actually put params inside [] when you call Main().

oh wow. This is one hell of a hack

Haha. Yep, I didn’t say I recommend doing it. :wink:

Well, you can execute them by just invoking an OS command line call (via FPlatformProcess). But not in a single process, no. They’re just not designed to be used that way. Likewise they’re not intended to have their member variables accessed from outside.

But if you need to for whatever reason, and the above workaround works, then fair enough. It’s better than just copying source code at least.

Ok I fixed this. Stupidly had isEditor set to false…