[PATCH 3/4] winevulkan: Add support for VK_KHR_external_memory_fd.
Derek Lesho
dlesho at codeweavers.com
Wed Oct 30 11:46:49 CDT 2019
Signed-off-by: Derek Lesho <dlesho at codeweavers.com>
---
dlls/winevulkan/make_vulkan | 11 +-
dlls/winevulkan/vulkan.c | 336 ++++++++++++++++++++++++++++++-
dlls/winevulkan/vulkan_private.h | 26 +++
3 files changed, 362 insertions(+), 11 deletions(-)
diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan
index 4f1c03cc7c..e0a988e7aa 100755
--- a/dlls/winevulkan/make_vulkan
+++ b/dlls/winevulkan/make_vulkan
@@ -107,7 +107,6 @@ BLACKLISTED_EXTENSIONS = [
"VK_EXT_pipeline_creation_feedback",
"VK_GOOGLE_display_timing",
"VK_KHR_external_fence_win32",
- "VK_KHR_external_memory_win32",
"VK_KHR_external_semaphore_win32",
# Relates to external_semaphore and needs type conversions in bitflags.
"VK_KHR_shared_presentable_image", # Needs WSI work.
@@ -117,7 +116,6 @@ BLACKLISTED_EXTENSIONS = [
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
"VK_KHR_external_fence_fd",
- "VK_KHR_external_memory_fd",
"VK_KHR_external_semaphore_fd",
# Deprecated extensions
@@ -168,11 +166,13 @@ FUNCTION_OVERRIDES = {
# Device functions
"vkAllocateCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : False},
+ "vkAllocateMemory" : {"dispatch" : True, "driver" : False, "thunk" : False},
"vkCmdExecuteCommands" : {"dispatch" : True, "driver" : False, "thunk" : False},
"vkCreateCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
"vkDestroyCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
"vkDestroyDevice" : {"dispatch" : True, "driver" : False, "thunk" : False},
"vkFreeCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : False},
+ "vkFreeMemory" : {"dispatch" : True, "driver" : False, "thunk" : False},
"vkGetDeviceProcAddr" : {"dispatch" : False, "driver" : True, "thunk" : False},
"vkGetDeviceQueue" : {"dispatch": True, "driver" : False, "thunk" : False},
"vkGetDeviceQueue2" : {"dispatch": True, "driver" : False, "thunk" : False},
@@ -211,6 +211,10 @@ FUNCTION_OVERRIDES = {
# VK_KHR_device_group
"vkGetDeviceGroupSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
"vkGetPhysicalDevicePresentRectanglesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
+
+ # VK_KHR_external_memory_win32
+ "vkGetMemoryWin32HandleKHR" : {"dispatch" : False, "driver" : False, "thunk" : False},
+ "vkGetMemoryWin32HandlePropertiesKHR" : {"dispatch" : False, "driver" : False, "thunk" : False},
}
STRUCT_CHAIN_CONVERSIONS = [
@@ -887,6 +891,9 @@ class VkHandle(object):
if self.name == "VkCommandPool":
return "wine_cmd_pool_from_handle({0})->command_pool".format(name)
+ if self.name == "VkDeviceMemory":
+ return "wine_dev_mem_from_handle({0})->dev_mem".format(name)
+
native_handle_name = None
if self.name == "VkCommandBuffer":
diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index 725bdf019e..01cab11e21 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -17,11 +17,22 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+#include "config.h"
+
#include <stdarg.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include "ntstatus.h"
+#define WIN32_NO_STATUS
#include "windef.h"
+#include "winternl.h"
#include "winbase.h"
#include "winuser.h"
+#include "wine/unicode.h"
+
+#include "dxgi1_2.h"
#include "vulkan_private.h"
@@ -111,6 +122,14 @@ static struct VkPhysicalDevice_T *wine_vk_physical_device_alloc(struct VkInstanc
{
if (wine_vk_device_extension_supported(host_properties[i].extensionName))
{
+ if (!strcmp(host_properties[i].extensionName, "VK_KHR_external_memory_fd"))
+ {
+ TRACE("Substituting VK_KHR_external_memory_fd for VK_KHR_external_memory_win32\n");
+
+ snprintf(host_properties[i].extensionName, sizeof(host_properties[i].extensionName),
+ VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
+ host_properties[i].specVersion = VK_KHR_EXTERNAL_MEMORY_WIN32_SPEC_VERSION;
+ }
TRACE("Enabling extension '%s' for physical device %p\n", host_properties[i].extensionName, object);
num_properties++;
}
@@ -224,9 +243,15 @@ static VkResult wine_vk_device_convert_create_info(const VkDeviceCreateInfo *src
{
VkDeviceGroupDeviceCreateInfo *group_info;
unsigned int i;
+ const char **enabled_extensions = NULL;
VkResult res;
- *dst = *src;
+ dst->sType = src->sType;
+ dst->flags = src->flags;
+ dst->pNext = src->pNext;
+ dst->queueCreateInfoCount = src->queueCreateInfoCount;
+ dst->pQueueCreateInfos = src->pQueueCreateInfos;
+ dst->pEnabledFeatures = src->pEnabledFeatures;
if ((res = convert_VkDeviceCreateInfo_struct_chain(src->pNext, dst)) < 0)
{
@@ -254,18 +279,31 @@ static VkResult wine_vk_device_convert_create_info(const VkDeviceCreateInfo *src
/* Should be filtered out by loader as ICDs don't support layers. */
dst->enabledLayerCount = 0;
dst->ppEnabledLayerNames = NULL;
+ dst->enabledExtensionCount = 0;
+ dst->ppEnabledExtensionNames = NULL;
- TRACE("Enabled %u extensions.\n", dst->enabledExtensionCount);
- for (i = 0; i < dst->enabledExtensionCount; i++)
+ if (src->enabledExtensionCount > 0)
{
- const char *extension_name = dst->ppEnabledExtensionNames[i];
- TRACE("Extension %u: %s.\n", i, debugstr_a(extension_name));
- if (!wine_vk_device_extension_supported(extension_name))
+ enabled_extensions = heap_calloc(src->enabledExtensionCount, sizeof(*src->ppEnabledExtensionNames));
+ if (!enabled_extensions)
{
- WARN("Extension %s is not supported.\n", debugstr_a(extension_name));
- wine_vk_device_free_create_info(dst);
- return VK_ERROR_EXTENSION_NOT_PRESENT;
+ ERR("Failed to allocate memory for enabled extensions\n");
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
}
+
+ for (i = 0; i < src->enabledExtensionCount; i++)
+ {
+ if (!strcmp(src->ppEnabledExtensionNames[i], "VK_KHR_external_memory_win32"))
+ {
+ enabled_extensions[i] = "VK_KHR_external_memory_fd";
+ }
+ else
+ {
+ enabled_extensions[i] = src->ppEnabledExtensionNames[i];
+ }
+ }
+ dst->ppEnabledExtensionNames = enabled_extensions;
+ dst->enabledExtensionCount = src->enabledExtensionCount;
}
return VK_SUCCESS;
@@ -1128,6 +1166,241 @@ void WINAPI wine_vkDestroyCommandPool(VkDevice device, VkCommandPool handle,
heap_free(pool);
}
+extern NTSTATUS CDECL __wine_create_gpu_resource(PHANDLE handle, PHANDLE kmt_handle, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr, int fd );
+extern NTSTATUS CDECL __wine_open_gpu_resource(HANDLE kmt_handle, OBJECT_ATTRIBUTES *attr, DWORD access, PHANDLE handle );
+extern NTSTATUS CDECL __wine_get_gpu_resource_fd(HANDLE handle, int *fd, int *needs_close);
+extern NTSTATUS CDECL __wine_get_gpu_resource_info(HANDLE handle, HANDLE *kmt_handle);
+
+static NTSTATUS server_create_dxgi_resource( PHANDLE handle, PHANDLE kmt_handle, int fd, DWORD access, SECURITY_ATTRIBUTES *sa, LPCWSTR name )
+{
+ OBJECT_ATTRIBUTES attr;
+
+ attr.Length = sizeof(attr);
+ attr.RootDirectory = 0;
+ attr.ObjectName = NULL;
+ attr.Attributes = OBJ_OPENIF | ((sa && sa->bInheritHandle) ? OBJ_INHERIT : 0);
+ attr.SecurityDescriptor = sa ? sa->lpSecurityDescriptor : NULL;
+ attr.SecurityQualityOfService = NULL;
+ if (name)
+ {
+ RtlInitUnicodeString( attr.ObjectName, name );
+ attr.RootDirectory = /*TODO*/0/*TODO*/;
+ }
+
+ return __wine_create_gpu_resource(handle, kmt_handle, access, &attr, fd);
+}
+
+static NTSTATUS server_open_dxgi_resource( PHANDLE handle, LPCWSTR name, DWORD access)
+{
+ OBJECT_ATTRIBUTES attr;
+
+ attr.Length = sizeof(attr);
+ attr.RootDirectory = 0;
+ attr.ObjectName = NULL;
+ attr.Attributes = 0;
+ attr.SecurityDescriptor = 0;
+ attr.SecurityQualityOfService = NULL;
+ if (name)
+ {
+ RtlInitUnicodeString( attr.ObjectName, name );
+ attr.RootDirectory = /*TODO*/0/*TODO*/;
+ }
+
+ return __wine_open_gpu_resource(NULL, &attr, access, handle);
+}
+
+VkResult WINAPI wine_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo *allocate_info, const VkAllocationCallbacks *allocator, VkDeviceMemory *memory_out)
+{
+ struct wine_dev_mem *object;
+ VkMemoryAllocateInfo allocate_info_host = *allocate_info;
+ VkBaseOutStructure *header;
+ VkExternalMemoryHandleTypeFlags handle_types = 0;
+ VkExportMemoryAllocateInfo *export_info = NULL;
+ VkExportMemoryWin32HandleInfoKHR *handle_export_info = NULL;
+ VkImportMemoryFdInfoKHR fd_import_info;
+ int needs_close = TRUE;
+ VkResult res;
+
+ TRACE("%p %p %p %p\n", device, allocate_info, allocator, memory_out);
+
+ if (allocator)
+ FIXME("Support for allocation callbacks not implemented yet\n");
+
+ if (!(object = heap_alloc_zero(sizeof(*object))))
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+
+ object->dev_mem = VK_NULL_HANDLE;
+ object->handle = INVALID_HANDLE_VALUE;
+ object->kmt_handle = INVALID_HANDLE_VALUE;
+ fd_import_info.fd = -1;
+
+ /* find and process handle import/export info and grab it */
+ for (header = (void *)allocate_info->pNext; header; header = header->pNext)
+ {
+ switch (header->sType)
+ {
+ case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
+ {
+ export_info = (VkExportMemoryAllocateInfo *)header;
+
+ handle_types = export_info->handleTypes;
+ if (handle_types & (VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT|VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT))
+ export_info->handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
+ }break;
+ case VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR:
+ {
+ handle_export_info = (VkExportMemoryWin32HandleInfoKHR *)header;
+ }break;
+ case VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR:
+ {
+ VkImportMemoryWin32HandleInfoKHR *win32_import_info = (VkImportMemoryWin32HandleInfoKHR *)header;
+
+ fd_import_info.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
+ fd_import_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
+
+ /* get the fd from the handle */
+ switch (win32_import_info->handleType)
+ {
+ case VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT:
+ if (win32_import_info->handle)
+ DuplicateHandle( GetCurrentProcess(), win32_import_info->handle, GetCurrentProcess(), &object->handle, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ else if (win32_import_info->name)
+ server_open_dxgi_resource( &object->handle, win32_import_info->name, DXGI_SHARED_RESOURCE_READ|DXGI_SHARED_RESOURCE_WRITE );
+ break;
+ case VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT:
+ __wine_open_gpu_resource( win32_import_info->handle, NULL, DXGI_SHARED_RESOURCE_READ|DXGI_SHARED_RESOURCE_WRITE, &object->handle );
+ object->kmt_handle = win32_import_info->handle;
+ break;
+ default:
+ TRACE("Invalid handle type %08x passed in.\n", win32_import_info->handleType);
+ res = VK_ERROR_INVALID_EXTERNAL_HANDLE;
+ goto done;
+ }
+
+ if (object->handle != INVALID_HANDLE_VALUE)
+ __wine_get_gpu_resource_fd(object->handle, &fd_import_info.fd, &needs_close);
+
+ if (fd_import_info.fd != -1)
+ {
+ fd_import_info.pNext = allocate_info_host.pNext;
+ /* we ignore the const because we'll restore it */
+ allocate_info_host.pNext = &fd_import_info;
+
+ /* if the fd needs closing, we can just pass it to vulkan where it can be consumed,
+ otherwise we need to duplicate it so the cached fd isn't consumed by vulkan */
+ if (!needs_close)
+ fd_import_info.fd = dup(fd_import_info.fd);
+ }
+ else
+ {
+ TRACE("Couldn't access resource handle or name. type=%08x handle=%p name=%s\n", win32_import_info->handleType, win32_import_info->handle,
+ win32_import_info->name ? debugstr_w(win32_import_info->name) : "");
+ res = VK_ERROR_INVALID_EXTERNAL_HANDLE;
+ goto done;
+ }
+ }break;
+ default:
+ {
+ TRACE("Unhandled stype = %08x\n", header->sType);
+ }
+ }
+ }
+
+ res = device->funcs.p_vkAllocateMemory(device->device, &allocate_info_host, NULL, &object->dev_mem);
+
+ if (res == VK_SUCCESS)
+ {
+ VkDeviceMemory memory = object->dev_mem;
+
+ if (export_info && export_info->handleTypes)
+ {
+ if (object->handle != INVALID_HANDLE_VALUE)
+ {
+ /* occurs if the caller imports *and* exports the memory */
+ if (handle_types & VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT && object->kmt_handle == INVALID_HANDLE_VALUE)
+ __wine_get_gpu_resource_info(object->handle, &object->kmt_handle);
+ } else {
+ int fd;
+ VkMemoryGetFdInfoKHR host_fd_info;
+
+ /* get an fd to represent it */
+
+ host_fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
+ host_fd_info.pNext = NULL;
+ host_fd_info.memory = memory;
+ host_fd_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
+
+ if (device->funcs.p_vkGetMemoryFdKHR(device->device, &host_fd_info, &fd) == VK_SUCCESS)
+ {
+ LPCWSTR name = handle_export_info ? handle_export_info->name : NULL;
+ SECURITY_ATTRIBUTES sa = handle_export_info ? (handle_export_info->pAttributes ? *handle_export_info->pAttributes : (SECURITY_ATTRIBUTES){0}) : (SECURITY_ATTRIBUTES){0};
+ if (sa.bInheritHandle){
+ sa.bInheritHandle = FALSE;
+ }
+ if (!(server_create_dxgi_resource(&object->handle, &object->kmt_handle, fd, object->access, sa.nLength ? &sa : NULL, name)))
+ {
+ object->handle_types = handle_types &
+ (VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT|VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT);
+ TRACE("Device Memory %p set-up to export handle types: %08x\n", object, object->handle_types);
+ } else {
+ TRACE("Failed to create server-side dxgi-resource.\n");
+ close(fd);
+ res = VK_ERROR_OUT_OF_HOST_MEMORY;
+ goto done;
+ }
+ } else {
+ TRACE("Failed to retrieve FD from native vulkan driver.\n");
+ res = VK_ERROR_OUT_OF_HOST_MEMORY;
+ goto done;
+ }
+ }
+ object->access = handle_export_info ? handle_export_info->dwAccess : DXGI_SHARED_RESOURCE_READ|DXGI_SHARED_RESOURCE_WRITE;
+ object->inherit = handle_export_info ? (handle_export_info->pAttributes ? handle_export_info->pAttributes->bInheritHandle : FALSE) : FALSE;
+ }
+
+ *memory_out = wine_dev_mem_to_handle(object);
+ }
+ else
+ {
+ TRACE("vkAllocateMemory failed with %u\n", res);
+ goto done;
+ }
+
+ done:
+ if (res != VK_SUCCESS)
+ {
+ if (object->dev_mem != VK_NULL_HANDLE)
+ device->funcs.p_vkFreeMemory(device->device, object->dev_mem, NULL);
+ if (fd_import_info.fd != -1 && needs_close)
+ close(fd_import_info.fd);
+ if (object->handle != INVALID_HANDLE_VALUE)
+ CloseHandle(object->handle);
+ heap_free(object);
+ }
+ if (export_info)
+ export_info->handleTypes = handle_types;
+ return res;
+}
+
+void WINAPI wine_vkFreeMemory(VkDevice device, VkDeviceMemory handle,
+ const VkAllocationCallbacks* allocator)
+{
+ struct wine_dev_mem *dev_mem = wine_dev_mem_from_handle(handle);
+
+ TRACE("%p 0x%s, %p\n", device, wine_dbgstr_longlong(handle), allocator);
+
+ if (!handle)
+ return;
+
+ if (allocator)
+ FIXME("Support for allocation callbacks not implemented yet\n");
+
+ device->funcs.p_vkFreeMemory(device->device, dev_mem->dev_mem, NULL);
+ if (dev_mem->handle != INVALID_HANDLE_VALUE)
+ CloseHandle(dev_mem->handle);
+ heap_free(dev_mem);
+}
+
static VkResult wine_vk_enumerate_physical_device_groups(struct VkInstance_T *instance,
VkResult (*p_vkEnumeratePhysicalDeviceGroups)(VkInstance, uint32_t *, VkPhysicalDeviceGroupProperties *),
uint32_t *count, VkPhysicalDeviceGroupProperties *properties)
@@ -1261,6 +1534,51 @@ void WINAPI wine_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(VkPhysicalDev
properties->externalSemaphoreFeatures = 0;
}
+VkResult WINAPI wine_vkGetMemoryWin32HandleKHR(VkDevice device,
+ const VkMemoryGetWin32HandleInfoKHR *handle_info, HANDLE *handle)
+{
+ struct wine_dev_mem *dev_mem;
+
+ TRACE("%p, %p %p\n", device, handle_info, handle);
+
+ dev_mem = wine_dev_mem_from_handle(handle_info->memory);
+
+ switch(handle_info->handleType)
+ {
+ case VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT:
+ if (!(dev_mem->handle_types & VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT))
+ {
+ *handle = INVALID_HANDLE_VALUE;
+ TRACE("VkDeviceMemory wasn't set-up to export native win32 handles.\n");
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ }
+ if (!(DuplicateHandle( GetCurrentProcess(), dev_mem->handle, GetCurrentProcess(), handle, dev_mem->access, dev_mem->inherit, 0 )))
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ break;
+ case VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT:
+ if (!(dev_mem->handle_types & VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT))
+ {
+ *handle = INVALID_HANDLE_VALUE;
+ TRACE("VkDeviceMemory wasn't set-up to export KMT handles.\n");
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ }
+ *handle = dev_mem->kmt_handle;
+ break;
+ default:
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ }
+
+ return VK_SUCCESS;
+}
+
+VkResult WINAPI wine_vkGetMemoryWin32HandlePropertiesKHR(VkDevice device,
+ VkExternalMemoryHandleTypeFlagBits type, HANDLE handle, VkMemoryWin32HandlePropertiesKHR *properties)
+{
+ TRACE("%p %u %p %p\n", device, type, handle, properties);
+
+ return VK_ERROR_INCOMPATIBLE_DRIVER;
+}
+
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved)
{
TRACE("%p, %u, %p\n", hinst, reason, reserved);
diff --git a/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h
index 17072d2341..f0b3b10dd3 100644
--- a/dlls/winevulkan/vulkan_private.h
+++ b/dlls/winevulkan/vulkan_private.h
@@ -120,16 +120,42 @@ struct wine_cmd_pool
struct list command_buffers;
};
+struct wine_dev_mem
+{
+ VkDeviceMemory dev_mem;
+
+ VkExternalMemoryHandleTypeFlags handle_types;
+
+ BOOL inherit;
+ DWORD access;
+
+ /* Internal handle that lasts as long as the object */
+ HANDLE handle;
+
+ /* KMT "handle" */
+ HANDLE kmt_handle;
+};
+
static inline struct wine_cmd_pool *wine_cmd_pool_from_handle(VkCommandPool handle)
{
return (struct wine_cmd_pool *)(uintptr_t)handle;
}
+static inline struct wine_dev_mem *wine_dev_mem_from_handle(VkDeviceMemory handle)
+{
+ return (struct wine_dev_mem *)(uintptr_t)handle;
+}
+
static inline VkCommandPool wine_cmd_pool_to_handle(struct wine_cmd_pool *cmd_pool)
{
return (VkCommandPool)(uintptr_t)cmd_pool;
}
+static inline VkDeviceMemory wine_dev_mem_to_handle(struct wine_dev_mem *dev_mem)
+{
+ return (VkDeviceMemory)(uintptr_t)dev_mem;
+}
+
void *wine_vk_get_device_proc_addr(const char *name) DECLSPEC_HIDDEN;
void *wine_vk_get_instance_proc_addr(const char *name) DECLSPEC_HIDDEN;
--
2.23.0
More information about the wine-devel
mailing list