Download

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.

FlyingPawnBP Events:

DBLoad Actor Events:

MainHUD Events:

MainHUD UpdateInventory Function:

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.

I managed to figure something out. So, after digging through pages and pages of posts, help docs, and anything else I could find on how to properly pass a blueprint instantiated UObject to a UClass in the code I gave up. Instead I ripped out all of that code and the exposed properties and recoded parts of the classes mentioned above so that they all have their own self-contained DataHandler/DatabaseHandler solution.

Well, at least Inventory does, any other classes that need database access will as well. I use NewObject within Inventory to create a new UDataHandler object and then call a function in that newly created instance to call NewObject on UDatabaseHandler as well.

What I will have to do now is just be sure I am closing any open connections to the database file once the data has been retrieved. I also will need to consider performance in terms of when I need to have multiple class to the database from one or more classes. The only scenario I currently have in my console-only code is when I am saving or loading save game data to the database. That will be done through one class which will need to then access any existing game objects, such as the player, to load the save data or to obtain the data for saving.

So in the end I ended up where I thought I would which is to have something that is much more code-centric than balanced between code and blueprints. Nothing wrong with that, I prefer it that way anyway as I find blueprinting for complex tasks to be highly convoluted and inefficient a lot of the time.