Implementing object table. Not complete, but better.
This commit is contained in:
parent
3e0db586c0
commit
d0d30ab719
|
@ -26,14 +26,12 @@ namespace {
|
||||||
|
|
||||||
KernelState::KernelState(Runtime* runtime) :
|
KernelState::KernelState(Runtime* runtime) :
|
||||||
runtime_(runtime),
|
runtime_(runtime),
|
||||||
executable_module_(NULL),
|
executable_module_(NULL) {
|
||||||
next_handle_(0) {
|
|
||||||
memory_ = runtime->memory();
|
memory_ = runtime->memory();
|
||||||
processor_ = runtime->processor();
|
processor_ = runtime->processor();
|
||||||
filesystem_ = runtime->filesystem();
|
filesystem_ = runtime->filesystem();
|
||||||
|
|
||||||
objects_mutex_ = xe_mutex_alloc(0);
|
object_table_ = new ObjectTable();
|
||||||
XEASSERTNOTNULL(objects_mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KernelState::~KernelState() {
|
KernelState::~KernelState() {
|
||||||
|
@ -43,27 +41,7 @@ KernelState::~KernelState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all objects.
|
// Delete all objects.
|
||||||
// We first copy the list to another list so that the deletion of the objects
|
delete object_table_;
|
||||||
// doesn't mess up iteration.
|
|
||||||
std::vector<XObject*> all_objects;
|
|
||||||
xe_mutex_lock(objects_mutex_);
|
|
||||||
for (std::tr1::unordered_map<X_HANDLE, XObject*>::iterator it =
|
|
||||||
objects_.begin(); it != objects_.end(); ++it) {
|
|
||||||
all_objects.push_back(it->second);
|
|
||||||
}
|
|
||||||
objects_.clear();
|
|
||||||
modules_.clear();
|
|
||||||
threads_.clear();
|
|
||||||
xe_mutex_unlock(objects_mutex_);
|
|
||||||
for (std::vector<XObject*>::iterator it = all_objects.begin();
|
|
||||||
it != all_objects.end(); ++it) {
|
|
||||||
// Perhaps call a special ForceRelease method or something?
|
|
||||||
XObject* obj = *it;
|
|
||||||
delete obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
xe_mutex_free(objects_mutex_);
|
|
||||||
objects_mutex_ = NULL;
|
|
||||||
|
|
||||||
filesystem_.reset();
|
filesystem_.reset();
|
||||||
processor_.reset();
|
processor_.reset();
|
||||||
|
@ -86,75 +64,31 @@ fs::FileSystem* KernelState::filesystem() {
|
||||||
return filesystem_.get();
|
return filesystem_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(benvanik): invesitgate better handle storage/structure.
|
ObjectTable* KernelState::object_table() const {
|
||||||
// A much better way of doing handles, if performance becomes an issue, would
|
return object_table_;
|
||||||
// be to try to make the pointers 32bit. Then we could round-trip them through
|
|
||||||
// PPC code without needing to keep a map.
|
|
||||||
// To achieve this we could try doing allocs in the 32-bit address space via
|
|
||||||
// the OS alloc calls, or maybe create a section with a reserved size at load
|
|
||||||
// time (65k handles * 4 is more than enough?).
|
|
||||||
// We could then use a free list of handle IDs and allocate/release lock-free.
|
|
||||||
|
|
||||||
XObject* KernelState::GetObject(X_HANDLE handle) {
|
|
||||||
xe_mutex_lock(objects_mutex_);
|
|
||||||
std::tr1::unordered_map<X_HANDLE, XObject*>::iterator it =
|
|
||||||
objects_.find(handle);
|
|
||||||
XObject* value = it != objects_.end() ? it->second : NULL;
|
|
||||||
if (value) {
|
|
||||||
value->Retain();
|
|
||||||
}
|
|
||||||
xe_mutex_unlock(objects_mutex_);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_HANDLE KernelState::InsertObject(XObject* obj) {
|
|
||||||
xe_mutex_lock(objects_mutex_);
|
|
||||||
X_HANDLE handle = 0x00001000 + (++next_handle_);
|
|
||||||
objects_.insert(std::pair<X_HANDLE, XObject*>(handle, obj));
|
|
||||||
switch (obj->type()) {
|
|
||||||
case XObject::kTypeModule:
|
|
||||||
modules_.insert(std::pair<X_HANDLE, XModule*>(
|
|
||||||
handle, static_cast<XModule*>(obj)));
|
|
||||||
break;
|
|
||||||
case XObject::kTypeThread:
|
|
||||||
threads_.insert(std::pair<X_HANDLE, XThread*>(
|
|
||||||
handle, static_cast<XThread*>(obj)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
xe_mutex_unlock(objects_mutex_);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KernelState::RemoveObject(XObject* obj) {
|
|
||||||
xe_mutex_lock(objects_mutex_);
|
|
||||||
objects_.erase(obj->handle());
|
|
||||||
xe_mutex_unlock(objects_mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XModule* KernelState::GetModule(const char* name) {
|
XModule* KernelState::GetModule(const char* name) {
|
||||||
XModule* found = NULL;
|
// TODO(benvanik): implement lookup. Most games seem to look for xam.xex/etc.
|
||||||
xe_mutex_lock(objects_mutex_);
|
XEASSERTALWAYS();
|
||||||
for (std::tr1::unordered_map<X_HANDLE, XModule*>::iterator it =
|
|
||||||
modules_.begin(); it != modules_.end(); ++it) {
|
|
||||||
if (xestrcmpa(name, it->second->name()) == 0) {
|
|
||||||
found = it->second;
|
|
||||||
found->Retain();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xe_mutex_unlock(objects_mutex_);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
XModule* KernelState::GetExecutableModule() {
|
|
||||||
if (executable_module_) {
|
|
||||||
executable_module_->Retain();
|
|
||||||
return executable_module_;
|
|
||||||
}
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XModule* KernelState::GetExecutableModule() {
|
||||||
|
if (!executable_module_) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
executable_module_->Retain();
|
||||||
|
return executable_module_;
|
||||||
|
}
|
||||||
|
|
||||||
void KernelState::SetExecutableModule(XModule* module) {
|
void KernelState::SetExecutableModule(XModule* module) {
|
||||||
if (executable_module_ && executable_module_ != module) {
|
if (module == executable_module_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executable_module_) {
|
||||||
executable_module_->Release();
|
executable_module_->Release();
|
||||||
}
|
}
|
||||||
executable_module_ = module;
|
executable_module_ = module;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <xenia/kernel/kernel_module.h>
|
#include <xenia/kernel/kernel_module.h>
|
||||||
#include <xenia/kernel/xbox.h>
|
#include <xenia/kernel/xbox.h>
|
||||||
#include <xenia/kernel/fs/filesystem.h>
|
#include <xenia/kernel/fs/filesystem.h>
|
||||||
|
#include <xenia/kernel/modules/xboxkrnl/object_table.h>
|
||||||
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -24,9 +25,7 @@ namespace kernel {
|
||||||
namespace xboxkrnl {
|
namespace xboxkrnl {
|
||||||
|
|
||||||
|
|
||||||
class XObject;
|
|
||||||
class XModule;
|
class XModule;
|
||||||
class XThread;
|
|
||||||
|
|
||||||
|
|
||||||
class KernelState {
|
class KernelState {
|
||||||
|
@ -39,7 +38,7 @@ public:
|
||||||
cpu::Processor* processor();
|
cpu::Processor* processor();
|
||||||
fs::FileSystem* filesystem();
|
fs::FileSystem* filesystem();
|
||||||
|
|
||||||
XObject* GetObject(X_HANDLE handle);
|
ObjectTable* object_table() const;
|
||||||
|
|
||||||
XModule* GetModule(const char* name);
|
XModule* GetModule(const char* name);
|
||||||
XModule* GetExecutableModule();
|
XModule* GetExecutableModule();
|
||||||
|
@ -54,13 +53,9 @@ private:
|
||||||
shared_ptr<cpu::Processor> processor_;
|
shared_ptr<cpu::Processor> processor_;
|
||||||
shared_ptr<fs::FileSystem> filesystem_;
|
shared_ptr<fs::FileSystem> filesystem_;
|
||||||
|
|
||||||
XModule* executable_module_;
|
ObjectTable* object_table_;
|
||||||
|
|
||||||
xe_mutex_t* objects_mutex_;
|
XModule* executable_module_;
|
||||||
X_HANDLE next_handle_;
|
|
||||||
std::tr1::unordered_map<X_HANDLE, XObject*> objects_;
|
|
||||||
std::tr1::unordered_map<X_HANDLE, XModule*> modules_;
|
|
||||||
std::tr1::unordered_map<X_HANDLE, XThread*> threads_;
|
|
||||||
|
|
||||||
friend class XObject;
|
friend class XObject;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <xenia/kernel/modules/xboxkrnl/object_table.h>
|
||||||
|
|
||||||
|
#include <xenia/kernel/modules/xboxkrnl/xobject.h>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace xe;
|
||||||
|
using namespace xe::kernel;
|
||||||
|
using namespace xe::kernel::xboxkrnl;
|
||||||
|
|
||||||
|
|
||||||
|
ObjectTable::ObjectTable() :
|
||||||
|
table_capacity_(0),
|
||||||
|
table_(NULL),
|
||||||
|
last_free_entry_(0) {
|
||||||
|
table_mutex_ = xe_mutex_alloc(0);
|
||||||
|
XEASSERTNOTNULL(table_mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectTable::~ObjectTable() {
|
||||||
|
xe_mutex_lock(table_mutex_);
|
||||||
|
|
||||||
|
// Release all objects.
|
||||||
|
for (uint32_t n = 0; n < table_capacity_; n++) {
|
||||||
|
ObjectTableEntry& entry = table_[n];
|
||||||
|
if (entry.object) {
|
||||||
|
entry.object->ReleaseHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table_capacity_ = 0;
|
||||||
|
last_free_entry_ = 0;
|
||||||
|
xe_free(table_);
|
||||||
|
table_ = NULL;
|
||||||
|
|
||||||
|
xe_mutex_unlock(table_mutex_);
|
||||||
|
|
||||||
|
xe_mutex_free(table_mutex_);
|
||||||
|
table_mutex_ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS ObjectTable::FindFreeSlot(uint32_t* out_slot) {
|
||||||
|
// Find a free slot.
|
||||||
|
uint32_t slot = last_free_entry_;
|
||||||
|
uint32_t scan_count = 0;
|
||||||
|
while (scan_count < table_capacity_) {
|
||||||
|
ObjectTableEntry& entry = table_[slot];
|
||||||
|
if (!entry.object) {
|
||||||
|
*out_slot = slot;
|
||||||
|
return X_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
scan_count++;
|
||||||
|
slot = (slot + 1) % table_capacity_;
|
||||||
|
if (slot == 0) {
|
||||||
|
// Never allow 0 handles.
|
||||||
|
scan_count++;
|
||||||
|
slot++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table out of slots, expand.
|
||||||
|
uint32_t new_table_capacity = MAX(16 * 1024, table_capacity_ * 2);
|
||||||
|
ObjectTableEntry* new_table = (ObjectTableEntry*)xe_recalloc(
|
||||||
|
table_,
|
||||||
|
table_capacity_ * sizeof(ObjectTableEntry),
|
||||||
|
new_table_capacity * sizeof(ObjectTableEntry));
|
||||||
|
if (!new_table) {
|
||||||
|
return X_STATUS_NO_MEMORY;
|
||||||
|
}
|
||||||
|
last_free_entry_ = table_capacity_;
|
||||||
|
table_capacity_ = new_table_capacity;
|
||||||
|
table_ = new_table;
|
||||||
|
|
||||||
|
// Never allow 0 handles.
|
||||||
|
slot = ++last_free_entry_;
|
||||||
|
*out_slot = slot;
|
||||||
|
|
||||||
|
return X_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS ObjectTable::AddHandle(XObject* object, X_HANDLE* out_handle) {
|
||||||
|
XEASSERTNOTNULL(out_handle);
|
||||||
|
|
||||||
|
X_STATUS result = X_STATUS_SUCCESS;
|
||||||
|
|
||||||
|
xe_mutex_lock(table_mutex_);
|
||||||
|
|
||||||
|
// Find a free slot.
|
||||||
|
uint32_t slot = 0;
|
||||||
|
result = FindFreeSlot(&slot);
|
||||||
|
|
||||||
|
// Stash.
|
||||||
|
if (XSUCCEEDED(result)) {
|
||||||
|
ObjectTableEntry& entry = table_[slot];
|
||||||
|
entry.object = object;
|
||||||
|
|
||||||
|
// Retain so long as the object is in the table.
|
||||||
|
object->RetainHandle();
|
||||||
|
object->Retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
xe_mutex_unlock(table_mutex_);
|
||||||
|
|
||||||
|
if (XSUCCEEDED(result)) {
|
||||||
|
*out_handle = slot << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
|
||||||
|
X_STATUS result = X_STATUS_SUCCESS;
|
||||||
|
|
||||||
|
xe_mutex_lock(table_mutex_);
|
||||||
|
|
||||||
|
// Lower 2 bits are ignored.
|
||||||
|
uint32_t slot = handle >> 2;
|
||||||
|
|
||||||
|
// Verify slot.
|
||||||
|
XObject* object = NULL;
|
||||||
|
if (slot > table_capacity_) {
|
||||||
|
result = X_STATUS_INVALID_HANDLE;
|
||||||
|
} else {
|
||||||
|
ObjectTableEntry& entry = table_[slot];
|
||||||
|
if (entry.object) {
|
||||||
|
object = entry.object;
|
||||||
|
|
||||||
|
// Release the object handle now that it is out of the table.
|
||||||
|
object->ReleaseHandle();
|
||||||
|
object->Release();
|
||||||
|
} else {
|
||||||
|
result = X_STATUS_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xe_mutex_unlock(table_mutex_);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS ObjectTable::GetObject(X_HANDLE handle, XObject** out_object) {
|
||||||
|
XEASSERTNOTNULL(out_object);
|
||||||
|
|
||||||
|
X_STATUS result = X_STATUS_SUCCESS;
|
||||||
|
|
||||||
|
xe_mutex_lock(table_mutex_);
|
||||||
|
|
||||||
|
// Lower 2 bits are ignored.
|
||||||
|
uint32_t slot = handle >> 2;
|
||||||
|
|
||||||
|
// Verify slot.
|
||||||
|
XObject* object = NULL;
|
||||||
|
if (slot > table_capacity_) {
|
||||||
|
result = X_STATUS_INVALID_HANDLE;
|
||||||
|
} else {
|
||||||
|
ObjectTableEntry& entry = table_[slot];
|
||||||
|
if (entry.object) {
|
||||||
|
object = entry.object;
|
||||||
|
} else {
|
||||||
|
result = X_STATUS_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retain the object pointer.
|
||||||
|
object->Retain();
|
||||||
|
|
||||||
|
xe_mutex_unlock(table_mutex_);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_KERNEL_MODULES_XBOXKRNL_OBJECT_TABLE_H_
|
||||||
|
#define XENIA_KERNEL_MODULES_XBOXKRNL_OBJECT_TABLE_H_
|
||||||
|
|
||||||
|
#include <xenia/common.h>
|
||||||
|
#include <xenia/core.h>
|
||||||
|
|
||||||
|
#include <xenia/kernel/xbox.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xboxkrnl {
|
||||||
|
|
||||||
|
|
||||||
|
class XObject;
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectTable {
|
||||||
|
public:
|
||||||
|
ObjectTable();
|
||||||
|
~ObjectTable();
|
||||||
|
|
||||||
|
X_STATUS AddHandle(XObject* object, X_HANDLE* out_handle);
|
||||||
|
X_STATUS RemoveHandle(X_HANDLE handle);
|
||||||
|
X_STATUS GetObject(X_HANDLE handle, XObject** out_object);
|
||||||
|
|
||||||
|
private:
|
||||||
|
X_STATUS FindFreeSlot(uint32_t* out_slot);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
XObject* object;
|
||||||
|
} ObjectTableEntry;
|
||||||
|
|
||||||
|
xe_mutex_t* table_mutex_;
|
||||||
|
uint32_t table_capacity_;
|
||||||
|
ObjectTableEntry* table_;
|
||||||
|
uint32_t last_free_entry_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace xboxkrnl
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_MODULES_XBOXKRNL_OBJECT_TABLE_H_
|
|
@ -5,6 +5,8 @@
|
||||||
'kernel_state.h',
|
'kernel_state.h',
|
||||||
'module.cc',
|
'module.cc',
|
||||||
'module.h',
|
'module.h',
|
||||||
|
'object_table.cc',
|
||||||
|
'object_table.h',
|
||||||
'xboxkrnl_debug.cc',
|
'xboxkrnl_debug.cc',
|
||||||
'xboxkrnl_debug.h',
|
'xboxkrnl_debug.h',
|
||||||
'xboxkrnl_hal.cc',
|
'xboxkrnl_hal.cc',
|
||||||
|
|
|
@ -17,18 +17,15 @@ using namespace xe::kernel::xboxkrnl;
|
||||||
|
|
||||||
XObject::XObject(KernelState* kernel_state, Type type) :
|
XObject::XObject(KernelState* kernel_state, Type type) :
|
||||||
kernel_state_(kernel_state),
|
kernel_state_(kernel_state),
|
||||||
ref_count_(1),
|
handle_ref_count_(0),
|
||||||
|
pointer_ref_count_(0),
|
||||||
type_(type), handle_(X_INVALID_HANDLE_VALUE) {
|
type_(type), handle_(X_INVALID_HANDLE_VALUE) {
|
||||||
handle_ = kernel_state->InsertObject(this);
|
kernel_state->object_table()->AddHandle(this, &handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
XObject::~XObject() {
|
XObject::~XObject() {
|
||||||
XEASSERTZERO(ref_count_);
|
XEASSERTZERO(handle_ref_count_);
|
||||||
|
XEASSERTZERO(pointer_ref_count_);
|
||||||
if (handle_ != X_INVALID_HANDLE_VALUE) {
|
|
||||||
// Remove from state table.
|
|
||||||
kernel_state_->RemoveObject(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runtime* XObject::runtime() {
|
Runtime* XObject::runtime() {
|
||||||
|
@ -47,16 +44,28 @@ XObject::Type XObject::type() {
|
||||||
return type_;
|
return type_;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_HANDLE XObject::handle() {
|
X_HANDLE XObject::handle() const {
|
||||||
return handle_;
|
return handle_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XObject::RetainHandle() {
|
||||||
|
xe_atomic_inc_32(&handle_ref_count_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XObject::ReleaseHandle() {
|
||||||
|
if (!xe_atomic_dec_32(&handle_ref_count_)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void XObject::Retain() {
|
void XObject::Retain() {
|
||||||
xe_atomic_inc_32(&ref_count_);
|
xe_atomic_inc_32(&pointer_ref_count_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XObject::Release() {
|
void XObject::Release() {
|
||||||
if (!xe_atomic_dec_32(&ref_count_)) {
|
if (!xe_atomic_dec_32(&pointer_ref_count_)) {
|
||||||
|
XEASSERT(pointer_ref_count_ >= handle_ref_count_);
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,10 @@ public:
|
||||||
KernelState* kernel_state();
|
KernelState* kernel_state();
|
||||||
|
|
||||||
Type type();
|
Type type();
|
||||||
X_HANDLE handle();
|
X_HANDLE handle() const;
|
||||||
|
|
||||||
|
void RetainHandle();
|
||||||
|
bool ReleaseHandle();
|
||||||
void Retain();
|
void Retain();
|
||||||
void Release();
|
void Release();
|
||||||
|
|
||||||
|
@ -52,7 +54,8 @@ protected:
|
||||||
private:
|
private:
|
||||||
KernelState* kernel_state_;
|
KernelState* kernel_state_;
|
||||||
|
|
||||||
volatile int32_t ref_count_;
|
volatile int32_t handle_ref_count_;
|
||||||
|
volatile int32_t pointer_ref_count_;
|
||||||
|
|
||||||
Type type_;
|
Type type_;
|
||||||
X_HANDLE handle_;
|
X_HANDLE handle_;
|
||||||
|
|
Loading…
Reference in New Issue