Custom Class UObject Reference Always Null

Hello, I am having some issues attempting to get a working tech demo for some of the systems I will have in my game working. First let me describe what I am trying to do with the area that is not working.

I have a few things spread across both custom UObjects as well as some Blueprints. I am sticking this in C++ Programming in case I am going about the classes in the wrong way. So, I am attempting to create a basic inventory system but before I actually build that all I want to do is query some data from my database, store the data in a struct, pass that struct back to my inventory class, iterate over the TArray of struct objects and then use the data to create new UItem objects to shove into my inventory class’ TArray of UItems.

During developing I have run into two issues. The first was an issue where the TArray of UItems in my UInventory class was always null. The second I have created while attempting to debug and fix that.

Here is the code for my UInventory, UItem, UDataHandler, UDatabaseHandler, and DataPlaceholder structs:

Inventory.h:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "DataHandler.h"
    #include "Item.h"
    #include "Inventory.generated.h"
    
    /**
     * 
     */
    UCLASS(Blueprintable)
    class OVMININGTECHDEMO_API UInventory : public UObject
    {
    	GENERATED_BODY()
    
    public:
    	UInventory();
    	~UInventory();
    
    	UFUNCTION(BlueprintCallable, Category = "Object") static void Create(UClass *ObjectClass, UObject* &CreatedObject);
    	UFUNCTION(BlueprintCallable, Category = "Inventory") void getItmData(FString tbl, FString op, int id);
    	UFUNCTION(BlueprintCallable, Category = "Inventory") void InvSetup();
    
    
    	UFUNCTION(BlueprintCallable, Category = "Inventory") TArray<UItem*> GetInventory();
    	UFUNCTION(BlueprintCallable, Category = "Inventory") UDataHandler* GetDHandler();
    	UFUNCTION(BlueprintCallable, Category = "Inventory") void SetDHandler(UDataHandler* dHndl);
    
    private:
    	TArray<UItem*> invItems;
    	UDataHandler* dHandler;
    };

Inventory.cpp:

// Fill out your copyright notice in the Description page of Project Settings.

#include "Inventory.h"
#include <string>

using std::string;

UInventory::UInventory() {
	
}

UInventory::~UInventory() {}
UFUNCTION(BlueprintCallable, Category = "Object")
void UInventory::Create(UClass *ObjectClass, UObject* &CreatedObject) { CreatedObject = NewObject<UObject>((UObject*)GetTransientPackage(), ObjectClass); }

UFUNCTION(BlueprintCallable, Category = "Inventory") void UInventory::InvSetup() {
	//invItems = TArray<UItem*>();
	dHandler = NewObject<UDataHandler>();
}

UFUNCTION(BlueprintCallable, Category = "Inventory") void UInventory::getItmData(FString tbl, FString op, int id) {
	TArray<item> newItems = TArray<item>();
	item newItem = item();

	dHandler->queryData(TCHAR_TO_ANSI(*tbl), TCHAR_TO_ANSI(*op), id);

	//dHandler->GetDBHandle()->ConOutput = "";

	if (id == 0) {
		newItems = dHandler->returnItems();

		for (item itm : newItems) {
			UItem* newItm = NewObject<UItem>();

			newItm->SetIName(itm.iName.c_str());
			newItm->SetIDesc(itm.iDesc.c_str());
			newItm->UpdateIAmt(1, "set");
			newItm->SetISG2(itm.iSG2);
			newItm->SetIType(tbl);

			invItems.Add(newItm);
		}

		FString itmName = invItems.Last()->GetIName();
		string iName = TCHAR_TO_ANSI(*itmName);
		string test = "Last Item Created in Inventory: " + iName;
		dHandler->GetDBHandle()->ConOutput = test.c_str();
	}
	else
	{
		newItem = dHandler->returnItem();

		UItem* newItm = NewObject<UItem>();
		newItm->SetIName(newItem.iName.c_str());
		newItm->SetIDesc(newItem.iDesc.c_str());
		newItm->UpdateIAmt(1, "set");
		newItm->SetISG2(newItem.iSG2);
		newItm->SetIType(tbl);

		invItems.Add(newItm);
	}
}

