I tried replicating the problem with the given intel, but it was impossible, so I created a small example (as close as possible to what's described in the question) - Also referred to as [SO]: How to create a Minimal, Reproducible Example (reprex (mcve)) (that should be included in the question BTW)
So, the problem that I'm illustrating here, is:
I am using (on Win 10 x64 (10.0.16299.125)):
The structure consists of:
VStudio project / solution
- Source file (main00.cpp (renamed it from main.cpp, but didn't feel like doing all the screenshots (containing it) all over again))
Python module (experiment_test.py)
Test file (test_file.txt)
main00.cpp:
#include <iostream>
#include <string>
#if defined(_DEBUG)
# undef _DEBUG
# define _DEBUG_UNDEFINED
#endif
#include <Python.h>
#if defined(_DEBUG_UNDEFINED)
# define _DEBUG
# undef _DEBUG_UNDEFINED
#endif
#define MOD_NAME "experiment_test"
#define FUNC_NAME "function_name"
#define TEST_FILE_NAME "..\\test_dir\\test_file.txt"
using std::cout;
using std::cin;
using std::endl;
using std::string;
int cleanup(const string &text = string(), int exitCode = 1) {
Py_Finalize();
if (!text.empty())
cout << text << endl;
cout << "Press ENTER to return...\n";
cin.get();
return exitCode;
}
int main() {
string fName = TEST_FILE_NAME, result;
PyObject *pName = NULL, *pModule = NULL, *pDict = NULL, *pFunc = NULL, *pArgs = NULL, *pValue = NULL, *pResult = NULL;
Py_Initialize();
pName = PyUnicode_FromString(MOD_NAME);
if (pName == NULL) {
return cleanup("PyUnicode_FromString returned NULL");
}
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule == NULL) {
return cleanup(string("NULL module: '") + MOD_NAME + "'");
}
pDict = PyModule_GetDict(pModule);
if (pDict == NULL) {
return cleanup("NULL module dict");
}
pFunc = PyDict_GetItemString(pDict, FUNC_NAME);
if (pFunc == NULL) {
return cleanup(string("module '") + MOD_NAME + "' doesn't export func '" + FUNC_NAME + "'");
}
pArgs = PyTuple_New(1);
if (pArgs == NULL) {
return cleanup("NULL tuple returned");
}
pValue = PyUnicode_FromString(fName.c_str());
if (pValue == NULL) {
Py_DECREF(pArgs);
return cleanup("PyUnicode_FromString(2) returned NULL");
}
int setItemResult = PyTuple_SetItem(pArgs, 0, pValue);
if (setItemResult) {
Py_DECREF(pValue);
Py_DECREF(pArgs);
return cleanup("PyTuple_SetItem returned " + setItemResult);
}
pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
Py_DECREF(pValue);
if (pResult == NULL) {
return cleanup("PyObject_CallObject returned NULL");
} else {
int len = ((PyASCIIObject *)(pResult))->length;
char *res = PyUnicode_AsUTF8(pResult);
Py_DECREF(pResult);
if (res == NULL) {
return cleanup("PyUnicode_AsUTF8 returned NULL");
} else {
cout << string("C(++) - Python call: ") << MOD_NAME << "." << FUNC_NAME << "('" << fName << "') returned '" << res << "' (len: " << len << ")" << endl;
}
}
return cleanup("OK", 0);
}
Notes:
The _DEBUG / _DEBUG_UNDEFINED stuff at the beginning - a (lame) workaround (gainarie) to link against Release Python lib (python35.lib) when building in Debug mode (as opposed to python35_d.lib) - read below
As I said, tried to simplify the code (got rid of the PyCallable_Check test)
It's easily noticeable that the code is written in C style, although it uses the C++ compiler
Since Python API ([Python.Docs]: Embedding Python in Another Application) (both extending / embedding) uses pointers, make sure to test for NULLs, otherwise there's a high chance getting SegFault (Access Violation)
Added the [Python.Docs]: Reference Counting - void Py_DECREF(PyObject *o) statements to avoid memory leaks
Build (compile / link) / Run options (obviously, you got past these, since you were able to run your program, but I'm going to list them anyway - for sure there are some shortcuts here, when dealing with more than one such project):

Notes:
The path ("c:\Install\x64\Python\Python\3.5") points to the installation downloaded from the official site
Obviously, for 032bit, the path must be set accordingly (to 32bit Python)
This path contains (as expected) a Release version, and this is fine as long as I don't need to get into Python code (and as long as I don't mess around with memory - as (when building my app in Debug mode) I have 2 C runtimes in my .exe - check the links below to see what happens when tampering with MSVC runtimes (UCRTs)):
Compile:
Let VStudio know about the Python include files location:

Link:
Let VStudio know about the Python lib files location (if only pythonxx*.lib (PYTHONCORE) is required, nothing extra needed, since PYTHONCORE is included by default by Python code; otherwise, all the rest should be specified in the [MS.Learn]: .Lib Files as Linker Input:

Run / Debug - let:

experiment_test.py:
import codecs
import os
import shutil
def function_name(file_name):
print("Py - arg: '{:s}'".format(file_name))
if not os.path.isfile(file_name):
return file_name
with open(file_name, mode="rb") as f:
content = f.read().decode()
print("Py - Content len: {:d}, Content (can spread across multiple lines): '{:s}'".format(len(content), content))
return content
Notes:
An almost dummy module, as specified at the beginning
Works only with text files (decode will fail for binary files)
Imports modules that aren't used, to see that they are OK (that's obvious, if one such import statement succeeds, all should)
Prints some data on stdout (to be matched with what's on the C++ side)
Located in a path known by Python (%PYTHONPATH% from previous step)
Has 1 argument (file_name) - crucial difference compared to the one in the question which doesn't have any (don't know whether that's a logical mistake or a typo like one)
test_dir\test_file.txt:
line 0 - dummy
line 1 - gainarie
Output (VStudio console):
Py - arg: 'test_dir\test_file.txt'
Py - Content len: 33, Content (can spread across multiple lines): 'line 0 - dummy
line 1 - gainarie'
C(++) - Python call: experiment_test.function_name('test_dir\test_file.txt') returned 'line 0 - dummy
line 1 - gainarie' (len: 33)
OK
Press ENTER to return...
Final note: