For the past couple of days, I've been trying to learn WinRT, working with the manifests, loading the C++ or C# DLLs, etc. The next logical step for me was to try to build a plugin system that loads my WinRT DLLs that can be written in C++ or C# into my Win32 apps. This seems to be a bit more of a challenge because of unclear documentation. What makes this even more unclear to me is the underlying usage of COM and WRL which I don't have experience with. I've prepared a minimal viable example on GitHub for anyone that could help me figure this out.
A bit of elaboration on why C++/WinRT is only being used from step 6. I'm aiming at building a plugin system around dynamically/runtime loaded DLLs. I don't want to have any explicit references to DLLs or WinMD files in my projects. The goal is to basically have a Win32 application that has a statically linked abstraction project that contains the WinRT generated header files and a set of DLLs that also make use of the same abstraction project and are dynamically loaded during the runtime of the Win32 application. I've built these kind of systems before for C++ and C# but I feel lost with the WinRT layers and which methods to call when to utilize the WinRT factories correctly.
Everything seems to be working fine until step 6 when I'm not sure what would come next.
typedef HRESULT(WINAPI *dllAbstractionFactoryFp)(HSTRING activableClassId, IActivationFactory **factory);
...
// #### 0. Prep
HRESULT hRes;
// #### 1. Load DLL into memory
HMODULE hLibrary;
hLibrary = LoadLibrary(L"TicketMachine.dll");
//hLibrary = ::LoadLibraryEx(L"TicketMachine.dll", NULL, NULL);
if (!hLibrary) return EXIT_FAILURE;
// #### 2. Get the pointer to the DllGetActivationFactory of the loaded DLL
dllAbstractionFactoryFp abstractionFactoryFp;
abstractionFactoryFp = (dllAbstractionFactoryFp)GetProcAddress(hLibrary, "DllGetActivationFactory");
if (!abstractionFactoryFp) return EXIT_FAILURE;
// #### 3. Define the class name to activate
HSTRING activableClassName;
WindowsCreateString(L"TicketMachine.Ticket", 20, &activableClassName);
if (!activableClassName) return EXIT_FAILURE;
// #### 4. Set the activation factory
IActivationFactory* activationFactoryP;
hRes = (abstractionFactoryFp)(activableClassName, &activationFactoryP);
if (!activationFactoryP || hRes != S_OK) return EXIT_FAILURE;
// #### 5. Get the runtime class name to check if everything is going fine
HSTRING className;
activationFactoryP->GetRuntimeClassName(&className);
if (!className) return EXIT_FAILURE;
// #### 6. The identification GUID
IID iid = winrt::guid_of<winrt::TicketMachine::ITicketFactory>();
winrt::TicketMachine::ITicketFactory* iTicketFactory;
hRes = activationFactoryP->QueryInterface(iid, (void**) & iTicketFactory); // iTicketFactory stays uninitialized
if (!iTicketFactory || hRes != S_OK) return EXIT_FAILURE;
// ### ?. ...
;
// #### ?. Profit???
;
With an explicitly linked WinMD file I can just call the class constructor or even the ::impl::call_factory method that will instantiatie the object for me. This of course won't work if the DLL is loaded dynamically on runtime.
winrt::TicketMachine::Ticket staTicket;
// or
winrt::Windows::Foundation::IInspectable baseInterface, innerInterface;
winrt::TicketMachine::Ticket ticket = winrt::impl::call_factory<winrt::TicketMachine::Ticket, winrt::TicketMachine::ITicketFactory>([&](winrt::TicketMachine::ITicketFactory const& f) { return f.CreateInstance(baseInterface, innerInterface); });
This is the GitHub repository where I've prepared this code and which should compile and run out of the box, https://github.com/zborowa/winrt-dynamic-dll-loading
FYI, I've already seen the following questions and also found the following resources:
Loading WinRT component without referencing the DLL/assembly
Consuming a DLL(Universal Apps) from a WinRT Component
Creating WinRT component with static methods in C++ / WRL
https://devblogs.microsoft.com/oldnewthing/20201118-00/?p=104459
https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f
These Aren't the COM Objects
You're Looking For
And many more...
Related
I am currently trying to piece together a shader-based music visualizer. The plan is to read data from the current MMDevice, which I'm trying to follow the documentation for, but I must be doing something wrong because I had to jump through all sorts of hoops to get even just the MMDeviceEnumerator to compile.
In order for the uuids of MMDeviceEnumerator and IMMDeviceEnumerator to be defined, I had to set #define WINAPI_FAMILY WINAPI_FAMILY_GAMES. This was also required for EDataFlow and ERole enumerations to be defined. My first question is if I've missed some configuration somewhere, or if this is the intended method of enabling these things.
Currently, I have the following code in an AudioStream class:
class AudioStream {
public:
AudioStream() {
//SUCCEEDING(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
SUCCEEDING(CoCreateInstance(
__uuidof(IMMDeviceEnumerator),
NULL,
CLSCTX_ALL,
__uuidof(MMDeviceEnumerator),
(void**)&this->mmDeviceEnumerator));
SUCCEEDING(this->mmDeviceEnumerator->GetDefaultAudioEndpoint(
eRender,
eConsole,
&this->mmDevice));
}
private:
IAudioClient* audioClient = NULL;
IAudioCaptureClient* captureClient = NULL;
IMMDeviceEnumerator* mmDeviceEnumerator = NULL;
IMMDevice* mmDevice = NULL;
};
If you're familiar with what the DirectX 12 project template looks like, this object is being instantiated in the Sample3DSceneRenderer constructor. The main issue I'm having right now is the following two errors which are immediately raised during startup:
onecore\com\combase\dcomrem\resolver.cxx(2299)\combase.dll!75AA0DFF: (caller: 75B1CF2C) ReturnHr(1) tid(42a8) 80040154 Class not registered
onecore\com\combase\dcomrem\resolver.cxx(2507)\combase.dll!75B1CF4D: (caller: 75AA29E4) ReturnHr(2) tid(42a8) 80040154 Class not registered
This causes the entire app to hang, and the project template visualization to never appear (the succeeding macro exits). Does anyone have any idea why this is failing? It must have to be something with the CoCreateInstance call :(
You are writing a Universal Windows Platform (UWP) app because that's what the "built-in" DirectX 12 App project template creates in Visual Studio. UWPs do not have access to all the same APIs and IMMDevice is not part of the UWP API surface area.
The fact that you defined WINAPI_FAMILY_GAMES means you hacked the API Family Partition macros which will define the API in a UWP context, but it doesn't mean that API actually works from the AppContainer process that all UWPs run in.
You really have two options:
(1) If you want to write a UWP, then you will need to enumerate audio devices via the proper Windows Runtime APIs which are in the Windows::Devices::Enumeration namespace.
Assuming you are using C++/CX language extensions (instead of the more modern C++/WinRT projections), then this code works:
auto operation = DeviceInformation::FindAllAsync(DeviceClass::AudioRender);
while (operation->Status == Windows::Foundation::AsyncStatus::Started)
{
Sleep(100);
}
if (operation->Status != Windows::Foundation::AsyncStatus::Completed)
{
throw std::runtime_error("FindAllAsync");
}
DeviceInformationCollection^ devices = operation->GetResults();
for (unsigned i = 0; i < devices->Size; ++i)
{
using Windows::Devices::Enumeration::DeviceInformation;
DeviceInformation^ d = devices->GetAt(i);
// d->Id->Data();
// d->Name->Data();
}
Also, if you want to get access to the audio capture device from a UWP, you must add a capability to your manifest to request it via <DeviceCapability Name="microphone"/>. See Microsoft Docs.
You should take the time to read the Microsoft Docs on UWPs so you have a better idea of what's supported and what's not.
(2) If you want to write a Win32 desktop app, use the directx-vs-templates instead which include DirectX 12 starting templates for Win32 desktop apps (plus alternative DirectX templates for UWP if that's your thing).
Whichever appmodel you use, you may want to take a look at DirectX Tool Kit for Audio.
BTW, WINAPI_FAMILY_GAMES is used by the Microsoft GDK for Xbox which is for writing titles for Xbox One and Xbox Series X|S. It uses Win32 APIs and doesn't use Windows Runtime APIs, so it has the IMMDevice interface in it's API surface. See Microsoft Docs.
I'm currently working on porting the Boost 'filesystem' library to Windows Phone 8.1. I succeeded in porting about half of the problematic functions by substituting 'banned' Win32 API functions with other, newer non-banned functions.
I now need to tackle those Boost functions for which there is no alternative Win32 API. Based on Steve Gates's excellent port of other Boost libraries to WP8.1, and in a private communication with him, I have decided to use WRL within the Boost code, rather than C++/CX.
To learn WRL and get my bearings, I wrote a minimal WP8.1 app consisting of a C++/CX client app that calls down into a Windows Runtime Component, the latter written in C++. In the runtime component I have a function that attempts to determine the file system path of the Picture Library. The problem I'm encountering is that the final path I get (i.e., pszPath) is an empty string.
Here is the runtime component code:
void Class1::Test1()
{
HRESULT hr;
HString hstrKnownFolders;
hstrKnownFolders.Set(RuntimeClass_Windows_Storage_KnownFolders);
// Get the Activation Factory
ComPtr<IActivationFactory> pKnownFoldersActivationFactory;
hr = ABI::Windows::Foundation::GetActivationFactory(hstrKnownFolders.Get(),
&pKnownFoldersActivationFactory);
if (FAILED(hr))
{
::Microsoft::WRL::Details::RaiseException(hr);
}
// QI for the IKnownFoldersStatics
ComPtr<IKnownFoldersStatics> pKnownFolders;
hr = pKnownFoldersActivationFactory.As(&pKnownFolders);
if (FAILED(hr))
{
::Microsoft::WRL::Details::RaiseException(hr);
}
// Get the Pictures library folder
ComPtr<IStorageFolder> pStorageFolder;
hr = pKnownFolders->get_PicturesLibrary(&pStorageFolder);
if (FAILED(hr))
{
::Microsoft::WRL::Details::RaiseException(hr);
}
// QI for the IStorageItem interface (from which IStorageFolder is derived)
ComPtr<IStorageItem> pItem;
hr = pStorageFolder.As(&pItem);
// Get the path corresponding to the folder
HSTRING hsPath;
pItem->get_Path(&hsPath);
PCWSTR pszPath = WindowsGetStringRawBuffer(hsPath, 0);
}
At the end of the function, the function get_Path() returns an empty string. Can anyone shed light one what I'm doing wrong, and how it should be done?
Thanks in advance!
No path is the right result: the Pictures library is a shell folder which compiles data from several locations (such as the public Pictures directory and the user's picture directory). The Pictures library itself doesn't have a path.
Individual items within the library probably have paths, but they may not be paths in the same file system directory.
StorageFiles are not limited to "files" from the file system. They can also include objects from elsewhere in the shell and objects from other apps. All of these are represented as file streams, but don't necessarily have file system paths.
I am learning basics in COM, so I try to write simple COM component in VS2010 C++ Windows 7.
I created dll for component, registered it using following REG-file:
REGEDIT
HKEY_CLASSES_ROOT\Math.Component.1 = Chapter 6 Math Component
HKEY_CLASSES_ROOT\Math.Component.1\CurVer = Math.Component.1
HKEY_CLASSES_ROOT\Math.Component.1\CLSID = {A888F560-58E4-11d0-A68A-0000837E3100}
HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100} = Chapter 6 Math Component
HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\ProgID = Math.Component.1
HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\VersionIndependentProgID = Math.Component
HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\InprocServer32 = D:\Proga\COM\Debug\server.dll
HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\NotInsertable
In dll I exported (stubs for last two)
DllGetClassObject
DllCanUnloadNow
DllRegisterServer
DllUnregisterServer
In my COM client CLSIDFromProgID( szWideProgID, &clsid ); works as expected, returning {A888F560-58E4-11d0-A68A-0000837E3100}.
But when I try to get access to IClassFactory REGDB_E_CLASSNOTREG CoGetClassObject(clsid, CLSCTX_INPROC, NULL, IID_IClassFactory, (void **)&pCF) I get (0x80040154) error.
Both server and client compiled for Win32 target platform (although I tried x64 too). Source code I got from tutorial, so I don't understand what is wrong.
The tutorial you found no doubt is old, written long before 64-bit Windows came around. Registry keys need to be written to HKLM\Software\Wow6432Node\Classes for 32-bit COM servers, to HKLM\Software\Classes for 64-bit COM servers. Your .reg file isn't going to take care of that. You must avoid the HKEY_CLASSES_ROOT alias and replace it with the explicit HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes to avoid accidents.
Use the SysInternals' ProcMon utility if you still have problems, you'll see your test program searching for the registry keys and the DLL.
I have a C# dll that I properly have registered for COM Interop, and made COM visible. Using cppbuilder, I imported the type library, which generated the wrapper classes, and I am now attempting to use to create an instance of my C# class. However, I'm getting a REGDB_E_CLASSNOTREG error in my C++ code. I verified the dll is in the registry, and even re-registered it with regasm. No change. What could I be missing?
Here is my C++ code:
_MyClassPtr obj;
HRESULT hr = obj.CreateInstance(__uuidof(MyClass));
//now hr equals REGDB_E_CLASSNOTREG
I've also tried it as such:
IMyClass* obj;
HRESULT hr = CoCreateInstance(__uuidof(MyClass), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMyClass), (void**) &obj);
//same result, hr equals REGDB_E_CLASSNOTREG
I do have one additional dependency in the C# app. I registered it for COM as well with no difference, but did not import it's type library into the C++ project.
UPDATE: based on the comments below, I discovered that CreateInstance is looking up the class guid in the following places in the registry:
HKCU\Software\Classes\Wow6432Node\CLSID\{guid}
HKCR\Wow6432Node\CLSID\{guid}
HKCU\Software\Classes\CLSID\{guid}
HKCR\CLSID\{guid}
But, going through the registry, the only entry under any of the CLSID nodes that is related to my assembly is the guid for the assembly itself, which is, of course, different than the guid for the class, or the interface.
I've manually run regasm under both x86 and x64 mode to try to acheive different results. No differences.
Well, I found out what would work.
IMyClassPtr obj;
HRESULT hr = obj.CreateInstance(CLSID_MyClass);
CLSID_MyCLass was a guid constant in the generated MyClass_TLB.cpp file. Using it instead of __uuidof(...) for the class types enabled everything to start working correctly.
I'm working on a project involving the Microsoft Unified Communications Client API; uccapi.dll. I'm also using Codegear C++Builder 2010, not Visual Studio. After registering the dll with regsvr32 and importing it as type library into C++Builder 2010, uccapi_tlb- and uccapi_ocx-files were generated. When having imported these into my new project I'm trying to follow the msdn guideline for creating a Office Communicator Client able of signing into the Office Communication server.
In this regard I have two questions:
What is the correct way of accessing the com-interfaces made available through the ocx?
I've so far found several ways of creating access points, such as.
TCOMIUccPlatform plat;
plat = CoUccPlatform::Create();
and
IUccPlatformPtr im;
im = CreateComObject(CLSID_UccPlatform);
and
IUccPlatform* pIUccPlatform;
hr = CoCreateInstance(CLSID_UccPlatform,
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IUccPlatform),
(void**)&pIUccPlatform);
and
IUccPlatformPtr pIPlat;
pIPlat.CreateInstance(__uuidof(IUccPlatform));
The three first seem to work well. The latter will give me an Assertion failed: intf!=0 error with 0×40000015 exception. Using any of the three top ones I can access methods and initialize the platform interface.
However when trying any of the same tactics to access any other interface, such as IUccContext, IUccUriManager or IUccUri, all of which have a clsid defined in the _tlb.h file, I either get a "class not registered" error in the first two cases, or a hresult failure in the third case. Which brings me to my next question.
Using ole-viewer all interfaces are registered as they should. Why wouldn't all co-creatable classes in the dll be registered when registering the dll? And what could be the reasons why don't they act similarly?
Edit1 from UCCAPILib_tlb.h:
//
// COCLASS DEFAULT INTERFACE CREATOR
// CoClass : UccPlatform
// Interface: TCOMIUccPlatform
//
typedef TCoClassCreatorT<TCOMIUccPlatform, IUccPlatform, &CLSID_UccPlatform, &IID_IUccPlatform> CoUccPlatform;
//
// COCLASS DEFAULT INTERFACE CREATOR
// CoClass : UccUriManager
// Interface: TCOMIUccUriManager
//
typedef TCoClassCreatorT<TCOMIUccUriManager, IUccUriManager, &CLSID_UccUriManager, &IID_IUccUriManager> CoUccUriManager;
This issue is already being discussed in detail in the Embarcadero forums.