UFUNCTION(BlueprintCallable, Category = "Inventory") TArray<UItem*> UInventory::GetInventory() { return invItems; }
UFUNCTION(BlueprintCallable, Category = "Inventory") UDataHandler* UInventory::GetDHandler() { return dHandler; }
UFUNCTION(BlueprintCallable, Category = "Inventory") void UInventory::SetDHandler(UDataHandler* dHndl) { dHandler = dHndl; }

Item.h:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Item.generated.h"

/**
 * 
 */
UCLASS(Blueprintable)
class OVMININGTECHDEMO_API UItem : public UObject
{
	GENERATED_BODY()

public:
	UItem();
	~UItem();

	UFUNCTION(BlueprintCallable, Category = "Object") static void Create(UClass *ObjectClass, UObject* &CreatedObject);
	UFUNCTION(BlueprintCallable, Category = "Item") int GetIAtm();
	UFUNCTION(BlueprintCallable, Category = "Item") float GetISG2();
	UFUNCTION(BlueprintCallable, Category = "Item") float GetAllISG2();
	UFUNCTION(BlueprintCallable, Category = "Item") FString GetIName();
	UFUNCTION(BlueprintCallable, Category = "Item") FString GetIDesc();
	UFUNCTION(BlueprintCallable, Category = "Item") FString GetIType();

	UFUNCTION(BlueprintCallable, Category = "Item") void SetISG2(float sg2);
	UFUNCTION(BlueprintCallable, Category = "Item") void SetIName(FString nm);
	UFUNCTION(BlueprintCallable, Category = "Item") void SetIDesc(FString desc);
	UFUNCTION(BlueprintCallable, Category = "Item") void SetIType(FString typ);
	UFUNCTION(BlueprintCallable, Category = "Item") void UpdateIAmt(int newVal, FString op);

	int getIID();
	void setIID(int id);
private:
	int iID;
	int iAmt;
	float iSG2;

	FString iName;
	FString iDesc;
	FString iTyp;
	
};

Item.cpp:

// Fill out your copyright notice in the Description page of Project Settings.

#include "Item.h"

UItem::UItem() {}
UItem::~UItem() {}
UFUNCTION(BlueprintCallable, Category = "Object") void UItem::Create(UClass *ObjectClass, UObject* &CreatedObject){ CreatedObject = NewObject<UObject>((UObject*)GetTransientPackage(), ObjectClass); }
UFUNCTION(BlueprintCallable, Category = "Item") int UItem::GetIAtm() { return iAmt; }
UFUNCTION(BlueprintCallable, Category = "Item") float UItem::GetISG2() { return iSG2; }
UFUNCTION(BlueprintCallable, Category = "Item") float UItem::GetAllISG2() { return iAmt * iSG2; }
UFUNCTION(BlueprintCallable, Category = "Item") FString UItem::GetIName() { return iName; }
UFUNCTION(BlueprintCallable, Category = "Item") FString UItem::GetIDesc() { return iDesc; }
UFUNCTION(BlueprintCallable, Category = "Item") FString UItem::GetIType() { return iTyp; }
UFUNCTION(BlueprintCallable, Category = "Item") void UItem::SetISG2(float sg2) { iSG2 = sg2; }
UFUNCTION(BlueprintCallable, Category = "Item") void UItem::SetIName(FString nm) { iName = nm; }
UFUNCTION(BlueprintCallable, Category = "Item") void UItem::SetIDesc(FString desc) { iDesc = desc; }
UFUNCTION(BlueprintCallable, Category = "Item") void UItem::SetIType(FString typ) { iTyp = typ; }
UFUNCTION(BlueprintCallable, Category = "Item") void UItem::UpdateIAmt(int newVal, FString op) {
	if (op == "add") {
		iAmt += newVal;
	}
	else if (op == "sub") {
		if (iAmt - newVal < 0) { iAmt = 0; }
		else { iAmt -= newVal; }
	}
	else if (op == "set") {
		iAmt = newVal;
	}
}

