建立一个可运行HAPI的独立C++程序。(不需要启动UE引擎,但是借用了一些官方插件的代码)
官方插件加载HAPI的细节可以看到在官方插件的模块启动函数中有加载HAPI的相关逻辑:
其中 HAPILibraryHandle是读取到的 dll 的 handle,路径就是Houdini安装目录中的binlibHAPIL.dll:
随后调用了FHoudiniApi::InitializeHAPI来得到每个函数的地址。
如果打开HoudiniApi.h/cpp文件,能看到大量针对每个接口函数的代码。(代码的结构可参阅本篇附录)
步骤0. 创建一个独立的C++工程以 “控制台应用” 为模板即可。
HAPI头文件的文件夹在Houdini安装目录toolkitincludeHAPI,将这个文件夹拷贝到工程里来。
首先要切换为64位来和DLL匹配:
这里有个小插曲是,我加载DLL一直失败,地址是0,而返回的错误代码是193。最后在《VS2015 LoadLibrary加载DLL失败的解决方案,GetLastError()返回值193》中才注意到问题是,应该切换为64位。
加载DLL用的函数为LoadLibrary(需要 #include
随后,我还尝试从DLL中读取了一个函数,然后打印其值,看是否是0。
#include#include #include "HAPI/HAPI.h" int main() { //Houdini安装目录: const std::wstring HoudiniPath = L"C:/Program Files/Side Effects Software/Houdini 19.0.383"; //HAPI的DLL的路径: const std::wstring HAPIDllPath = L"/bin/libHAPIL.dll"; //加载HAPI的DLL: HINSTANCE HAPILibraryHandle = LoadLibrary((HoudiniPath + HAPIDllPath).c_str()); //定义AddAttribute函数的类型 typedef HAPI_Result(*AddAttributeFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_PartId part_id, const char* name, const HAPI_AttributeInfo* attr_info); //从DLL中加载AddAttribute函数 AddAttributeFuncPtr AddAttribute = (AddAttributeFuncPtr)GetProcAddress(HAPILibraryHandle, "HAPI_AddAttribute"); //测试打印,看是否是非0值 std::cout << AddAttribute << std::endl; //释放DLL FreeLibrary(HAPILibraryHandle); return 0; }
结果非0,说明读取成功了:
上面的代码只读取了一个函数。但是HAPI里有相当多的函数,每个函数都写一遍显然太慢了。
因此,可以借用官方插件的 HoudiniApi.h/cpp 代码(他们其实也是自动生成的)。
下面,将 HoudiniApi.h/cpp 拷贝进工程,然后需要做一些小小的改动:
对于 HoudiniApi.h,要去掉不再需要的头文件 “HAL/PlatformProcess.h”,以及宏 HOUDINIENGINE_API。
对于 HoudiniApi.cpp,去掉头文件 “HoudiniEnginePrivatePCH.h”,但是补上
另外,为了编译通过,需要补上一些宏以及函数的实现,代码如下:
#define TEXT(x) x
struct FPlatformProcess
{
static void* GetDllExport(void* DllHandle, const char* ProcName)
{
return GetProcAddress((HMODULE)DllHandle, ProcName);
}
};
//这个函数名字和已有的宏冲突了,因此undef
#undef GetGeoInfo
这样,编译就可以通过了。
步骤4. 测试使用HAPI所有准备已经完成,下面就可以测试用一下HAPI了,代码如下:
#include#include #include "HAPI/HAPI.h" #include "HoudiniApi.h" int main() { //Houdini安装目录: const std::wstring HoudiniPath = L"C:/Program Files/Side Effects Software/Houdini 19.0.383"; //HAPI的DLL的路径: const std::wstring HAPIDllPath = L"/bin/libHAPIL.dll"; //加载HAPI的DLL: HINSTANCE HAPILibraryHandle = LoadLibrary((HoudiniPath + HAPIDllPath).c_str()); //加载所有HAPI接口函数 FHoudiniApi::InitializeHAPI(HAPILibraryHandle); //一段测试 { //创建session HAPI_Session session; FHoudiniApi::CreateInProcessSession(&session); //初始化session HAPI_CookOptions options; FHoudiniApi::Initialize(&session, &options, false, -1, "", "", "", "", ""); //得到obj节点 HAPI_NodeId ObjNode = -1; FHoudiniApi::GetManagerNodeId(&session, HAPI_NodeType::HAPI_NODETYPE_OBJ, &ObjNode); //创建Geo节点 HAPI_NodeId Geonode = -1; FHoudiniApi::CreateNode(&session, ObjNode, "geo", "MyGeo", true, &GeoNode); //创建一个测试用节点 HAPI_NodeId TestNode = -1; FHoudiniApi::CreateNode(&session, GeoNode, "platonic", "MyTest", true, &TestNode); //设置其为二十面体 FHoudiniApi::SetParmIntValue(&session, TestNode, "type", 0, 3); //测试输出到bgeo中 FHoudiniApi::SaveGeoToFile(&session, TestNode, "D:/Temp/TestGeo.bgeo"); //清理并关闭Session FHoudiniApi::Cleanup(&session); FHoudiniApi::CloseSession(&session); } //释放DLL FreeLibrary(HAPILibraryHandle); return 0; }
在上面的测试代码中,我创建了一个测试用的二十面体节点,然后将其输出为bgeo文件:
将其使用 GPlay(应该是bgeo格式默认的打开方式)打开后可以看到:
看来HAPI成功运行了。
虽然上面代码成功运行了,但是在退出的时候还会有报错:
待之后研究。
在上面代码中,虽然看起来只加载了一个dll,但是实际用到的dll非常多,可以在输出窗口看到他们:
省略掉针对每个接口函数的形式重复的代码后,他们的结构实际上很简单:
HoudiniApi.h:
struct HOUDINIENGINE_API FHoudiniApi
{
public:
static void InitializeHAPI(void* LibraryHandle);
static void FinalizeHAPI();
static bool IsHAPIInitialized();
public:
typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info);
typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name);
...
public:
static AddAttributeFuncPtr AddAttribute;
static AddGroupFuncPtr AddGroup;
...
public:
static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info);
static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name);
...
};
HoudiniApi.cpp:
FHoudiniApi::AddAttributeFuncPtr
FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub;
FHoudiniApi::AddGroupFuncPtr
FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub;
...
void
FHoudiniApi::InitializeHAPI(void* LibraryHandle)
{
if(!LibraryHandle) return;
FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute"));
FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup"));
...
}
void
FHoudiniApi::FinalizeHAPI()
{
FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub;
FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub;
...
}
bool
FHoudiniApi::IsHAPIInitialized()
{
return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub );
}
HAPI_Result
FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info)
{
return HAPI_RESULT_FAILURE;
}
HAPI_Result
FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name)
{
return HAPI_RESULT_FAILURE;
}
...
可以看到:
函数名是函数指针,其类型是函数名FuncPtr。函数名FuncPtr定义了函数的参数和返回值类型,函数名EmptyStub是空实现,如果没有正常加载到HAPI实际的函数,则默认就是空实现。在InitializeHAPI中,每个函数名通过名字加载到DLL中的函数。



