I have two files, one is main file (main.cpp) other one is for multi-threading (threads.cpp).
I use SDL_PushEvent() in threads.cpp and SDL_PollEvent() in main.cpp.
Below is a logic of my sample code.
main.cpp
bool Init() {
if (SDL_Init(SDL_INIT_VIDEO) < 0)
return false;
SDL_DisplayMode mode;
SDL_GetDisplayMode(0, 0, &mode);
this->win_width = mode.w;
this->win_height = mode.h;
this->win = SDL_CreateWindow(NULL, 0, 0, win_width, win_height, SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);
if (this->win == NULL) {
LOGE("[Init] SDL Window Created failed : %s", SDL_GetError());
return false;
}
this->renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
if (this->renderer == NULL) {
LOGE("[Init] SDL Renderer Created failed : %s", SDL_GetError());
return false;
}
this->bmp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, win_width, win_height);
if (this->bmp == NULL) {
LOGE("[Init] SDL Texture Created failed : %s", SDL_GetError());
return false;
}
return true;
}
void DisplayEvent (SDL_Event e) {
FrameObject obj = *(FrameObject*) e.user.data1;
SDL_Rect rect;
rect.x = rect.y = 0;
rect.w = obj.frameWidth;
rect.h = obj.frameHeight;
int r = SDL_UpdateTexture(this->bmp, NULL, obj.FrameData.RGB, rect.w*2);
LOGI("[DisplayEvent] - UpdateTexture");
// Reneder this Frame
SDL_RenderClear(this->renderer);
LOGI("[DisplayEvent] - RenderClear");
SDL_RenderCopy(this->renderer, this->bmp, NULL, &rect);
LOGI("[DisplayEvent] - RenderCopy");
SDL_RenderPresent(this->renderer);
LOGI("[DisplayEvent] - RenderPresent");
}
int main (int argc, char **argv) {
Init();
while (!quit) {
SDL_Event e;
// Event Polling
while (SDL_PollEvent(&e)) {
switch (e.type) {
case MY_EVENT:
LOGI("[main] - Get MY_EVENT");
DisplayEvent(e);
LOGI("[main] - %s more MY_EVENT", SDL_HasEvent(MY_EVENT) ? "Has" : "Hasn't");
break;
default:
break;
}
}
}
SDL_Quit();
}
threads.cpp
void* push_event(void *arg) {
FrameObject obj = (FrameObject*) arg;
while (!quit) {
SDL_Event event;
SDL_zero(event);
event.type = MY_EVENT;
event.user.data1 = obj;
event.user.data2 = 0;
if (SDL_PushEvent(&event) == 1) LOGI("[push_event] - Push MY_EVENT");
else LOGE("[push_event] - Event Push Error : %s", SDL_GetError());
sleep(1);
}
}
EDIT:
I add more sample code. I found the problem is not missing the SDL Event. The problem is SDL thread (main Thread) is blocked at SDL_RenderClear().
The log message output "[DisplayEvent] - UpdateTexture" but not print "[DisplayEvent] - RenderClear". It's weird. For create single thread to run push_event is find, but when I create two threads to run push_event, the SDL Thread is blocked.
Dost it problem is hardware i.e. GPU?
SDL Wiki on SDL_PushEvent says that it is thread-safe so I'm assuming it is Ok to call it from other threads. However, it says there that you need first to call SDL_RegisterEvents to get an event ID suitable for using as application specific events, and then use it to push your events.
This is not clear from your code, are you calling SDL_RegisterEvents? This might be the problem. Also, have you checked if it works if you push the event on the same thread you initialized the video? This test will make sure the problem isn't related to threads.
Related
I am trying to write an android app which uses the Java SDK for the GUI, and the NDK to draw things and read the Linear Acceleration sensor (based of the accelerometer but does not include gravity). Basically, I want to know when the device is being shaken and my app is active.
I am using ASensorEventQueue_hasEvents and ALooper_pollAll in a fairly basic manner, I think, but they return -1 and -4 respectively. These are errors, but I have no clue what they mean or what I could be doing wrong. Any help would be welcome, thanks! Here are some code snips:
To start listening for data from the linear acceleration sensor, I did:
//sensorManager = ASensorManager_getInstance();
sensorManager = ASensorManager_getInstanceForPackage(mypackage);
sensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_LINEAR_ACCELERATION);
if (sensor == nullptr) {
return env->NewStringUTF("Linear accelerometer is not present");
}
looper = ALooper_prepare(0);
if (looper == nullptr) {
return env->NewStringUTF("Could not get looper.");
}
eventQueue = ASensorManager_createEventQueue(sensorManager, looper, EVENT_TYPE_LINEAR_ACCELEROMETER, nullptr, nullptr);
if (eventQueue == nullptr) {
return env->NewStringUTF("Could not create event queue for sensor");
}
//ASensorEventQueue_disableSensor(eventQueue, sensor);
int minDelay = ASensor_getMinDelay(sensor);
minDelay = std::max(minDelay, MAX_EVENT_REPORT_TIME);
int rc = ASensorEventQueue_registerSensor(eventQueue, sensor, minDelay, minDelay);
rc = ASensorEventQueue_setEventRate(eventQueue, sensor, minDelay);
return env->NewStringUTF("");
And my drawing loop looks like:
int rc = ASensorEventQueue_enableSensor(eventQueue, sensor);
std::vector<std::string> results;
while (!stopDrawing) {
int event = 0;
void *data = nullptr;
int rc;
rc = ASensorEventQueue_hasEvents(eventQueue); // testing
rc = ALooper_pollAll(wait_timeout, nullptr, &event, &data);
if (rc == ALOOPER_POLL_TIMEOUT) {
waiting += wait_timeout;
if (waiting > EVENT_LOOP_TIMEOUT) {
// If no data has been received, we have been waiting for a while, so assume
// the shaking has stopped
ASensorEventQueue_disableSensor(eventQueue, sensor);
}
} else if (rc == ALOOPER_POLL_ERROR) {
return env->NewStringUTF("A looper error occurred");
} else if (rc >= 0 && event == EVENT_TYPE_LINEAR_ACCELEROMETER) {
std::vector<ASensorEvent> events;
events.resize(100);
while (ASensorEventQueue_hasEvents(eventQueue) == 1) {
ssize_t nbrEvents = ASensorEventQueue_getEvents(eventQueue, events.data(), events.size());
if (nbrEvents < 0) {
// an error has occurred
return env->NewStringUTF("");
}
float x = 0;
float y = 0;
float z = 0;
for (int i = 0; i < nbrEvents; i++) {
x += events[i].acceleration.x;
y += events[i].acceleration.y;
z += events[i].acceleration.z;
}
graphics.updateAcceleration(x, y, z);
}
}
graphics.updateUniformBuffer();
graphics.drawFrame();
}
Note: the call to ASensorEventQueue_hasEvents right before ALooper_pollAll was just so that I could see what it was returning since the ALooper_pollAll call always returned an error. When running it in the debugger, I see that hasEvents is returning -1 and pollAll is returning -4. This is happening in SDK versions 24 and 26 (I haven't tried 27 and vulkan is not supported below 24). Any ideas why?
I had the precise symptoms described here, and for me it turned out that I was preparing the looper on a different thread than I was polling on.
I'm experiencing an infinite loop trying to trace simple hello world using ptrace() on Android ARM64 emulator that emulates AARCH64. I'm not sure why it is not stopping. I'm trying to trace a hello world program and get all executed instructions but it seems that this condition never returns false: while (WIFSTOPPED(wait_status))
int main(int argc, char** argv)
{
pid_t child_pid;
if(argc < 2)
{
fprintf(stderr, "Expected a program name as argument\n");
return -1;
}
child_pid = fork();
if (child_pid == 0)
run_target(argv[1]);
else if (child_pid > 0)
run_debugger(child_pid);
else
{
perror("fork");
return -1;
}
return 0;
}
void run_target(const char* programname)
{
printf("target started. will run '%s'\n", programname);
/* Allow tracing of this process */
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
{
perror("ptrace");
return;
}
/* Replace this process's image with the given program */
execl(programname, programname, 0);
}
void run_debugger(pid_t child_pid)
{
int wait_status;
unsigned icounter = 0;
printf("debugger started\n");
/* Wait for child to stop on its first instruction */
wait(&wait_status);
while (WIFSTOPPED(wait_status))
{
icounter++;
struct user_pt_regs regs;
struct iovec io;
io.iov_base = ®s;
io.iov_len = sizeof(regs);
if (ptrace(PTRACE_GETREGSET, child_pid, (void*)NT_PRSTATUS, (void*)&io) == -1)
printf("BAD REGISTER REQUEST\n");
unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.pc, 0);
printf("icounter = %u. PCP = 0x%08x. instr = 0x%08x\n", icounter, regs.pc, instr);
/* Make the child execute another instruction */
if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0)
{
perror("ptrace");
return;
}
/* Wait for child to stop on its next instruction */
wait(&wait_status);
}
printf("the child executed %u instructions\n", icounter);
}
We are not able to get the exact cause of this exception. Does anybody have an idea, why this exception occurs in android application? Thanks in advance.
Here is the full stack trace of Exception :
Fatal Exception: java.lang.InternalError: Thread starting during runtime shutdown
at java.lang.Thread.nativeCreate(Thread.java)
at java.lang.Thread.start(Thread.java:1063)
at org.apache.http.impl.conn.tsccm.AbstractConnPool.enableConnectionGC(AbstractConnPool.java:145)
at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.createConnectionPool(ThreadSafeClientConnManager.java:125)
at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.<init>(ThreadSafeClientConnManager.java:103)
at org.acra.util.HttpRequest.getHttpClient(HttpRequest.java:214)
at org.acra.util.HttpRequest.send(HttpRequest.java:141)
at org.acra.sender.HttpSender.send(HttpSender.java:225)
at org.acra.SendWorker.sendCrashReport(SendWorker.java:179)
at org.acra.SendWorker.checkAndSendReports(SendWorker.java:141)
at org.acra.SendWorker.run(SendWorker.java:77)
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
CHECK(java_peer != nullptr);
Thread* self = static_cast<JNIEnvExt*>(env)->self;
if (VLOG_IS_ON(threads)) {
ScopedObjectAccess soa(env);
ArtField* f = soa.DecodeField(WellKnownClasses::java_lang_Thread_name);
mirror::String* java_name = reinterpret_cast<mirror::String*>(f->GetObject(
soa.Decode<mirror::Object*>(java_peer)));
std::string thread_name;
if (java_name != nullptr) {
thread_name = java_name->ToModifiedUtf8();
} else {
thread_name = "(Unnamed)";
}
VLOG(threads) << "Creating native thread for " << thread_name;
self->Dump(LOG(INFO));
}
Runtime* runtime = Runtime::Current();
// Atomically start the birth of the thread ensuring the runtime isn't shutting down.
bool thread_start_during_shutdown = false;
{
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
if (runtime->IsShuttingDownLocked()) {
thread_start_during_shutdown = true;
} else {
runtime->StartThreadBirth();
}
}
if (thread_start_during_shutdown) {//At there!!!
ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
return;
}
Thread* child_thread = new Thread(is_daemon);
// Use global JNI ref to hold peer live while child thread starts.
child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
stack_size = FixStackSize(stack_size);
// Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing to
// assign it.
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
reinterpret_cast<jlong>(child_thread));
// Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and
// do not have a good way to report this on the child's side.
std::unique_ptr<JNIEnvExt> child_jni_env_ext(
JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));
int pthread_create_result = 0;
if (child_jni_env_ext.get() != nullptr) {
pthread_t new_pthread;
pthread_attr_t attr;
child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
"PTHREAD_CREATE_DETACHED");
CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
pthread_create_result = pthread_create(&new_pthread,
&attr,
Thread::CreateCallback,
child_thread);
CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
if (pthread_create_result == 0) {
// pthread_create started the new thread. The child is now responsible for managing the
// JNIEnvExt we created.
// Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
// between the threads.
child_jni_env_ext.release();
return;
}
}
// Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
{
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
runtime->EndThreadBirth();
}
// Manually delete the global reference since Thread::Init will not have been run.
env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
child_thread->tlsPtr_.jpeer = nullptr;
delete child_thread;
child_thread = nullptr;
// TODO: remove from thread group?
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
{
std::string msg(child_jni_env_ext.get() == nullptr ?
"Could not allocate JNI Env" :
StringPrintf("pthread_create (%s stack) failed: %s",
PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
ScopedObjectAccess soa(env);
soa.Self()->ThrowOutOfMemoryError(msg.c_str());
}
}
i've successfully cross compiled the sdl library for the android platform now i want to display my sdl forms like SDL_Surface and the SDL_Rect in the android screen .
How is that possible?
here is my first try
SDLRenderer::SDLRenderer () :
bmp (NULL),
screen (NULL),
imgConvertCtx (NULL),
isInit (false),
quitKeyPressed (false)
{
}
SDLRenderer::~SDLRenderer ()
{
}
bool SDLRenderer::init (int width, int height)
{ LOGI("sdlrenderer init");
this->screen = SDL_SetVideoMode(width, height, 0, 0);
if(!screen){
LOGI("!screen");
return false;
}
this->bmp = SDL_CreateYUVOverlay(width, height, SDL_YV12_OVERLAY, this->screen);
LOGI("SDL_CreateYUVOverlay passed");
return true;
}
bool SDLRenderer::processEvents ()
{
SDL_Event sdlEvent;
while(SDL_PollEvent(&sdlEvent))
{
switch(sdlEvent.type)
{
case SDL_KEYDOWN:
if(sdlEvent.key.keysym.sym == SDLK_ESCAPE)
this->quitKeyPressed = true;
break;
case SDL_QUIT: this->quitKeyPressed = true; break;
}
}
return true;
}
bool SDLRenderer::isQuitKeyPressed ()
{
return this->quitKeyPressed;
}
void SDLRenderer::onVideoDataAvailable (const uint8_t **data, videoFrameProperties* props)
{LOGI("sdlrenderer data availabe");
if(!this->isInit){
this->isInit = this->init(props->width, props->height);
LOGI("sdlrenderer data availabe calling render init");
}
LOGI("before SDL_LockYUVOverlay(bmp);");
SDL_LockYUVOverlay(bmp);
LOGI("after SDL_LockYUVOverlay(bmp);");
AVPicture pict;
LOGI("after AVPicture pict;");
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
LOGI("after creating avpicture");
// Convert the image into YUV format that SDL uses
if(imgConvertCtx == NULL)
{
int w = props->width;
int h = props->height;
imgConvertCtx = sws_getContext(props->width, props->height, (PixelFormat)props- >pxlFmt, w, h, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
if(imgConvertCtx == NULL)
{ LOGI("imgConvertCtx == NULL");
fprintf(stderr, "Cannot initialize the conversion context!\n");
exit(1);
}
}
sws_scale(imgConvertCtx, data, props->linesize, 0, props->height, pict.data, pict.linesize);
LOGI("calling SDL_UnlockYUVOverlay(bmp);");
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = props->width;
rect.h = props->height;
LOGI("sdlrenderer displaying");
SDL_DisplayYUVOverlay(bmp, &rect);
}
there is my main
int main(int argc, char *argv[])
{
SDLRenderer *renderer = new SDLRenderer();
DASHReceiver *receiver = new DASHReceiver(30);
receiver->Init("http://www----custom url here");
LibavDecoder *decoder = new LibavDecoder(receiver);
decoder->attachVideoObserver(renderer);
decoder->setFrameRate(24);
decoder->init();
bool eos = false;
while(!renderer->isQuitKeyPressed() && !eos)
{
eos = !decoder->decode();
renderer->processEvents();
}
decoder->stop();
return 0;
}
Thanks in advance!
You are missing an SDL_Flip or an SDL_UpdateRect to be called on your main SDL_surface, which will update it on the screen.
As far as I can see you are trying to port the bitmovin opensource dash player.
I have already done that and once SDL was ported to android, all other parts of the sofware were working.
I've got exactly the same code as you and this part is working well
be sure to define the surface in the java part
Try google with SDLActivity and use the java code provided from here
Then take a carefull look here http://lists.libsdl.org/pipermail/sdl-libsdl.org/2011-July/081481.html to make some little modification to the java code
// The Unimplemented OpenGL ES API notices *always* indicate you have
// the incorrect context version, which has to be fixed in SDLActivity.java .
int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
int contextAttrs[] = new int[]{
EGL_CONTEXT_CLIENT_VERSION, majorVersion,
EGL10.EGL_NONE
};
EGLContext ctx = egl.eglCreateContext(dpy, config,EGL10.EGL_NO_CONTEXT, contextAttrs);
if (ctx == EGL10.EGL_NO_CONTEXT) {
Log.e("SDL", "Couldn't create context");
return false;
}
/*
EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, null);
if (ctx == EGL10.EGL_NO_CONTEXT) {
Log.e("SDL", "Couldn't create context");
return false;
}
*/
When I use CreateProcess to create process adb.exe, It will Block in ReadFile.
void KillAdbProcess()
{
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
return;
cProcesses = cbNeeded / sizeof(DWORD);
for ( i = 0; i < cProcesses; i++ )
if( aProcesses[i] != 0 ){
bool shouldKill =false;
wchar_t szProcessName[MAX_PATH] = L"<unknown>";
//Get a handle to the process.
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ | PROCESS_TERMINATE,
FALSE, aProcesses[i] );
if (NULL != hProcess )
{
HMODULE hMod;
DWORD cbNeeded;
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod),
&cbNeeded) )
{
GetModuleFileNameExW( hProcess, hMod, szProcessName,
sizeof(szProcessName)/sizeof(TCHAR));
int len = wcslen(szProcessName);
if(!wcscmp(L"\\adb.exe",szProcessName+len-8)){
shouldKill = true;
}
}
}
if(shouldKill) TerminateProcess(hProcess,0);
CloseHandle( hProcess );
}
}
int testadb(){
KillAdbProcess();
char buff[4096] = {0};
int len = sizeof(buff);
DWORD exitCode = 0;
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(sa));
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(sa);
HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
// Create the child output pipe.
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
return false;
// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&hOutputRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
return false;
// Close inheritable copies of the handles you do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) return false;
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
STARTUPINFOW si;
GetStartupInfoW(&si);
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = NULL;
if(buff) {
si.hStdOutput = hOutputWrite;
si.hStdError = hOutputWrite;
} else {
si.hStdOutput = NULL;
si.hStdError = NULL;
}
wchar_t cmdBuf[512] = L"adb.exe start-server";
if( !::CreateProcessW(NULL, cmdBuf, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi) )
{
exitCode = -1;
goto exit;
}
::CloseHandle(hOutputWrite);
hOutputWrite = NULL;
len--; //keep it for string end char.
DWORD dwBytes = 0;
DWORD dwHasRead = 0;
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
printf("read byte=%d\n",dwBytes);
if(0 == dwBytes) break;
dwHasRead += dwBytes;
//GetExitCodeProcess(pi.hProcess, &exitCode);
//if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
buff[dwHasRead] = 0;
::GetExitCodeProcess(pi.hProcess, &exitCode);
exit:
if(hOutputRead) ::CloseHandle(hOutputRead);
if(hOutputWrite) ::CloseHandle(hOutputWrite);
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
return 0;
}
If I change code to
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
printf("read byte=%d\n",dwBytes);
if(0 == dwBytes) break;
dwHasRead += dwBytes;
GetExitCodeProcess(pi.hProcess, &exitCode);
if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
it works, but when I delete printf code, it will block again.
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
if(0 == dwBytes) break;
dwHasRead += dwBytes;
GetExitCodeProcess(pi.hProcess, &exitCode);
if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
In the code of adb.exe, I see some code like belows:
#if ADB_HOST
int launch_server()
{
#ifdef HAVE_WIN32_PROC
/* we need to start the server in the background */
/* we create a PIPE that will be used to wait for the server's "OK" */
/* message since the pipe handles must be inheritable, we use a */
/* security attribute */
HANDLE pipe_read, pipe_write;
SECURITY_ATTRIBUTES sa;
STARTUPINFO startup;
PROCESS_INFORMATION pinfo;
char program_path[ MAX_PATH ];
int ret;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
/* create pipe, and ensure its read handle isn't inheritable */
ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 );
if (!ret) {
fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() );
return -1;
}
SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 );
ZeroMemory( &startup, sizeof(startup) );
startup.cb = sizeof(startup);
startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE );
startup.hStdOutput = pipe_write;
startup.hStdError = GetStdHandle( STD_ERROR_HANDLE );
startup.dwFlags = STARTF_USESTDHANDLES;
ZeroMemory( &pinfo, sizeof(pinfo) );
/* get path of current program */
GetModuleFileName( NULL, program_path, sizeof(program_path) );
ret = CreateProcess(
program_path, /* program path */
"adb fork-server server",
/* the fork-server argument will set the
debug = 2 in the child */
NULL, /* process handle is not inheritable */
NULL, /* thread handle is not inheritable */
TRUE, /* yes, inherit some handles */
DETACHED_PROCESS, /* the new process doesn't have a console */
NULL, /* use parent's environment block */
NULL, /* use parent's starting directory */
&startup, /* startup info, i.e. std handles */
&pinfo );
CloseHandle( pipe_write );
if (!ret) {
fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() );
CloseHandle( pipe_read );
return -1;
}
CloseHandle( pinfo.hProcess );
CloseHandle( pinfo.hThread );
/* wait for the "OK\n" message */
{
char temp[3];
DWORD count;
ret = ReadFile( pipe_read, temp, 3, &count, NULL );
CloseHandle( pipe_read );
if ( !ret ) {
fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() );
return -1;
}
if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
fprintf(stderr, "ADB server didn't ACK\n" );
return -1;
}
}
#elif defined(HAVE_FORKEXEC)
char path[PATH_MAX];
int fd[2];
// set up a pipe so the child can tell us when it is ready.
// fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child.
if (pipe(fd)) {
fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
return -1;
}
get_my_path(path);
pid_t pid = fork();
if(pid < 0) return -1;
if (pid == 0) {
// child side of the fork
// redirect stderr to the pipe
// we use stderr instead of stdout due to stdout's buffering behavior.
adb_close(fd[0]);
dup2(fd[1], STDERR_FILENO);
adb_close(fd[1]);
// child process
int result = execl(path, "adb", "fork-server", "server", NULL);
// this should not return
fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
} else {
// parent side of the fork
char temp[3];
temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C';
// wait for the "OK\n" message
adb_close(fd[1]);
int ret = adb_read(fd[0], temp, 3);
adb_close(fd[0]);
if (ret < 0) {
fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno);
return -1;
}
if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
fprintf(stderr, "ADB server didn't ACK\n" );
return -1;
}
setsid();
}
#else
#error "cannot implement background server start on this platform"
#endif
return 0;
}
#endif
I think the child process of adb.exe inherit the handle of adb.exe, if the child process of adb.exe doesn't exit, ReadFile will block for ever. But when I exec "adb.exe start-server" in command, all is Ok. So how does windows command call CreateProcess and ReadFile?
I have found the answer: Redirecting an arbitrary Console's Input/Output - CodeProject.
The technique of redirecting the input/output of a console process is very sample: The CreateProcess() API through the STARTUPINFO structure enables us to redirect the standard handles of a child console based process. So we can set these handles to either a pipe handle, file handle, or any handle that we can read and write. The detail of this technique has been described clearly in MSDN: HOWTO: Spawn Console Processes with Redirected Standard Handles.
However, MSDN's sample code has two big problem. First, it assumes the child process will send output at first, then wait for input, then flush the output buffer and exit. If the child process doesn't behave like that, the parent process will be hung up. The reason of this is the ReadFile() function remains blocked untill the child process sends some output, or exits.
Second, It has problem to redirect a 16-bit console (including console based MS-DOS applications.) On Windows 9x, ReadFile remains blocked even after the child process has terminated; On Windows NT/XP, ReadFile always returns FALSE with error code set to ERROR_BROKEN_PIPE if the child process is a DOS application.
Solving the block problem of ReadFile
To prevent the parent process from being blocked by ReadFile, we can simply pass a file handle as stdout to the child process, then monitor this file. A more simple way is to call PeekNamedPipe() function before calling ReadFile(). The PeekNamedPipe function checks information about data in the pipe, then returns immediately. If there's no data available in the pipe, don't call ReadFile.
By calling PeekNamedPipe before ReadFile, we also solve the block problem of redirecting a 16-bit console on Windows 9x.