int UItem::getIID() { return iID; }
void UItem::setIID(int id) { iID = id; }

DataHandler.h:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DatabaseHandler.h"
#include "DataHandler.generated.h"

/**
 * 
 */
UCLASS(Blueprintable)
class OVMININGTECHDEMO_API UDataHandler : public UObject
{
	GENERATED_BODY()
	
public:
	UDataHandler();
	~UDataHandler();

	//Item Returns
	string getIName();
	string getIDesc();
	float getISG2();
	int getIID();
	TArray<item> getAllItems();


	//Database Functions
	void queryData(string tbl, string op, int id);

	//Data Retrieval

	//Items
	void getItems();
	void getItem();
	TArray<item> returnItems();
	item returnItem();

	UFUNCTION(BlueprintCallable) void setupDataHandler(UDatabaseHandler* dbHandle);
	UFUNCTION(BlueprintCallable) UDatabaseHandler* GetDBHandle();
	UFUNCTION(BlueprintCallable) void SetDBHandle(UDatabaseHandler* dbHndl);

	

private:
	TArray<item> items;
	item itm;
	UDatabaseHandler* dbHandle;    
};

DataHandler.cpp:

// Fill out your copyright notice in the Description page of Project Settings.

#include "DataHandler.h"

UDataHandler::UDataHandler() {
	dbHandle = NewObject<UDatabaseHandler>();
}
UDataHandler::~UDataHandler() {}

//Item Returns
string UDataHandler::getIName() { return itm.iName; }
string UDataHandler::getIDesc() { return itm.iDesc; }
float UDataHandler::getISG2() { return itm.iSG2; }
int UDataHandler::getIID() { return itm.iID; }
TArray<item> UDataHandler::getAllItems() { return items; }


//Database Functions
void UDataHandler::queryData(string tbl, string op, int id) {
	//dbHandle->ConOutput = "Test";

	//if (tbl == "Resource") {
		dbHandle->buildQueryString(tbl, op, 0, vector<string>{});

		items.Empty();

		dbHandle->qryItmData(id == 0 ? false : true);

		if (id == 0) {
			items = dbHandle->getRtnItems();
		}
		else {
			itm = dbHandle->getRtnItem();
		}
	//}
}

void UDataHandler::getItems() { items = dbHandle->getRtnItems(); }
void UDataHandler::getItem() { itm = dbHandle->getRtnItem(); }
TArray<item> UDataHandler::returnItems() { return items; }
item UDataHandler::returnItem() { return itm; }

UFUNCTION(BlueprintCallable) void UDataHandler::setupDataHandler(UDatabaseHandler* dbh) {
	dbHandle = dbh;
}

UFUNCTION(BlueprintCallable) UDatabaseHandler* UDataHandler::GetDBHandle() { return dbHandle; }
UFUNCTION(BlueprintCallable) void UDataHandler::SetDBHandle(UDatabaseHandler* dbHndl) {
	dbHandle = dbHndl;
}

DatabaseHandler.h:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "sqlite3.h"
#include <string>
#include "DataPlaceholders.h"
#include "DatabaseHandler.generated.h"

using std::string;
using std::to_string;

/**
 * 
 */
UCLASS(Blueprintable)
class OVMININGTECHDEMO_API UDatabaseHandler : public UObject
{
	GENERATED_BODY()

public:

	UDatabaseHandler();
	~UDatabaseHandler();

	UFUNCTION(BlueprintCallable, Category = "Object")
		static void Create(UClass *ObjectClass, UObject* &CreatedObject);

	UFUNCTION(BlueprintCallable, Category = "Database")
		bool OpenDB(FString dbName);

	UPROPERTY(BlueprintReadOnly, Category = "Database")
		FString ConOutput;

	void buildQueryString(string tbl, string op, int id, vector<string> fldList);
	bool qryItmData(bool bSingleton);
	TArray<item> getRtnItems();
	item getRtnItem();

	//Utilities
	void finalizeStmt(sqlite3_stmt* stmt);
	void closeDB();
	
private:
	sqlite3 *dbCon;
	sqlite3_stmt *sqlStmt;

	string sqlStr;

	TArray<item> items;
	item itm;

	int rslt; //Stores various sqlite3 function call results for ifing
	int cols; //Stores the number of returned columns

	const char* data; //Stores strings from DB to check for nulls
};

DatabaseHandler.cpp:

// Fill out your copyright notice in the Description page of Project Settings.

#include "DatabaseHandler.h"


UDatabaseHandler::UDatabaseHandler() {}
UDatabaseHandler::~UDatabaseHandler() {}

UFUNCTION(BlueprintCallable, Category = "Object")
void UDatabaseHandler::Create(UClass *ObjectClass, UObject* &CreatedObject) { CreatedObject = NewObject<UObject>((UObject*)GetTransientPackage(), ObjectClass); }
UFUNCTION(BlueprintCallable, Category = "Database")
bool UDatabaseHandler::OpenDB(FString dbName) {
	int rslt = sqlite3_open_v2(TCHAR_TO_ANSI(*dbName), &dbCon, SQLITE_OPEN_READWRITE, NULL);
	//"F:/UE4/Projects/OVMiningTechDemo/Source/OVMiningTechDemo/Private/OVDB.sqlite"

	if (rslt != SQLITE_OK) {
		string errMsg = "Could not open database: " + (string)sqlite3_errmsg(dbCon) + "   " + to_string(rslt);
		ConOutput = errMsg.c_str();

		return false;
	}
	else {
		ConOutput = "Connected to database successfully";
		return true;
	}
}

void UDatabaseHandler::buildQueryString(string tbl, string op, int id, vector<string> fldNames) {
	// qConOutput = "Creating query string";
	string fldList = "";


	sqlStr = op + " ";
	
	if (fldNames.size() == 0) {
		sqlStr += "* ";
	} else{
		for (string fld : fldNames) {
			fldList += "," + fld;
		}

		fldList.erase(0, 1);
		sqlStr += fldList + " ";
	}

	sqlStr += "from " + tbl;	

	ConOutput = sqlStr.c_str();
}

bool UDatabaseHandler::qryItmData(bool bSingleton) {
	bool bErrors = false;

	rslt = sqlite3_prepare_v2(dbCon, sqlStr.c_str(), sqlStr.size(), &sqlStmt, 0);

	if (rslt == SQLITE_OK)
	{
		if (bSingleton) {
			itm = item();
		}
		else {
			items = TArray<item>();
		}

		while (sqlite3_step(sqlStmt) == SQLITE_ROW) {
			cols = sqlite3_column_count(sqlStmt);
			items.Add(item());

			if (cols != 0)
			{
				if (bSingleton) {					
						itm.iID = sqlite3_column_int(sqlStmt, 0);
						
						data = (char*)sqlite3_column_text(sqlStmt, 1);

						if (data != NULL)
						{
							itm.iName = data;
							bErrors = false;
						}
						
						data = (char*)sqlite3_column_text(sqlStmt, 2);

						if (data != NULL)
						{
							itm.iDesc = data;
							bErrors = false;
						}
						
						itm.iSG2 = (float)sqlite3_column_double(sqlStmt, 3);
						
				}
				else {
					for (int i1 = 0; i1 <= cols; i1++)
					{
						switch (i1)
						{
						case 0:
							items[items.Num() - 1].iID = sqlite3_column_int(sqlStmt, i1);
							break;

						case 1:
							data = (char*)sqlite3_column_text(sqlStmt, i1);

							if (data != NULL)
							{
								items[items.Num() - 1].iName = data;
								bErrors = false;
							}
							break;

						case 2:
							data = (char*)sqlite3_column_text(sqlStmt, i1);

							if (data != NULL)
							{
								items[items.Num() - 1].iDesc = data;
								bErrors = false;
							}
							break;

						case 3:
							items[items.Num() - 1].iSG2 = (float)sqlite3_column_double(sqlStmt, i1);
							break;

						default:
							break;
						}
					}
				}
			}

			else
			{
				
			}
		}
	}

	else
	{
		
	}

	string inames = "";

	for (item itm : items) {
		inames += itm.iName;
	}

	ConOutput = inames.c_str();

	finalizeStmt(sqlStmt);

	return true;
}

TArray<item> UDatabaseHandler::getRtnItems() {
	return items;
}
item UDatabaseHandler::getRtnItem() { return itm; }
void UDatabaseHandler::finalizeStmt(sqlite3_stmt* stmt)
{
	if (sqlite3_finalize(stmt) != SQLITE_OK)
	{
		
	}

	else
	{
		
	}
}

void UDatabaseHandler::closeDB()
{
	if (sqlite3_close(dbCon) != SQLITE_OK)
	{
		
	}

	else
	{
		
	}
}

DataPlaceholders.h:

#pragma once
#ifndef	DATAPLACEHOLDERS_H
#define DATAPLACEHOLDERS_H

#include <string>
#include <vector>

using std::string;
using std::to_string;
using std::vector;

struct resource {
	int rID;
	float rSG2;
};

struct item {
	int iID;
	string iName;
	string iDesc;
	float iSG2;
};



#endif

Now let me walk you through the blueprint stuff. I will just attach all three images below and then guide you through under those.

So, the first thing I do is, on FlyingPawn’s BeginGameplay event, create the HUD and store it as a reference in FlyingPawn. A DBLoad actor blueprint also fires some actions on BeginGameplay to create a DatabaseHandler object to be used by the MainHUD blueprint.

On the OnConstruct event for the MainHUD I am grabbing the DBLoad actor and pulling the Database Handler reference from it and setting it as the MainHUD’s DBH reference and then I am calling a function to open the database.

Once that is done, back in the FlyingPawnBP execution path, I am creating a UInventory object, setting it to the Cargo blueprint variable, and then I call a function to get a pointer to the UInventory DataHandler pointer. Once I have that I am using another function, this one a member of DataHandler, to set the DataHandler’s DatabaseHandler pointer.

It is at this point that, if I am watching this function from the debugger in VS2017, that I am always seeing a null pointer passed through. I believe all of my other problems are caused by this issue. Now, before I exposed the DatabaseHandler and DataHandler pointers to the editor these worked fine with no errors but when I would go to call the UpdateInventory function in the MainHUD the UInventory TArray invItems property was always empty.

This is an issue with that array and not the data collection as in the function that populates this array after I query the data from the database I am setting the value of a MainHUD bound textbox with some text to print out the last item added to the inventory. It was correctly printing out Gold Ore before I exposed the handler pointers.

So some questions:

How do we go about passing a valid pointer to a custom UClass that has been created as a blueprint variable back to a custom UClass? I want to pass the instanced DatabaseHandler class that we created in the DBLoad actor by grabbing it from the MainHUD so that we are connect to the database and don’t need to close that one to reopen another connection to the database (doing so throws a File_Locked error because SQLite only allows 1 connection open at a time).

Or, why was my TArray always empty once I got to the point where I called the GetInventory function?

The answers to either of these should be enough to get this thing working unless anyone spots some other kind of more general structural issue with how I have written the code. This is my first foray into C++ coding for UE so I am still not familiar with the quirks of the engine and how certain things you do in normal C++ won’t or can’t work in UE’s implementation.

If you need a stack trace or anything else please let me know.

Thanks.

Can you clean code a bit, remove all UFUNCTION from cpp, they are not needed there as UHT only parse the header files.

You also should use UPROPERTY() in all variables that have pointer (or array of them) to UObject, without engine don’t know if object is referenced there and assume it not referenced anywhere and consider it trash and clean it.

I managed to solve this waiting for a mod to approve. I basically just removed all of the uproperties from the DataHandler and DatabaseHandler handler instances. Instead I instance them locally in Inventory (UDataSystem) and then in DataSystem(UDatabaseHandler). Instead of having a global instance of either I am just going to have each class that needs database access create their own local instances and just remember to close out the db connection once they are done with them.

Once I did that and cleaned up the mess that those changes left my blue prints everything works perfectly.